Уровень представления в традиционном приложении 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 . Если повезет, мне было ясно — но если нет, я буду скрываться в разделе комментариев, если у вас есть какие-либо вопросы.
