Знакомство с Presto — Часть Вторая

в 10:25, , рубрики: framework, ruby, метки: ,

Знакомство с Presto — Часть Вторая

Продолжаем осваивать простоту использования свеже-выRUBYленного веб-фреймворка.

Это вторая часть и посвящена она целомудрию HTML рендеринга.

А именно:

  • выбор движка и расширения
  • установка пути к шаблонам
  • как, где и кем используются лайоуты
  • рендеринг текущего/произвольного акшиона
  • рендеринг произвольного файла/лайоута
  • компиляция шаблонов для увеличения производительности
  • использование изолированного окружения для увеличения уровня безопасности
  • итд

Шаблоны — Опции

Ниже приведён список опций для тонкого насатраивания View Api.

Важно отметить что все опции можно установить как на уровне контролера, так и на уровне слайса.

Выбор Движка

Благодоря такому замечательному проекту как Tilt, Presto поддерживает большинство популярных шаблонных движков как Haml, Sass, CoffeeScript, Erubis, Liquid и другие.
Тут полный список.

По умолчанию, Presto использует ERB.

Чтобы использовать другой движок, надо вызвать view.engine на уровне контролера или слайса.

Пример: Юзаем :Erubis на уровне контролера

class News
	include Presto::Api
	# basic setup
	
	view.engine :Erubis
end

Пример: Юзаем :Haml на уровне слайса

module Ctrl
	class News
		include Presto::Api
		# basic setup

	end
	
	class Articles
		include Presto::Api
		# basic setup

		view.engine :Erubis
	end
end

app = Presto::App.new
app.mount Ctrl do
	view.engine :Haml
end
app.run

В данном примере, News будет использовать :Haml, тогда как Articles будет использовать :Erubis.

Если выбранный движок нуждается в аргументах при инициализации, передаём их через тот-же view.engine.

view.engine :SomeEngine, :some_arg, :some => :option

Расширение

Presto будет использовать расширение которое ассоциировано с движком по умолчанию: Erubis => .erb, Haml => .haml etc.

Если ваши шаблоны используют нестандартное расширение, дайте об этом знать посредством view.ext

class News
	include Presto::Api
	# basic setup
	
	view.engine :Erubis
	view.ext :xhtml
end

Путь к шаблонам

По умолчанию, Presto будет искать шаблоны в папке view/ внутри вашего приложения.

Если же ваши шаблоны находятся в другой папке, дайте об этом знать посредством view.path

module Ctrl
	class Index
		include Presto::Api
		http.map

	end
	
	class Articles
		include Presto::Api
		http.map :articles
	end
end

app = Presto::App.new
app.mount Ctrl do
	view.path 'base/views'
end
app.run

В данном примере, класс Index будет искать шаблоны в папке base/views/, тогда как Articles — в папке base/views/articles.

То есть Presto формирует путь к шаблонам добавив http.map к view.path.

Имя самого шаблона формируется из имени акшиона и расширения.
Расширение устанавливается автоматически, или же можно установить собственный через view.ext

class Users
	include Presto::Api
	http.map :users
	view.engine :Haml
	
	def index
		# some logic
		view.render
	end

	def register
		# some logic
		view.render
	end
end

/users/index будет рендерить view/users/index.haml
/users/register будет рендерить view/users/register.haml

Стоит отметить что при нестандартных ситуациях, когда шаблоны находятся на луне или на марсе,
надо использовать view.root для установления абсолютного пути к шаблонам.
В данном случае, view.path игнорируется.

Пример

class News
	include Presto::Api
	# basic setup
	view.root File.expand_path '../../../shared-templates', __FILE__
end

Путь к лайоутам

Ожидается что лайоуты находятся в той-же папке что и шаблоны.
Если же они находятся в другой папке, нужно использовать view.layouts_path
чтобы задать правильный путь. Правильный путь нужно задать относительно папки с шаблонами.
Абсолютный путь к лайоутам задать некак, они должны быть внутри папки с шаблонами.

Пример: лайоуты находятся в папке layouts/ внутри папки с шаблонами

class News
	include Presto::Api
	# basic setup
	view.layouts_path 'layouts/'
end

Лайоут

По умолчанию, Presto не будет рендерить никакие лайоуты.
Presto мог бы конечно проверять папку с шаблонами на наличие лайоута и рендерить его в случае нахождения.

Но это сильно жестоко для производительности.
Файловые операции чувствительно тормозят приложение.

Програмер лучше знает, как и когда его приложение использует лайоуты,
и он должен разделить эти знания с дружелюбным фреймворком.

Так что если ваш контролер или слайс использует лайоуты, дайте об этом знать посредством view.layout

Если вызвать view.layout только с одним аргументом, он установит лайоут для всех акшионов.

Пример: все акшионы используют :master лайоут

class News
	include Presto::Api
	# basic setup
	view.layout :master
end

Если вызвать view.layout c двумя или бодее аргументами, он установит лайоут для акшионов переданных как аргументы.

Пример: :signin и :signup используют :register_layout лайоут, остальные лайоут не используют

class News
	include Presto::Api
	# basic setup
	view.layout :register_layout, :signin, :signup
end

Можно также игнорировать лайоут для определённого списка акшионов, передав false [Boolean] в качестве первого аргумента.

Пример: все акшионы используют :default, тогда как print игнорирует лайоут

class Articles
	include Presto::Api
	# basic setup
	view.layout :default
	view.layout false, :print
end

view.layout можно вызывать много раз, такой уж он отзывчивый.
Порядок вызовов на установку не влияет, такой уж он понимающий.

Пример: :apple и :orange используют :fruits лайоут, остальные — :default

class News
	include Presto::Api
	# basic setup
	view.layout :default
	view.layout :register, :apple, :orange
end

Контекст

По умолчанию, шаблоны рендерятся в контексте текущего инициированного контролера.

То есть, шаблону будут доступны все переменные и методы доступны контролеру.

Удобно но не всегда безопасно.

Для увеличения уровня безопасности стоит рендерить шаблоны в изолированном окружении.
В Presto, это делается легко и просто посредством view.scope

Файл app.rb

class Sandbox
    def initialize params
      @params = params
    end
end

class HelloWorld
    include Presto::Api
    http.map

    view.scope do
      Sandbox.new http.params
    end

    def index
      view.render_partial
    end
end
Presto::App.new { mount HelloWorld }.run

Файл view/index.rhtml

Hello <%= @params['var'] %>

Теперь, если в браузере набрать /index/?var=World, ответом будет «Hello World»

В прочем, view.scope не обязательно должен быть блоком.
Можно передать любой объект в качестве контекста.

Даже так:

view.scope Object.new

Стоит отметить что контекст можно задать как на уровне контролера или слайса, так и на уровне вызываемого метода.
Не будем же изолировать весь контролер/слайс из за одного метода.

def register
	view.render Sandbox.new(http.params)
end

или

def login
	view.render_partial Object.new
end

Компиляция

Для большинства веб-сайтов, наибольшее время тратиться на чтение и рендеринг шаблонов.
При рендере, наибольшее время тратиться на компиляцию шаблонов.

Presto предлагает простой и надёжный способ обойти чтение и компиляцию шаблонов.

Способ отнюдь не является новым и используется довольно часто в стране веб.

Всё просто — при первом запросе шаблон компилируется и сохранятся в памяти или высоко-скоростном БД.
При последующих запросах, шаблон извлекается и рендерится, обходя файловые вызовы для чтения и экономя процессорное время для компиляции.

Обратите внимание — это не кэширование шаблонов!
Шабон остаётся динамическим, все переменные и методы будут работать как обычно.

Естественно, данное добро, по умолчанию отключенно.

Чтобы включить, нужно вызвать view.compile, с аргументами или без, но с обязательном блоком.
Блок будет решать при каких запросах использовать компилированный шаблон, при каких не использовать,
и при каких пере-компилировать.

  • Если блок возвращает true [Boolean] или же любое non-nil / non-false значение, будет использоваться ранее компилированный шаблон.
  • Если блок возвращает :update [Symbol], шаблоны текущего акшиона пере-компилируются.
  • Если блок возвращает :purge или :truncate [Symbol], все шаблоны данного контролера пере-компилируется.
  • И соответственно, если блок вернёт nil или false, компилированная версия игнорируется и шаблон(ы) читаются с диска с последующей компиляцией.

Пример: все акшионы используют компиляцию

class News
	include Presto::Api
	# basic setup
	view.compile { true }
end

Пример: толко :details и :features используют компиляцию

class News
	include Presto::Api
	# basic setup
	view.compile :details, :features do
		true
	end
end

Пример: все акшионы используют компиляцию, пере-компилируя шаблоны если в HTTP параметрах обнаружена переменная «recompile»

class News
	include Presto::Api
	# basic setup
	view.compile do
		http.params['recompile'] ? :update : true
	end
end

Важно отметить что блок выполняется при каждом запросе, так что логика в нём должна быть наипростейшей.
Логика блока должна занимать намного меньше одной миллисекунды, иначе, компиляция не имеет смысла.

Подробнее на счёт хранилища

По умолчанию компилированные шаблоны хранятся в оперативной памяти.
Быстро и Удобно.
Но в некоторых случаях может стать разумнее использовать MongoDB(для этого(и других целей) у Presto есть встроенный адаптер к MongoDB).

В таком случае устанавливаем хранилище посредством view.compiler_pool

Пример: используем MongoDB для хранения компилированных шаблонов

class News
	include Presto::Api
	# basic setup

	db = Mongo::Connection.new('localhost').db('db-name')
	view.compiler_pool Presto::Cache::MongoDB.new(db)

	view.compile do
		http.params['recompile'] ? :update : true
	end
end

Шаблоны — Рендеринг

view.render

Данный метод рендерит текущий акшион вместе с лайоутом.

Пример: рендерим текущий акшион

class News
	include Presto::Api
	# basic setup
	def details
		# some logic
		view.render
	end
end

Можно также рендерить иной акшион, передав его имя в качестве первого аргумента.
При этом, сам акшион может не существовать, главное чтобы его шаблон был на месте.

Пример: рендерим иной акшион

class News
	include Presto::Api
	# basic setup
	def details
		# some logic
		view.render :about
	end
end

Примечательно что иной акшион рендерится только если первый аргумент является символом.
Если же первый аргумент является объектом другого типа(кроме хэша), он используется как контекст.

Пример: рендерим текущий акшион с измененным контекстом

class News
	include Presto::Api
	# basic setup
	def details
		# some logic
		view.render Object.new
	end
end

Кроме контекста можно также передать список переменных, в виде хэша.

Пример: рендерим текущий акшион с измененным контекстом и со списком переменных

class Product
	include Presto::Api
	# basic setup
	def details
		item = blah_blah_get_item
		view.render Object.new, price: item.price, weight: item.weight
	end
end

Стоит отметить что переменные переданные списком будут доступны в виде instance methods нежели в виде instance variables.

Так что шаблон для примера выше надо оформить примерно так:

Price: <%= price %>
Weight: <%= weight %>

То есть в данном примере, @price и @weight работать не будут.

И конечно-же можно рендерить в текущем контексте и передать список дополнительных переменных.

Пример: рендерим текущий акшион со списком дополнительных переменных

class Product
	include Presto::Api
	# basic setup
	def details
		@item = blah_blah_get_item
		view.render var: :val
	end
end

Важно отметить что тоже самое можно проделать и при рендере иного акшиона, просто передав имя акшиона в качестве первого аргумента.

Пример: рендерим иной акшион

class Product
	include Presto::Api
	# basic setup
	def details
		@item = blah_blah_get_item

		# рендерим в текущем контексте
		view.render :about

		# рендерим в текущем контексте и со списком дополнительных переменных	
		view.render :about, var: :val

		# рендерим с изменёным контекстом
		view.render :about, Object.new

		# рендерим с изменёным контекстом и со списком переменных
		view.render :about, Object.new, price: @item.price, weight: @item.weight

	end
end

view.render_partial

Аналогичен view.render с единственной разницей — шаблоны рендерятся без лайоута.

Аналогичен в полном смысле слова, так что можно рендерить текущий или иной акшион,
в текущем или изменённом контексте, со списком дополнительных переменных или без.


view.render_view

Используется когда нужно рендерить шаблон из другого контролера или же просто файл из папки шаблонов.

Пример

class Product
	include Presto::Api
	# basic setup
	def details

		@pager = view.render_view 'pager'
		@news = view.render_view 'news/latest'

	end
end

В плане контекста и списка переменных, view.render_view полностью аналогичен
view.render и view.render_partial


view.render_layout

Удобен в случаях когда нужно рендерить произвольный файл как лайоут.

Примечание: чтобы файл работал как лайоут, он должен содержать yield в месте где будет вставляться контент.

Первый аргумент обязателен и должен содержать имя файла(файл должен находится в папке шаблонов).

Остальные аргументы, как и в случае с другими рендер методами, используются для контекста и дополнительных переменных.

Контент который нужно вставить в лайоут передаётся посредством блока.

Пример

view/pager/layout.erb

header

	<%= yield %>

footer

app.rb

class Product
	include Presto::Api
	# basic setup
	def index

		@pager = view.render_layout 'pager/layout' do
			view.render_view 'pager/default'
		end

	end
end

view.render_*_view, view.render_*_layout

Аналоги view.render_view и view.render_layout но для случаев когда надо рендерить произвольный файл используя иной движок.

Пример: основной движок Erubis, но надо рендерить Haml файл

class Product
	include Presto::Api
	view.engine :Erubis

	def details

		@pager = view.render_haml_layout 'pager/layout' do
			view.render_haml_view 'pager/default'
		end

	end
end

Presto включает подобные методы для всех поддерживаемых движков — render_rdiscount_view/render_rdiscount_layout,
render_markdown_view/render_markdown_layout etc.

Правило измененного контекста и дополнительных переменных работает и здесь без исключений.


Кросс-контролерный рендеринг

Примечательно что любой акшион любого контролера можно рендерить без инициализации самого контролера,
то есть рендерить на уровне класса, а не инстанса.

Пример: рендерим Shoes из Pants

class Shoes
	include Presto::Api
	view.engine :Erubis

	def laces
		view.render
	end
end

class Pants
	include Presto::Api
	view.engine :Haml
	
	def index
		@shoe_laces = Shoes.view.render :laces, self
	end
end

И здесь правило измененного контекста и дополнительных переменных работает без исключений.
В примере выше, мы использовали self в качестве контекста,
так что Shoes#laces будет рендерится в контексте текущего контролера, таким образом имея доступ к HTTP параметрам.


На этом, с «шаблонизмом» закончено.

И со второй частью знакомства тоже.

Увы не получилось рассказать про то всё что было обещано в конце первой части.
Рендеринг занял довольно много пространства(и времени),
так что, про content_type, hooks, cache, middleware, error handling, authentication, sandbox и многое другое в следующей части.

Автор: slivu

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js