по-русски

Обзор Action View

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

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

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

Что такое Action View?

Action View - это V в MVC. Action Controller и Action View работают вместе для обработки веб-запросов. Action Controller отвечает за взаимодействие с уровнем модели (в MVC) и получение данных. Затем Action View отвечает за отрисовку тела ответа на веб-запрос, используя эти данные.

По умолчанию шаблоны Action View (также называемые просто "вью") пишутся с использованием Embedded Ruby (ERB), который позволяет использовать код Ruby внутри HTML-документов.

Action View предоставляет множество вспомогательных методов - хелперов для динамического создания HTML-тегов для форм, дат и строк. Также можно добавлять в ваше приложение собственные хелперы по мере необходимости.

Action View может использовать функции Active Model, такие как to_param и to_partial_path для упрощения кода. Это не означает, что Action View зависит от Active Model. Action View является независимым пакетом, который можно использовать с любой библиотекой Ruby.

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

Шаблоны Action View (также называемые "вью") хранятся во вложенных папках внутри директории app/views. Там есть вложенная папка, соответствующая имени каждого контроллера. Файлы вью внутри этой папки используются для отрисовки конкретных вью в ответ на экшны контроллера.

Например, при использовании генератора скаффолда для создания ресурса article, Rails создает следующие файлы в app/views/articles:

$ 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.html.erb, edit.html.erb и т.д.

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

Итоговый HTML, возвращаемый клиенту, формируется из комбинации файла ERB .html.erb, обертывающего его шаблона макета и всех партиалов, на которые может ссылаться файл ERB. В оставшейся части этого руководства вы найдете более подробную информацию о каждом из трех компонентов: шаблонов (Templates), партиалов (Partials) и макетов (Layouts).

Шаблоны

Шаблоны Action View могут быть написаны в разных форматах. Если файл шаблона имеет расширение .erb, он использует встроенный Ruby для создания HTML-ответа. Если шаблон имеет расширение .jbuilder, он использует гем Jbuilder для создания JSON-ответа. А шаблон с расширением .builder использует библиотеку Builder::XmlMarkup для создания XML-ответа.

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

ERB

ERB шаблон - это способ внедрить код Ruby внутрь статического HTML с помощью специальных тегов ERB, таких как <% %> и <%= %>.

При обработке Rails вью в формате ERB, заканчивающихся на .html.erb, он выполняет встроенный код Ruby и заменяет теги ERB на динамический вывод. Это динамическое содержимое объединяется со статической разметкой HTML для формирования окончательного HTML-ответа.

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

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

Цикл настроен с помощью обычных встраиваемых тегов (<% %>), а имя вставлено с помощью выводящих встраиваемых тегов (<%= %>).

Имейте в виду, что функции вроде print и puts не будут отображаться во вью при использовании ERB-шаблонов. Таким образом, следующий код не будет работать:

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

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

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

Jbuilder

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

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

gem "jbuilder"

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

Вот базовый пример:

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

что произведет:

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

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

Builder

Шаблоны Builder — это более программная альтернатива ERB. Они похожи на JBuilder, но используются для создания XML, а не JSON.

Объект 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>

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

Компиляция шаблонов

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

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

Партиалы

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

Давайте разберемся на практике с помощью примеров:

Рендеринг партиалов

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

<%= render "product" %>

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

<%= render "application/product" %>

Этот код найдет и отрисует файл партиала _product.html.erb в app/views/application/.

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

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

<%= render "application/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 "application/footer" %>

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

В приведенном выше примере также используется партиал _product.html.erb. Этот партиал содержит детали для отрисовки отдельного продукта и используется для отрисовки каждого продукта из коллекции @products.

Передача данных в партиалы с помощью опции locals

При отрисовке партиала вы можете передавать данные из основной вью в партиал. Для этого используется хэш опций locals:. Каждый ключ в опции locals: доступен как локальная переменная партиала:

<%# app/views/products/show.html.erb %>

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

<%# app/views/products/_product.html.erb %>

<%= tag.div id: dom_id(my_product) do %>
  <h1><%= my_product.name %></h1>
<% end %>

"Локальная переменная партиала" - это переменная, которая существует только внутри данного партиала и доступна только из него. В приведенном выше примере my_product является локальной переменной партиала. Ей было присвоено значение @product при передаче в партиал из исходной вью.

Имейте в виду, что обычно эту локальную переменную мы просто назвали бы product. В данном примере мы используем my_product, чтобы отличить ее от названия переменной экземпляра и названия шаблона.

Поскольку locals - это хэш, вы можете передавать несколько переменных по мере необходимости, например locals: { my_product: @product, my_reviews: @reviews }.

Однако, если шаблон ссылается на переменную, которая не передана в представление как часть опции locals:, шаблон выдаст ошибку ActionView::Template::Error:

<%# app/views/products/_product.html.erb %>

<%= tag.div id: dom_id(my_product) do %>
  <h1><%= my_product.name %></h1>

  <%# => raises ActionView::Template::Error for `product_reviews` %>
  <% product_reviews.each do |review| %>
    <%# ... %>
  <% end %>
<% end %>

Использование local_assigns

Каждый партиал имеет метод под названием local_assigns. Вы можете использовать этот метод для доступа к ключам, переданным через опцию locals:. Если партиал был отрисован без установленного :some_key, значение local_assigns[:some_key] внутри партиала будет равно nil.

Например, product_reviews будет иметь значение nil в приведенном ниже примере, поскольку в locals: установлено только значение product:

<%# app/views/products/show.html.erb %>

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

<%# app/views/products/_product.html.erb %>

<% local_assigns[:product]          # => "#<Product:0x0000000109ec5d10>" %>
<% local_assigns[:product_reviews]  # => nil %>

Одним из вариантов использования local_assigns является необязательная передача локальной переменной, а затем условное выполнение действия в партиале в зависимости от того, установлена ли локальная переменная. Например:

<% if local_assigns[:redirect] %>
  <%= form.hidden_field :redirect, value: true %>
<% end %>

Еще один пример из _blob.html.erb Active Storage. В данном случае размер устанавливается в зависимости от того, установлена ли локальная переменная in_gallery при отрисовке партиала, который содержит эту строку:

<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>

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

В предыдущих примерах render принимает 2 опции: partial и locals. Однако, если вам нужны только эти опции, вы можете пропустить ключи partial и locals и указать только значения.

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

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

Можно написать:

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

Вы также можете использовать эту краткую запись, основанную на соглашениях:

<%= render @product %>

Этот код будет искать партиал с названием _product.html.erb в app/views/products/. Дополнительно, он передаст локальную переменную product со значением @product.

Опции as и object

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

<%= render @product %>

внутри партиала _product.html.erb вы получите переменную экземпляра @product в локальной переменной product, как если бы вы написали:

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

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

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

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

можно написать:

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

Эта строка присваивает переменную экземпляра @item локальной переменной партиала с именем product. А что, если вы хотите изменить имя локальной переменной с product по умолчанию на что-то другое? Для этого можно использовать опцию :as.

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

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

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

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

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

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

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

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

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

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

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

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

<%= render @products %>

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

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

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

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

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

Переменные счетчика

Rails также предоставляет внутри партиала, вызываемого из коллекции, счетчик. Эта переменная названа по имени партиала с добавлением суффикса _counter. Например, при отрисовке коллекции @products партиал _product.html.erb может получить доступ к переменной product_counter. Эта переменная указывает на количество раз, которое партиал был отрисован внутри ограничивающей его вью, начиная с значения 0 при первой отрисовке.

<%# index.html.erb %>
<%= render partial: "product", collection: @products %>
<%# _product.html.erb %>
<%= product_counter %> # 0 для первого продукта, 1 для второго продукта...

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

Примечание: Следующие два раздела, Строгие локальные переменные и local_assigns с соответствием образцу, посвящены более продвинутым функциям использования партиалов и включены сюда для полноты картины.

local_assigns с соответствием образцу

Поскольку local_assigns является Hash, он совместим с оператором присваивания с помощью соответствия образцу в Ruby 3.1:

local_assigns => { product:, **options }
product # => "#<Product:0x0000000109ec5d10>"
options # => {}

Когда ключи, кроме :product, присваиваются локальной хэш-переменной для партиала, их можно включить в вызов вспомогательных методов:

<%# app/views/products/_product.html.erb %>

<% local_assigns => { product:, **options } %>

<%= tag.div id: dom_id(product), **options do %>
  <h1><%= product.name %></h1>
<% end %>

<%# app/views/products/show.html.erb %>

<%= render "products/product", product: @product, class: "card" %>
<%# => <div id="product_1" class="card">
  #      <h1>A widget</h1>
  #    </div>
%>

Присваивание с помощью соответствия образцу также поддерживает переименование переменных:

local_assigns => { product: record }
product             # => "#<Product:0x0000000109ec5d10>"
record              # => "#<Product:0x0000000109ec5d10>"
product == record   # => true

Вы также можете условно читать переменную, а затем переходить к значению по умолчанию, если ключ не входит в опцию locals:, используя метод fetch:

<%# app/views/products/_product.html.erb %>

<% local_assigns.fetch(:related_products, []).each do |related_product| %>
  <%# ... %>
<% end %>

Совмещение оператора присваивания с помощью соответствия образцу в Ruby 3.1 с вызовами Hash#with_defaults позволяет компактно назначать значения по умолчанию локальным переменным в партиале.

<%# app/views/products/_product.html.erb %>

<% local_assigns.with_defaults(related_products: []) => { product:, related_products: } %>

<%= tag.div id: dom_id(product) do %>
  <h1><%= product.name %></h1>

  <% related_products.each do |related_product| %>
    <%# ... %>
  <% end %>
<% end %>

По умолчанию партиалы принимают любые locals в качестве именованных аргументов. Чтобы определить, какие locals принимает партиал, используйте специальный комментарий `locals:. Подробнее об этом можно узнать в разделе Строгие локальные переменные.

Строгие локальные переменные

Партиалы Action View принимают любое количество locals в качестве именованных аргументов. С помощью специального комментария locals: вы можете контролировать, сколько и какие locals принимает шаблон, устанавливать значения по умолчанию и многое другое.

Ниже приведены примеры использования специального комментария locals:.

<%# app/views/messages/_message.html.erb %>

<%# locals: (message:) -%>
<%= message %>

Вышеуказанный код делает message обязательной локальной переменной. Рендеринг партиала без аргумента локальной переменной :message приведет к ошибке:

render "messages/message"
# => ActionView::Template::Error: missing local: :message for app/views/messages/_message.html.erb

Если установлено значение по умолчанию, оно может быть использовано, если message не передается в locals:.

<%# app/views/messages/_message.html.erb %>

<%# locals: (message: "Hello, world!") -%>
<%= message %>

Рендеринг партиала без локальной переменной :message приведет к использованию значения по умолчанию, установленного в специальном комментарии locals:.

render "messages/message"
# => "Hello, world!"

Рендеринг партиала с локальными переменными, не указанными в специальном комментарии local:, также приведет к ошибке:

render "messages/message", unknown_local: "will raise"
# => ActionView::Template::Error: unknown local: :unknown_local for app/views/messages/_message.html.erb

Вы можете разрешить необязательные аргументы локальных переменных с помощью оператора двойного разбиения **:

<%# app/views/messages/_message.html.erb %>

<%# locals: (message: "Hello, world!", **attributes) -%>
<%= tag.p(message, **attributes) %>

Либо вы можете полностью отключить locals, установив locals: в пустые скобки ().

<%# app/views/messages/_message.html.erb %>

<%# locals: () %>

Рендеринг партиала с любыми аргументами локальных переменных приведет к ошибке:

render "messages/message", unknown_local: "will raise"
# => ActionView::Template::Error: no locals accepted for app/views/messages/_message.html.erb

Action View обработает специальный комментарий locals: во всех шаблонизаторах, которые поддерживают комментарии, начинающиеся с символа #, и прочитает его на любой строке внутри партиала.

CAUTION: Поддерживаются только именованные аргументы. Определение позиционных или блочных аргументов вызовет ошибку Action View во время отрисовки.

Макеты

Макеты используются для рендеринга общего шаблона вью поверх результатов экшнов контроллеров Rails. В Rails-приложении может быть несколько макетов, в которых могут быть отрисованы страницы.

Например, в приложении может быть один макет для зарегистрированного пользователя и другой для маркетинговой части сайта. Макет для зарегистрированного пользователя может включать верхнюю панель навигации, которая должна присутствовать во многих экшнах контроллера. Макет продаж для SaaS-приложения может включать верхнюю панель навигации для таких страниц, как "Цены" и "Контакты". Различные макеты могут иметь разное содержимое шапки и колонтитула.

Чтобы найти макет для текущего экшна контроллера, Rails сначала ищет файл в app/views/layouts с тем же базовым именем, что и у контроллера. Например, для рендеринга экшнов из класса ProductsController будет использоваться файл app/views/layouts/products.html.erb.

Если макет, специфичный для контроллера, не существует, Rails использует app/views/layouts/application.html.erb.

Вот пример простого макета в файле application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <title><%= "Your Rails App" %></title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  <%= javascript_importmap_tags %>
</head>
<body>

<nav>
  <ul>
    <li><%= link_to "Home", root_path %></li>
    <li><%= link_to "Products", products_path %></li>
    <!-- Additional navigation links here -->
  </ul>
</nav>

<%= yield %>

<footer>
  <p>&copy; <%= Date.current.year %> Your Company</p>
</footer>

В примере макета выше, содержимое вью будет отрисовано на месте <%= yield %> и окружено тем же содержимым <head>, <nav> и <footer>.

Rails предоставляет дополнительные способы назначения определенных макетов отдельным контроллерам и экшнам. Подробнее о макетах в целом вы можете узнать в руководстве Макеты и рендеринг в Rails.

Макеты для партиалов

К партиалам можно применять собственные макеты. Они отличаются от тех, которые применяются к экшнам контроллера, но работают схожим образом.

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

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

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

<%# app/views/articles/show.html.erb %>
<%= render partial: 'article', layout: 'box', locals: { article: @article } %>

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

<%# app/views/articles/_box.html.erb %>
<div class="box">
  <%= yield %>
</div>

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

В отличие от макетов приложения, макеты для партиалов по-прежнему имеют префикс подчеркивания в своем названии.

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

<%# app/views/articles/show.html.erb %>
<%= render(layout: 'box', locals: { article: @article }) do %>
  <div>
    <p><%= article.body %></p>
  </div>
<% end %>

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

Коллекции с макетами для партиалов

При отрисовке коллекций также можно использовать опцию :layout.

<%= render partial: "article", collection: @articles, layout: "special_layout" %>

Макет будет отрисован вместе с партиалом для каждого элемента коллекции. Переменные текущего объекта и счетчика объектов, article и article_counter в приведенном выше примере, также будут доступны в макете, так же как и внутри партиала.

Хелперы

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

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

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

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

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

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

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

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

On this page