Знакомство с 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