Обзор Action View

После прочтения этого руководства вы узнаете:

  • Что такое Action View, и как его использовать вместе с Rails.
  • Как лучше использовать шаблоны, партиалы и макеты.
  • Как использовать локализованные вьюхи.

1. Что такое Action View?

В Rails веб-запросы обрабатываются Action Controller и Action View. Обычно Action Controller ответственен за связь с базой данных и выполнение экшнов CRUD. Тогда как Action View ответственен за компиляцию отклика.

Шаблоны Action View пишутся с помощью тегов встроенного Ruby, смешанных с HTML. Чтобы избежать загромождения вьюх шаблонным кодом, общее поведение для форм, дат и строк представлено рядом классов хелпера. В существующее приложение также легко добавлять новые хелперы.

Некоторые особенности Action View связаны с Active Record, но это не означает, что Action View зависит от Active Record. Action View — это независимый пакет, который можно использовать с любой библиотекой Ruby.

2. Использование Action View с Rails

Для каждого контроллера имеется связанная директория в директории app/views, содержащая файлы шаблонов, которые формируют вьюхи, связанные с этим контроллером. Эти файлы используются для отображения вьюхи, являющейся результатом каждого экшна контроллера.

Давайте взглянем на то, что делает Rails по умолчанию, когда создает новый ресурс с помощью генератора скаффолда:

$ bin/rails generate scaffold article
      [...]
      invoke  scaffold_controller
      create    app/controllers/articles_controller.rb
      invoke    erb
      create      app/views/articles
      create      app/views/articles/index.html.erb
      create      app/views/articles/edit.html.erb
      create      app/views/articles/show.html.erb
      create      app/views/articles/new.html.erb
      create      app/views/articles/_form.html.erb
      [...]

В Rails имеется соглашение по именованию вьюх. Как правило, имя вьюхи совпадает с соответствующим экшном контроллера, как вы могли видеть выше. Например, экшн index контроллера в articles_controller.rb будет использовать файл вьюхи index.html.erb в директории app/views/articles. Полный HTML, возвращенный клиенту, состоит из комбинации этого файла ERB, шаблона макета, оборачивающего его, и всех партиалов, на которые вьюха может ссылаться. В этом руководстве имеется более детальное описание каждого из этих трех компонентов.

3. Шаблоны, партиалы и макеты

Как уже упоминалось, итоговый HTML состоит из трех элементов Rails: шаблонов (Templates), партиалов (Partials) и макетов (Layouts). Ниже краткий обзор каждого из них.

3.1. Шаблоны

Шаблоны Action View могут быть написаны несколькими способами. Если у файла шаблона расширение .erb, то он использует смесь ERB (Embedded Ruby) и HTML. Если у файла шаблона расширение .builder, то используется библиотека Builder::XmlMarkup.

Rails поддерживает несколько систем шаблонирования и использует расширение файла, чтобы различать их. Например, файл HTML, использующий систему шаблонирования ERB, будет иметь расширение файла .html.erb.

3.1.1. ERB

В шаблоне ERB код Ruby может быть включен с помощью тегов <% %> и <%= %>. Теги <% %> используются для выполнения кода Ruby, который ничего не возвращает, такого как условия, циклы или блоки, а теги <%= %> используются, когда вам нужен результат выполнения.

Рассмотрим следующий цикл для имен:

<h1>Names of all the people</h1>
<% @people.each do |person| %>
  Name: <%= person.name %><br>
<% end %>

Цикл настроен с помощью обычных встраиваемых тегов (<% %>), а имя вставлено с помощью выводящих встраиваемых тегов (<%= %>). Отметьте, что это не просто совет по использованию: обычные функции для вывода, такие как print и puts не будут рендериться во вьюху в шаблонах ERB. Поэтому, так будет неправильно:

<%# WRONG %>
Hi, Mr. <% puts "Frodo" %>

Чтобы запретить предварительные и завершающие пробелы можно использовать <%- -%> вместо <% и %>.

3.1.2. Builder

Шаблоны Builder — это более программная альтернатива ERB. Они особенно полезны для генерации содержимого в XML. Объект XmlMarkup с именем xml автоматически доступен в шаблонах с расширением .builder.

Вот несколько простых примеров:

xml.em("emphasized")
xml.em { xml.b("emph & bold") }
xml.a("A Link", "href" => "https://rubyonrails.org")
xml.target("name" => "compile", "option" => "fast")

которые создадут:

<em>emphasized</em>
<em><b>emph &amp; bold</b></em>
<a href="https://rubyonrails.org">A link</a>
<target option="fast" name="compile" />

Любой метод с блоком будет трактован как разметка тега XML с вложенной разметкой в блоке. Например, следующее:

xml.div {
  xml.h1(@person.name)
  xml.p(@person.bio)
}

создаст что-то вроде:

<div>
  <h1>David Heinemeier Hansson</h1>
  <p>A product of Danish Design during the Winter of '79...</p>
</div>

Ниже приведен полноценный пример RSS, фактически используемый в Basecamp:

xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
  xml.channel do
    xml.title(@feed_title)
    xml.link(@url)
    xml.description "Basecamp: Recent items"
    xml.language "en-us"
    xml.ttl "40"

    for item in @recent_items
      xml.item do
        xml.title(item_title(item))
        xml.description(item_description(item)) if item_description(item)
        xml.pubDate(item_pubDate(item))
        xml.guid(@person.firm.account.url + @recent_items.url(item))
        xml.link(@person.firm.account.url + @recent_items.url(item))
        xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
      end
    end
  end
end

3.1.3. Jbuilder

Jbuilder — это гем, поддерживаемый командой Rails и включенный в Rails Gemfile по умолчанию. Он похож на Builder, но используется для генерации JSON вместо XML.

Если у вас его нет, можно добавить следующее в Gemfile:

gem 'jbuilder'

Объект Jbuilder с именем json автоматически становится доступным в шаблонах с расширением .jbuilder.

Вот простой пример:

json.name("Alex")
json.email("alex@example.com")

что создаст:

{
  "name": "Alex",
  "email": "alex@example.com"
}

Больше примеров и информации в документации Jbuilder.

3.1.4. Кэширование шаблонов

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

3.2. Партиалы

Частичные шаблоны - обычно называемые "партиалы" - это другая схема разделения процесса рендеринга на более управляемые части. С помощью партиалов можно извлечь части кода из ваших шаблонов в отдельные файлы, а также повторно использовать их в разных шаблонах.

3.2.1. Именование партиалов

Чтобы отрендерить партиал как часть вьюхи, в ней используется метод render:

<%= render "menu" %>

Это отрендерит файл с именем _menu.html.erb во вьюхе, которая рендерится. Обратите внимание на первый символ подчеркивания: партиалы именуются, начинаясь с подчеркивания, чтобы отличить их от обычных вьюх, хотя к ним и обращаются без подчеркивания. Это истинно, даже если вы вставляете партиал из другой директории:

<%= render "shared/menu" %>

Этот код вставит партиал из app/views/shared/_menu.html.erb.

3.2.2. Использование партиалов для упрощения вьюх

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

<%= render "shared/ad_banner" %>

<h1>Products</h1>

<p>Here are a few of our fine products:</p>
<% @products.each do |product| %>
  <%= render partial: "product", locals: { product: product } %>
<% end %>

<%= render "shared/footer" %>

Здесь партиалы _ad_banner.html.erb и _footer.html.erb могут содержать контент, общий для многих страниц приложения. Нет необходимости видеть код этих разделов, чтобы сконцентрироваться на определенной странице.

3.2.3. render без опций partial и locals

В вышеприведенном примере render принимает 2 опции: partial и locals. Но если это единственные опции, которые нужно передать, их можно опустить. Например, вместо:

<%= render partial: "product", locals: { product: @product } %>

Можно сделать:

<%= render "product", product: @product %>

3.2.4. Опции as и object

По умолчанию у ActionView::Partials::PartialRenderer есть собственный объект в локальной переменной с тем же именем, как у шаблона. Так, если имеем:

<%= render partial: "product" %>

в партиале _product мы получим @product в локальной переменной product, как будто мы написали:

<%= render partial: "product", locals: { product: @product } %>

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

Например, вместо:

<%= render partial: "product", locals: { product: @item } %>

можно сделать:

<%= render partial: "product", object: @item %>

С помощью опции as можно указать другое имя для данной локальной переменной. Например, если нам нужно, чтобы оно было item вместо product, мы сделаем:

<%= render partial: "product", object: @item, as: "item" %>

Это эквивалентно:

<%= render partial: "product", locals: { item: @item } %>

3.2.5. Рендеринг коллекций

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

Поэтому такой пример для рендеринга всех продуктов:

<% @products.each do |product| %>
  <%= render partial: "product", locals: { product: product } %>
<% end %>

может быть переписан с помощью одной строчки:

<%= render partial: "product", collection: @products %>

Когда партиал вызывается с коллекцией, у отдельных экземпляров партиала есть доступ к рендерящемуся члену коллекции через переменную с именем партиала. В данном случае партиалом является _product, и внутри него можно обратиться к product, чтобы получить член коллекции, который будет отрендерен.

Для рендеринга коллекций можно использовать сокращенный синтаксис. Предположим, @products — это коллекция экземпляров Product, тогда можно просто написать следующее, чтобы получить тот же самый результат:

<%= render @products %>

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

3.2.6. Разделяющие шаблоны

Также можете определить второй партиал, который будет отрендерен между экземплярами главного партиала, используя опцию :spacer_template:

<%= render partial: @products, spacer_template: "product_ruler" %>

Rails отрендерит партиал _product_ruler (без переданных в него данных) между каждой парой партиалов _product.

3.3. Макеты

Макеты могут быть использованы для рендеринга общего шаблона вьюхи вокруг результатов экшна контроллера Rails. Обычно в приложении Rails несколько макетов, в которых будут рендериться страницы. Например, на сайте может быть один макет для авторизованного пользователя и другой для маркетинга или продаж. Макет для авторизованного пользователя может включать навигацию верхнего уровня, которая должна присутствовать во многих экшнах контроллера. Макет для продаж для приложения SaaS может включать верхнеуровневую навигацию для таких, скажем, страниц как "Pricing" и "Contact Us". Ожидается, что каждый макет должен выглядеть по разному. Подробнее о макетах можно прочитать в руководстве Макеты и рендеринг в Rails.

4. Макеты партиалов

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

Допустим, мы отображаем статью на странице, которая должна быть обернута в div, с целью отображения ее как блочный элемент. Сначала мы создадим новую Article:

Article.create(body: 'Partial Layouts are cool!')

В шаблоне show мы отрендерим партиал _article, обернутый в макет box:

articles/show.html.erb

<%= render partial: 'article', layout: 'box', locals: { article: @article } %>

Макет box просто оборачивает партиал _article в div:

articles/_box.html.erb

<div class='box'>
  <%= yield %>
</div>

Отметьте, что у макета партиала есть доступ к локальной переменной article, переданной в вызов render. Однако, в отличие от макетов приложения, макеты партиалов должны начинаться с подчеркивания.

Также можно отрендерить блок кода в макете партиала вместо вызова yield. Например, если у нас нет партиала _article, вместо него можно использовать это:

articles/show.html.erb

<% render(layout: 'box', locals: { article: @article }) do %>
  <div>
    <p><%= article.body %></p>
  </div>
<% end %>

Предположив, что мы используем тот же партиал _box, мы получим тот же результат, что и в предыдущем примере.

5. Пути вьюх

При рендеринге отклика контроллер должен решить, где располагаются различные вьюхи. По умолчанию он смотрит только в директории app/views.

Мы можем добавить другие места расположения и дать им некий приоритет при определении путей с помощью методов prepend_view_path и append_view_path.

5.1. Prepend view path

Метод может быть полезным, например, когда мы хотим поместить вьюхи в разные директории для поддоменов.

Мы можем сделать это используя:

prepend_view_path "app/views/#{request.subdomain}"

Тогда Action View при поиске нужной вьюхи будет искать сначала в этой директории.

5.2. Append view path

Аналогично, мы можем добавить путь:

append_view_path "app/views/direct"

Это добавит путь app/views/direct в конец поиска путей.

6. Хелперы

Rails предоставляет множество вспомогательных методов для использования с Action View. Эти методы включают:

  • Форматирование дат, строк и чисел
  • Создание ссылок HTML на картинки, видео, таблицы стилей и т.д...
  • Очистку содержимого
  • Создание форм
  • Локализацию содержимого

Подробнее о хелперах можно узнать в руководстве по хелперам Action View и руководстве по хелперам форм Action View.

7. Локализованные вьюхи

В Action View есть возможность рендерить различные шаблоны в зависимости от текущей локали.

Например, предположим, что у вас есть ArticlesController с экшном show. По умолчанию вызов этого экшна отрендерит app/views/articles/show.html.erb. Но если вы установите I18n.locale = :de, то вместо него будет отрендерен app/views/articles/show.de.html.erb. Если локализованный шаблон отсутствует, будет использована недекорированная версия. Это означает, что не нужно предоставлять локализованные вьюхи для всех случаев, но они будут предпочтительными и будут использоваться, если станут доступны.

Ту же технику можно использовать для локализации страниц ошибок в директории public. Например, установка I18n.locale = :de и создание public/500.de.html и public/404.de.html позволит иметь локализованные страницы ошибок.

Так как Rails не ограничивает символы, используемые для установления I18n.locale, эту систему можно использовать для отображения различного содержимого, зависящего от чего-либо иного. Например, предположим у вас есть пользователи "expert", которые должны видеть страницы иные, чем пользователи "normal". Можно добавить следующее в app/controllers/application.rb:

before_action :set_expert_locale

def set_expert_locale
  I18n.locale = :expert if current_user.expert?
end

Затем можно добавить специальные вьюхи, такие как app/views/articles/show.expert.html.erb которые будут отображены только пользователям expert.

Подробнее об API интернационализации Rails (I18n) можно прочитать в руководстве API интернационализации Rails (I18n).