Уровень представления в традиционном приложении Rails не претерпел значительных изменений с тех пор, как rails 1.0 была выпущена еще в сумасшедших середине девяностых. Конечно, вы могли перейти с Prototype на jQuery, с XHTML на HTML5, с CSS на более приятный и отзывчивый CSS, — но все, что всегда было движущей силой — наш хороший друг ERB. Возможно, пришло время для перемен?
Но что не так с ERB?
ERB отлично подходит для определенных типов приложений, но у него есть некоторые недостатки, как эстетические, так и практические.
Полный рубин
Что за поговорка? «С великой силой приходит ужасное разделение интересов». ERB в значительной степени дает вам доступ ко всей полной и ужасающей силе Ruby. Искушение всегда будет близко к флирту с моделями и библиотеками, которые не имеют никакого дела, вызываемого в коде представления. Конечно, только один раз не преступление, но искушение есть и скользкий путь.
Кроме того, возможность выполнения произвольного кода в ваших представлениях означает, что ваш слой представления застрял в доме. Вы не сможете позволить сторонним разработчикам создавать темы для вашего приложения (если это то, что вы хотели бы, чтобы они делали), потому что они всего лишь обратная галочка и плохо настроили сервер, чтобы не причинять какой-либо реальный ущерб, если вы с ними поссорились.
Вещи могут быть лучше
Если есть что-то, что верно в сообществе Ruby — возможно, иногда из-за ошибки — то, что есть общее желание, чтобы код был красивым. ERB не прекрасна, но некоторые усилия, такие как усы и HAML, позволяют большую часть силы ERB, улучшая при этом эстетику.
Используя жидкость
Мой предпочитаемый язык шаблонов — Liquid. Жидкость была извлечена из Shopify и поддерживает их усилия по созданию магазина. Я ценю жидкость в два раза: во-первых, она позволяет создавать красивые безопасные шаблоны, которые я могу позволить (опытным) пользователям редактировать почти так же, как это делает Shopify. Во-вторых, оно обеспечивает более четкое различие между уровнем данных вашего приложения и уровнем представления.
Начиная
Как и с большинством вещей Rails, установка очень проста (перейдите на главную страницу Liquid, чтобы узнать последние инструкции ).
Вообще говоря, жидкие шаблоны анализируются и затем отображаются с контекстом. Контекст, в большинстве простых случаев, должен быть только Hash. При написании шаблона каждый ключ в хэше становится именем переменной, значение которой полностью доступно для вас. Заменить эти ключи можно с помощью двойных фигурных скобок: {{x}}
template = «{{foo}}» | |
Liquid::Template.parse(template).render({‘foo’ => ‘bar’}) |
фильтры
Одна из особенностей, которые я люблю в жидкости — это фильтры. Фильтры ведут себя примерно так же, и их следует использовать для тех же целей, что и помощники в Rails. Это короткие методы, которые помогают в представлении информации. Давайте посмотрим, как мы можем использовать фильтры.
{{ ‘world’ | prepend:’hello’ | upcase }} |
Как вы можете догадаться, это выведет строку HELLOWORLD . Здесь нужно запомнить несколько вещей. Каждый канал означает, что выходные данные предыдущего раздела будут переданы следующему, поэтому «мир» передается методу prepend с дополнительным аргументом hello, который передается методу upcase — так что мы можем получить крик.
Теги и блоки
Фильтры хороши, но в конечном итоге вы захотите выполнить некоторую логику, например, оператор if или цикл for. Для этого у нас есть теги и блоки.
Теги, как и ERB, используют альтернативный синтаксис для логических разделов: {%%} . Это лучше всего показывает пример.
{% assign name = ‘bar’ %} | |
{% if name == ‘bar’ %} | |
{{name}} | |
{%endif%} |
Удлиняющая жидкость
Итак, это почти все, что вам нужно знать, чтобы начать использовать жидкость для своих шаблонных нужд. Одна из замечательных вещей в жидкости, по сравнению с другими подобными шаблонными двигателями, заключается в том, насколько она расширяема.
фильтры
Фильтры в значительной степени просто помощники. Они принимают один или несколько аргументов, первый из которых является значением слева от канала (|), дополнительные аргументы передаются один за другим после этого. Давайте посмотрим на реализацию усеченного фильтра из стандартного набора.
#{{ some_text | truncate: 20, » }} | |
def truncate(input, length = 50, truncate_string = «…») | |
if input.nil? then return end | |
l = length.to_i — truncate_string.length | |
l = 0 if l < 0 | |
input.length > length.to_i ? input[0…l] + truncate_string : input | |
end |
Значение «некоторого текста» будет передано в качестве входных данных, а длина и усеченная строка пройдены по порядку.
Теги и блоки
Теги и блоки, как мы видели, обеспечивают расширяемость по сравнению с фильтрами. Чтобы проиллюстрировать, насколько полезными могут быть блоки, мы напишем блок, который позволяет нашему дизайнеру обрабатывать конкретную временную шкалу Twitter и выводить ее любым удобным для него способом. Использование будет выглядеть примерно так:
{% rss source: http://rubysource.com/feed/%} | |
<article class=’main rss_list’> | |
<h1>{{ feed.title }}</h1> | |
{% for item in feed.items limit:3%} | |
<section> | |
<h1><a href='{{item.link}}’>{{item.title}}</a></h1> | |
<p>{{item.description}}</p> | |
</section> | |
<hr/> | |
{%endfor%} | |
</article> | |
{% endrss %} |
Первым шагом в создании нового блока является наследование от Liquid::Block
и определение инициализатора. Вы захотите вызвать super в вашем инициализаторе в первой строке, он сделает кучу настроек для вас. Инициализатор принимает три аргумента.
- название тэга
- разметка — аргументы передаются как блоки. В приведенном выше примере использования это будет
source: http://www.sitepoint.com/feed/
строкиsource: http://www.sitepoint.com/feed/
- токены — полностью пройденный список токенов. Если вы не делаете довольно продвинутый тег, который попадает в кишки жидкости, вы можете игнорировать это.
Поскольку у нас есть некоторые аргументы для обработки, а именно источник нашего RSS-канала, нам нужно немного разобрать. Liquid Gem предоставляет Regex, Liquid::TagAttributes
, который поможет нам обработать вышеприведенную строку и получить хеш так, как вы ожидаете.
Фактический рендеринг в жидком блоке выполняется методом render
. Метод render
всегда принимает один аргумент: context. Контекст является текущим состоянием стека рендеринга. Все ваши входы и данные живут в этой структуре данных.
Мы создаем блок, который будет создавать временные переменные, которые будут использоваться только в контексте блока. Это ведет себя так же, как блок в ruby (например, если вы вызываете каждый из них в массиве). Мы можем временно #stack
в стек, вызвав #stack
для объекта контекста и предоставив блок.
Если вы планируете иметь какие-либо выходные данные для своего блока — и в этом случае мы определенно делаем — тогда #render_all
является метод #render_all
. Он принимает два аргумента: контекст и список узлов. Если вы назвали super
в своем инициализаторе, то список узлов будет предоставлен как @nodelist
.
Итак, соберите все эти части вместе, и вы должны придумать что-то вроде этого:
class RenderRSS < Liquid::Block | |
def initialize(tag_name, markup, tokens) | |
super | |
@markup = markup | |
@attributes = {} | |
markup.scan(Liquid::TagAttributes) do |key, value| | |
@attributes[key] = value | |
end | |
RestClient.get(@attributes[‘source’]).force_encoding(‘UTF-8’) | |
@parsed_rss = SimpleRSS.parse(rss_data) | |
end | |
def render(context) | |
rss_reg = { | |
«title» => @parsed_rss.title, | |
«items» => @parsed_rss.items.map{|u| u.stringify_keys} | |
} | |
context.stack do | |
context[‘feed’] = rss_reg | |
render_all(@nodelist, context) | |
end | |
end | |
end | |
Liquid::Template.register_tag(‘rss’, RenderRSS) |
Вы почти наверняка захотите добавить кеширование и обработку ошибок здесь — я оставил это для удобства чтения.
Стоит помнить, что аргументы этого блока всегда будут передаваться в виде строк, и поэтому вы не сможете использовать переменные для заполнения источника из коробки. Но, независимо от того, что контекст (который вы запомните, передается методу рендеринга) предоставляет все переменные, которые использовал дизайнер. Где-то в блоке рендеринга вы сможете извлечь значение переменной следующим образом:
source = context[@attributes[‘source’]] if context.has_key? @attributes[‘source’] |
Обратите внимание, как мы сначала проверяем, имеет ли контекст соответствующий ключ. Мы должны сделать это, потому что у нас нет способа узнать, ссылается ли токен на переменную или это просто строка. В качестве альтернативы, для этого конкретного тега вы можете проверить, выглядит ли источник как URL, прежде чем проверять контекст.
Заворачивать
Итак, я надеюсь, вы согласитесь, что Liquid довольно опрятен. Это мощный и выразительный языковой шаблон, который отлично справляется с предоставлением вам («программисту») инструментов, позволяющих вашим дизайнерам создавать отличные сайты, не беспокоясь о том, что они все это rm -rf
. Если повезет, мне было ясно — но если нет, я буду скрываться в разделе комментариев, если у вас есть какие-либо вопросы.