Статьи

Ditching ERB: руководство по использованию жидкости

Уровень представления в традиционном приложении 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’})

view raw
gistfile1.rb
hosted with ❤ by GitHub

фильтры

Одна из особенностей, которые я люблю в жидкости — это фильтры. Фильтры ведут себя примерно так же, и их следует использовать для тех же целей, что и помощники в Rails. Это короткие методы, которые помогают в представлении информации. Давайте посмотрим, как мы можем использовать фильтры.

{{ ‘world’ | prepend:’hello’ | upcase }}

view raw
gistfile1.rb
hosted with ❤ by GitHub

Как вы можете догадаться, это выведет строку HELLOWORLD . Здесь нужно запомнить несколько вещей. Каждый канал означает, что выходные данные предыдущего раздела будут переданы следующему, поэтому «мир» передается методу prepend с дополнительным аргументом hello, который передается методу upcase — так что мы можем получить крик.

Теги и блоки

Фильтры хороши, но в конечном итоге вы захотите выполнить некоторую логику, например, оператор if или цикл for. Для этого у нас есть теги и блоки.
Теги, как и ERB, используют альтернативный синтаксис для логических разделов: {%%} . Это лучше всего показывает пример.

{% assign name = ‘bar’ %}
{% if name == ‘bar’ %}
{{name}}
{%endif%}

view raw
gistfile1.rb
hosted with ❤ by GitHub

Удлиняющая жидкость

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

фильтры

Фильтры в значительной степени просто помощники. Они принимают один или несколько аргументов, первый из которых является значением слева от канала (|), дополнительные аргументы передаются один за другим после этого. Давайте посмотрим на реализацию усеченного фильтра из стандартного набора.

#{{ some_text | truncate: 20, » }}
def truncate(input, length = 50, truncate_string = «…»)
if input.nil? then return end
l = length.to_itruncate_string.length
l = 0 if l < 0
input.length > length.to_i ? input[0l] + truncate_string : input
end

view raw
gistfile1.rb
hosted with ❤ by GitHub

Значение «некоторого текста» будет передано в качестве входных данных, а длина и усеченная строка пройдены по порядку.

Теги и блоки

Теги и блоки, как мы видели, обеспечивают расширяемость по сравнению с фильтрами. Чтобы проиллюстрировать, насколько полезными могут быть блоки, мы напишем блок, который позволяет нашему дизайнеру обрабатывать конкретную временную шкалу 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 %}

view raw
gistfile1.txt
hosted with ❤ by GitHub

Первым шагом в создании нового блока является наследование от Liquid::Block и определение инициализатора. Вы захотите вызвать super в вашем инициализаторе в первой строке, он сделает кучу настроек для вас. Инициализатор принимает три аргумента.

  1. название тэга
  2. разметка — аргументы передаются как блоки. В приведенном выше примере использования это будет source: http://www.sitepoint.com/feed/ строки source: http://www.sitepoint.com/feed/
  3. токены — полностью пройденный список токенов. Если вы не делаете довольно продвинутый тег, который попадает в кишки жидкости, вы можете игнорировать это.

Поскольку у нас есть некоторые аргументы для обработки, а именно источник нашего 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)

view raw
rss_tag.rb
hosted with ❤ by GitHub

Вы почти наверняка захотите добавить кеширование и обработку ошибок здесь — я оставил это для удобства чтения.

Стоит помнить, что аргументы этого блока всегда будут передаваться в виде строк, и поэтому вы не сможете использовать переменные для заполнения источника из коробки. Но, независимо от того, что контекст (который вы запомните, передается методу рендеринга) предоставляет все переменные, которые использовал дизайнер. Где-то в блоке рендеринга вы сможете извлечь значение переменной следующим образом:

source = context[@attributes[‘source’]] if context.has_key? @attributes[‘source’]

view raw
gistfile1.rb
hosted with ❤ by GitHub

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

Заворачивать

Итак, я надеюсь, вы согласитесь, что Liquid довольно опрятен. Это мощный и выразительный языковой шаблон, который отлично справляется с предоставлением вам («программисту») инструментов, позволяющих вашим дизайнерам создавать отличные сайты, не беспокоясь о том, что они все это rm -rf . Если повезет, мне было ясно — но если нет, я буду скрываться в разделе комментариев, если у вас есть какие-либо вопросы.