После прочтения этого руководства вы узнаете:
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 (также называемые "вью") хранятся во вложенных папках внутри директории 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 шаблон - это способ внедрить код 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
- это гем, поддерживаемый командой 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 — это более программная альтернатива 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 & 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:
во всех шаблонизаторах, которые поддерживают комментарии, начинающиеся с символа #
, и прочитает его на любой строке внутри партиала.
Поддерживаются только именованные аргументы. Определение позиционных или блочных аргументов вызовет ошибку 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>© <%= 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. Эти методы включают:
Подробнее о хелперах можно узнать в руководстве по хелперам 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).