Обзор 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 & 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.erbAction 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>© <%= 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).
Основы Active Model
Это руководство познакомит вас со всем необходимым для начала использования Active Model. Active Model предоставляет способ хелперам Action Pack и Action View взаимодействовать с обычными объектами на
Макеты и рендеринг в Rails
Это руководство раскрывает основные возможности макетов Action Controller и Action View.