Это руководство раскрывает основные возможности макетов Action Controller и Action View.
После прочтения этого руководства, вы узнаете:
Это руководство сосредотачивается на взаимодействии между контроллером и вью (представлением) в треугольнике модель-представление-контроллер (MVC). Как вы знаете, контроллер ответственен за управление целым процессом обслуживания запросов в Rails, хотя обычно любой серьезный код переносится в модель. Но когда приходит время послать отклик обратно пользователю, контроллер передает все вью. Именно этой передаче посвящено данное руководство.
В общих чертах все связано с решением, что же должно быть послано как отклик, и вызовом подходящего метода для создания этого отклика. Если откликом является полноценная вью, Rails также проводит дополнительную работу по упаковыванию вью в макет и, возможно, по вставке частичных вью. В общем, все эти этапы вы увидите сами в следующих разделах.
С точки зрения контроллера есть три способа создать отклик HTTP:
render
для создания полного отклика, возвращаемого браузеру
redirect_to
для передачи браузеру кода переадресации HTTP
head
для создания отклика, включающего только заголовки HTTP, возвращаемого браузеру
Вы уже слышали, что Rails содействует принципу "соглашения по конфигурации". Рендеринг по умолчанию - прекрасный пример этого. По умолчанию контроллеры в Rails автоматически рендерят вью с именами, соответствующими валидным маршрутам. Например, если есть такой код в вашем классе BooksController
:
class BooksController < ApplicationController
end
И следующее в файле маршрутов:
resources :books
И у вас имеется файл вью app/views/books/index.html.erb
:
<h1>Books are coming soon!</h1>
Rails автоматически отрендерит app/views/books/index.html.erb
при переходе на адрес /books
, и вы увидите на экране надпись "Books are coming soon!"
Однако это сообщение минимально полезно, поэтому вскоре вы создадите модель Book
и добавите экшн index в BooksController
:
class BooksController < ApplicationController
def index
@books = Book.all
end
end
Снова отметьте, что у нас соглашения превыше конфигурации в том, что отсутствует избыточный рендер в конце этого экшна index. Правило в том, что не нужно что-то избыточно рендерить в конце экшна контроллера, rails будет искать шаблон action_name.html.erb
по пути вью контроллера и отрендерит его, поэтому в нашем случае Rails отрендерит файл app/views/books/index.html.erb
.
Итак, в нашей вью мы хотим отобразить свойства всех книг, это делается с помощью шаблона ERB, подобного следующему:
<h1>Listing Books</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @books.each do |book| %>
<tr>
<td><%= book.title %></td>
<td><%= book.content %></td>
<td><%= link_to "Show", book %></td>
<td><%= link_to "Edit", edit_book_path(book) %></td>
<td><%= link_to "Destroy", book, data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to "New book", new_book_path %>
Фактически рендеринг осуществляется вложенными классами модуля ActionView::Template::Handlers
. Мы не будем углубляться в этот процесс, но важно знать, что расширение файла вью контролирует выбор обработчика шаблона.
render
Во многих случаях метод ActionController::Renderer#render
выполняет большую работу по рендерингу содержимого Вашего приложения для использования в браузере. Имеются различные способы настройки возможностей render
. Вы можете рендерить вью по умолчанию для шаблона Rails, или определенный шаблон, или файл, или встроенный код, или совсем ничего. Можно рендерить текст, JSON или XML. Также можно определить тип содержимого или статус HTTP отрендеренного отклика.
Если хотите увидеть точные результаты вызова render
без необходимости проверять это в браузере, можете вызвать render_to_string
. Этот метод принимает те же самые опции, что и render
, но возвращает строку вместо отправки отклика обратно браузеру.
Если хотите отрендерить вью, соответствующую другому шаблону этого же контроллера, можно использовать render
с именем вью:
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to(@book)
else
render "edit"
end
end
Если вызов update
проваливается, вызов экшна update
в этом контроллере отрендерит шаблон edit.html.erb
, принадлежащий тому же контроллеру.
Если хотите, можете использовать символ вместо строки для определения экшна для рендеринга:
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to(@book)
else
render :edit, status: :unprocessable_entity
end
end
Что, если вы хотите отрендерить шаблон из абсолютно другого контроллера? Это можно также сделать с render
, который принимает полный путь шаблона для рендеринга (относительно app/views
). Например, если запускаем код в AdminProductsController
который находится в app/controllers/admin
, можете отрендерить результат экшна в шаблон в app/views/products
следующим образом:
render "products/show"
Rails знает, что эта вью принадлежит другому контроллеру, поскольку содержит символ слэша в строке. Если хотите быть точными, можете использовать опцию :template
(которая требовалась в Rails 2.2 и более ранних):
render template: "products/show"
Вышеописанные два метода рендеринга (рендеринг шаблона другого экшна в контроллере и рендеринг шаблона другого экшна в другом контроллере) на самом деле являются вариантами одной и той же операции.
Фактически в классе BooksController
, в экшне update, в котором мы хотим отрендерить шаблон edit, если книга не была успешно обновлена, все нижеследующие вызовы отрендерят шаблон edit.html.erb
в директории views/books
:
render :edit
render action: :edit
render "edit"
render action: "edit"
render "books/edit"
render template: "books/edit"
Какой из них вы будете использовать - это вопрос стиля и соглашений, но практическое правило заключается в использовании простейшего, который больше подходит по стилю написания вашего кода.
render
с :inline
Метод render
вполне может обойтись без вью, если вы используете опцию :inline
для поддержки ERB, как части вызова метода. Это вполне валидно:
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
Должно быть серьезное основание для использования этой опции. Вкрапление ERB в контроллер нарушает MVC ориентированность Rails и создает трудности для других разработчиков в следовании логике вашего проекта. Вместо этого используйте отдельную erb-вью.
По умолчанию встроенный рендеринг использует ERB. Можете принудить использовать вместо этого Builder с помощью опции :type
:
render inline: "xml.p {'Horrid coding practice!'}", type: :builder
Вы можете послать простой текст - совсем без разметки - обратно браузеру с использованием опции :plain
в render
:
render plain: "OK"
Рендеринг чистого текста наиболее полезен, когда вы делаете Ajax-отклик или отвечаете на запросы веб-сервиса, ожидающего что-то иное, чем HTML.
По умолчанию при использовании опции :plain
текст рендерится без использования текущего макета. Если хотите, чтобы Rails вложил текст в текущий макет, необходимо добавить опцию layout: true
и использовать расширение .text.erb
для файла макета.
Вы можете вернуть HTML, используя опцию :html
метода render
:
render html: helpers.tag.strong('Not Found')
Это полезно когда вы хотите отрендерить небольшой кусочек HTML-кода. Однако, если у вас достаточно сложная разметка, стоит рассмотреть выделение её в файл шаблона.
Когда используется опция html:
, HTML объекты будут экранироваться, если строка не состоит из API, поддерживающих html_safe
.
JSON - это формат данных JavaScript, используемый многими библиотеками Ajax. Rails имеет встроенную поддержку для преобразования объектов в JSON и рендеринга этого JSON обратно браузеру:
render json: @product
Не нужно вызывать to_json
в объекте, который хотите рендерить. Если используется опция :json
, render
автоматически вызовет to_json
за вас.
Rails также имеет встроенную поддержку для преобразования объектов в XML и рендеринга этого XML обратно вызывающему:
render xml: @product
Не нужно вызывать to_xml
в объекте, который хотите рендерить. Если используется опция :xml
, render
автоматически вызовет to_xml
за вас.
Rails может рендерить чистый JavaScript:
render js: "alert('Hello Rails');"
Это пошлет указанную строку в браузер с типом MIME text/javascript
.
Вы можете вернуть необработанный текст, без установки типа содержимого,
используя опцию :body
, метода render
:
render body: "raw"
Эта опция должна использоваться, только если не важен тип содержимого отклика.
Использование :plain
или :html
уместнее в большинстве случаев.
Возвращенным откликом от этой опции будет text/plain
(если не будет переопределен),
так как это тип содержимого по умолчанию у отклика Action Dispatch.
Rails может рендерить необработанный файл по абсолютному пути. Это полезно для условного рендеринга статичных файлов, таких как страницы ошибок.
render file: "#{Rails.root}/public/404.html", layout: false
Это отрендерит необработанный файл (ERB или другие обработчики не поддерживаются). По умолчанию файл рендерится с использованием текущего макета.
Использование опции :file
в комбинации с данными, введенными пользователем, может привести к проблемам безопасности, так как злоумышленник может использовать этот экшн для доступа к чувствительным, с точки зрения безопасности, файлам вашей файловой системы.
send_file
часто является более быстрым и лучшим вариантом, если макет не требуется.
Rails может рендерить объекты, отвечающие на :render_in
.
render MyRenderable.new
Это вызывает render_in
на представленном объекте в контексте текущей вью.
render
Вызов метода render
как правило принимает шесть опций:
:content_type
:layout
:location
:status
:formats
:variants
:content_type
По умолчанию Rails будет обрабатывать результаты операции рендеринга с типом содержимого MIME text/html
(или application/json
, если используется опция :json
, или application/xml
для опции :xml
). Иногда бывает так, что нужно изменить это, и тогда необходимо настроить опцию :content_type
:
render template: "feed", content_type: "application/rss"
:layout
С большинством опций для render
, отрендеренное содержимое отображается как часть текущего макета. Вы узнаете более подробно о макетах, и как их использовать, позже в этом руководстве.
Опция :layout
нужна, чтобы сообщить Rails о необходимости использовать определенный файл как макет для текущего экшна:
render layout: "special_layout"
Также можно сообщить Rails, что требуется рендерить вообще без макета:
render layout: false
:location
Опцию :location
можно использовать, чтобы установить заголовок HTTP Location
:
render xml: photo, location: photo_url(photo)
:status
Rails автоматически сгенерирует отклик с правильным кодом статуса HTML (в большинстве случаев равный 200 OK
). Опцию :status
можно использовать, чтобы изменить это:
render status: 500
render status: :forbidden
Rails понимает как числовые коды статуса, так и соответствующие символы, показанные ниже.
Класс отклика | Код статуса HTTP | Символ |
---|---|---|
Informational | 100 | :continue |
101 | :switching_protocols | |
102 | :processing | |
Success | 200 | :ok |
201 | :created | |
202 | :accepted | |
203 | :non_authoritative_information | |
204 | :no_content | |
205 | :reset_content | |
206 | :partial_content | |
207 | :multi_status | |
208 | :already_reported | |
226 | :im_used | |
Redirection | 300 | :multiple_choices |
301 | :moved_permanently | |
302 | :found | |
303 | :see_other | |
304 | :not_modified | |
305 | :use_proxy | |
307 | :temporary_redirect | |
308 | :permanent_redirect | |
Client Error | 400 | :bad_request |
401 | :unauthorized | |
402 | :payment_required | |
403 | :forbidden | |
404 | :not_found | |
405 | :method_not_allowed | |
406 | :not_acceptable | |
407 | :proxy_authentication_required | |
408 | :request_timeout | |
409 | :conflict | |
410 | :gone | |
411 | :length_required | |
412 | :precondition_failed | |
413 | :payload_too_large | |
414 | :uri_too_long | |
415 | :unsupported_media_type | |
416 | :range_not_satisfiable | |
417 | :expectation_failed | |
421 | :misdirected_request | |
422 | :unprocessable_entity | |
423 | :locked | |
424 | :failed_dependency | |
426 | :upgrade_required | |
428 | :precondition_required | |
429 | :too_many_requests | |
431 | :request_header_fields_too_large | |
451 | :unavailable_for_legal_reasons | |
Server Error | 500 | :internal_server_error |
501 | :not_implemented | |
502 | :bad_gateway | |
503 | :service_unavailable | |
504 | :gateway_timeout | |
505 | :http_version_not_supported | |
506 | :variant_also_negotiates | |
507 | :insufficient_storage | |
508 | :loop_detected | |
510 | :not_extended | |
511 | :network_authentication_required |
Если попытаться отрендерить содержимое наряду с кодом статуса без содержимого (100-199, 204, 205 или 304), он будет исключён из отклика.
:formats
Rails использует формат, определённый в запросе (или :html
по умолчанию). Вы можете изменить его, передав в опцию :formats
символ или массив:
render formats: :xml
render formats: [:json, :xml]
Если шаблон с указанным форматом не существует, вызывается ошибка ActionView::MissingTemplate
.
:variants
Она сообщает Rails искать варианты шаблона того же формата. Можно указать список вариантов, передав опции :variants
символ или массив.
Пример использования.
# called in HomeController#index
render variants: [:mobile, :desktop]
С таком набором вариантов, Rails будет искать следующий набор шаблонов и использовать первый из существующих.
app/views/home/index.html+mobile.erb
app/views/home/index.html+desktop.erb
app/views/home/index.html.erb
Если шаблон с указанным форматом не существует, будет вызвана ошибка ActionView::MissingTemplate
.
Вместо указания варианта на вызове render, его также можно установить на объекте request в экшне контроллера.
def index
request.variant = determine_variant
end
private
def determine_variant
variant = nil
# некоторый код для определения варианта(ов) для использования
variant = :mobile if session[:use_mobile]
variant
end
Чтобы найти текущий макет, Rails сначала смотрит файл в app/views/layouts
с именем, таким же, как имя контроллера. Например, рендеринг экшнов из класса PhotosController
будет использовать /app/views/layouts/photos.html.erb
(или app/views/layouts/photos.builder
). Если такого макета нет, Rails будет использовать /app/views/layouts/application.html.erb
или /app/views/layouts/application.builder
. Если макет .erb
отсутствует, Rails будет использовать макет .builder
, если таковой имеется. Rails также предоставляет несколько способов для более точного назначения определенных макетов отдельным контроллерам и экшнам.
Вы можете переопределить дефолтные соглашения по макетам в контроллере, используя объявление layout
. Например:
class ProductsController < ApplicationController
layout "inventory"
#...
end
С этим объявлением все вью, отрендеренные ProductsController
, будут использовать app/views/layouts/inventory.html.erb
как макет.
Чтобы привязать определенный макет к приложению в целом, используйте объявление layout
в классе ApplicationController
:
class ApplicationController < ActionController::Base
layout "main"
#...
end
С этим объявлением каждая из вью во всем приложении будет использовать app/views/layouts/main.html.erb
как макет.
Можно использовать символ для отсрочки выбора макета до тех пор, пока не будет обработан запрос:
class ProductsController < ApplicationController
layout :products_layout
def show
@product = Product.find(params[:id])
end
private
def products_layout
@current_user.special? ? "special" : "products"
end
end
Теперь, если текущий пользователь является специальным, он получит специальный макет при просмотре продукта.
Можно даже использовать встроенный метод, такой как Proc, для определения макета. Например, если передать объект Proc, то блоку, которому вы передаете Proc, будет предоставлен экземпляр controller
, поэтому макет может быть определен, основываясь на текущем запросе:
class ProductsController < ApplicationController
layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
Макеты, определенные на уровне контроллера, поддерживают опции :only
и :except
. Эти опции принимают либо имя метода, либо массив имен методов, соответствующих именам методов в контроллере:
class ProductsController < ApplicationController
layout "product", except: [:index, :rss]
end
С таким объявлением макет product
будет использован везде, кроме методов rss
и index
.
Объявление макета ниже по иерархии и более специфическое объявление макета всегда переопределяет более общие. Например:
application_controller.rb
class ApplicationController < ActionController::Base
layout "main"
end
articles_controller.rb
class ArticlesController < ApplicationController
end
special_articles_controller.rb
class SpecialArticlesController < PostsController
layout "special"
end
old_articles_controller.rb
class OldArticlesController < SpecialPostsController
layout false
def show
@article = Article.find(params[:id])
end
def index
@old_articles = Article.older
render layout: "old"
end
# ...
end
В этом приложении:
main
ArticlesController#index
будет использовать макет main
SpecialArticlesController#index
будет использовать макет special
OldArticlesController#show
не будет использовать макет совсем
OldArticlesController#index
будет использовать макет old
Следуя логике наследования макета, если шаблон или партиал не найдены по обычному пути, контроллер будет искать шаблон или партиал для рендеринга по цепочке наследования. Например:
# app/controllers/application_controller
class ApplicationController < ActionController::Base
end
# app/controllers/admin_controller
class AdminController < ApplicationController
end
# app/controllers/admin/products_controller
class Admin::ProductsController < AdminController
def index
end
end
Порядок поиска экшна admin/products#index
будет такой:
app/views/admin/products/
app/views/admin/
app/views/application/
Это делает app/views/application/
хорошим местом для общих партиалов, которые затем могут быть отрендерены в ERB следующим образом:
<%# app/views/admin/products/index.html.erb %>
<%= render @products || "empty_list" %>
<%# app/views/application/_empty_list.html.erb %>
There are no items in this list <em>yet</em>.
Рано или поздно, большинство разработчиков на Rails увидят сообщение об ошибке "Can only render or redirect once per action". Хоть такое и раздражает, это относительно просто правится. Обычно такое происходит в связи с фундаментальным непониманием метода работы render
.
Например, вот некоторый код, который вызовет эту ошибку:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
render action: "regular_show"
end
Если @book.special?
вычисляется как true
, Rails начинает процесс рендеринга, выгружая переменную @book
во вью special_show
. Но это не остановит от выполнения остальной код в экшне show
, и когда Rails достигнет конца экшна, он начнет рендерить вью show
- и выдаст ошибку. Решение простое: убедитесь, что у вас есть только один вызов render
или redirect
за один проход. Еще может помочь такая вещь, как return
. Вот исправленная версия метода:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
return
end
render action: "regular_show"
end
Отметьте, что неявный рендер, выполняемый ActionController, определяет, был ли вызван render
поэтому следующий код будет работать без проблем:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
end
Это отрендерит книгу (book) с special?
, заданным с помощью шаблона special_show
, в то время как остальные книги будут рендериться с дефолтным шаблоном show
.
redirect_to
Другой способ управлять возвратом отклика на HTTP-запрос - с помощью redirect_to
. Как вы видели, render
сообщает Rails, какую вью (или иной ассет) использовать при построении отклика. Метод redirect_to
делает нечто совершенно отличное: он говорит браузеру послать новый запрос по другому URL. Например, можно перенаправить из любого места, где сейчас выполняется код, к экшну index фотографий вашего приложения с помощью этого вызова:
redirect_to photos_url
Можно использовать redirect_back
, чтобы вернуть пользователя на страницу с которой он только что пришел. Это место расположения вытаскивается из заголовка HTTP_REFERER
, который не обязательно будет установлен браузером, поэтому нужно предоставить fallback_location
для использования в таком случае.
redirect_back(fallback_location: root_path)
redirect_to
и redirect_back
не прерывают и не возвращают из выполняемого метода немедленно, а просто устанавливают отклики HTTP. Выражения, следующие после них в методе, будут выполнены. При необходимости можно прервать явным return
или любым другим механизмом прерывания.
Rails использует код статуса HTTP 302, временное перенаправление, при вызове redirect_to
. Если хотите использовать иной код статуса, возможно 301, постоянное перенаправление, можете использовать опцию :status
:
redirect_to photos_path, status: 301
Подобно опции :status
для render
, :status
для redirect_to
принимает и числовые, и символьные обозначения заголовка.
render
и redirect_to
Иногда неопытные разработчики думают о redirect_to
как о разновидности команды goto
, перемещающую выполнение из одного места в другое в вашем коде Rails. Это не верно. Ваш код останавливается и ждет нового запроса от браузера. Просто получается так, что вы говорите браузеру, какой запрос он должен сделать следующим, возвращая код статуса HTTP 302.
Рассмотрим эти экшны, чтобы увидеть разницу:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
render action: "index"
end
end
С кодом в такой форме, вероятно, будет проблема, если переменная @book
равна nil
. Помните, render :action
не запускает какой-либо код в указанном экшне, и таким образом ничего не будет присвоено переменной @books
, которую, возможно, потребует вью index
. Один из способов исправить это - использовать перенаправление вместо рендеринга:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
redirect_to action: :index
end
end
С помощью этого кода браузер сделает новый запрос для индексной страницы, код в методе index
запустится, и все будет хорошо.
Единственный недостаток этого кода в том, что он требует круговорот через браузер: браузер запрашивает экшн show с помощью /books/1
, и контроллер обнаруживает, что книг нет, поэтому отсылает отклик-перенаправление 301 браузеру, сообщающий перейти на /books/
, браузер выполняет и посылает новый запрос контроллеру, теперь запрашивая экшн index
, затем контроллер получает все книги в базе данных и рендерит шаблон index, отсылает его обратно браузеру, который затем показывает его на экране.
Пока это небольшое приложение, такая добавленная задержка не может быть проблемой, но иногда стоит подумать о том, является ли время отклика проблемой. Можем продемонстрировать один из способов управления этим с помощью хитрого примера:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
@books = Book.all
flash.now[:alert] = "Your book was not found"
render "index"
end
end
Это обнаружит, что нет книг с определенным ID, заполнит переменную экземпляра @books
всеми книгами в модели, и затем напрямую отрендерит шаблон index.html.erb
, возвратив его браузеру с предупреждающим сообщением в flash, сообщающим пользователю, что произошло.
head
для создания отклика, содержащего только заголовокМетод head
может использоваться для отправки браузеру откликов, содержащих только заголовки. Метод head
принимает число или символ (смотрите таблицу соответствия), представляющие код статуса HTTP. Аргумент опций интерпретируется как хэш заголовков имен и значений. Например, можно возвратить только заголовок ошибки:
head :bad_request
Это создаст следующий заголовок:
HTTP/1.1 400 Bad Request
Connection: close
Date: Sun, 24 Jan 2010 12:15:53 GMT
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
X-Runtime: 0.013483
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache
Или можете использовать другие заголовки HTTP для передачи другой информации:
head :created, location: photo_path(@photo)
Что создаст:
HTTP/1.1 201 Created
Connection: close
Date: Sun, 24 Jan 2010 12:16:44 GMT
Transfer-Encoding: chunked
Location: /photos/1
Content-Type: text/html; charset=utf-8
X-Runtime: 0.083496
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache
Когда Rails рендерит вью как отклик, он делает это путем объединения вью с текущим макетом, используя правила для нахождения текущего макета, которые были рассмотрены ранее. В макетах у вас есть доступ к трём инструментам для объединения различных кусочков результата для формирования общего отклика:
yield
и content_for
Хелперы ассетных тегов предоставляют методы для генерации HTML, связывающие вью с лентами новостей, JavaScript, таблицами стилей, изображениями, видео и аудио. В Rails доступно шесть хелперов ассетных тегов:
Эти теги можно использовать в макетах или других вью, хотя auto_discovery_link_tag
, javascript_include_tag
и stylesheet_link_tag
как правило используются в разделе <head>
макета.
Хелперы ассетных тегов не проверяют существование ассетов по заданному месту расположения; они просто предполагают, что вы знаете, что делаете, и генерируют ссылку.
auto_discovery_link_tag
Хелпер auto_discovery_link_tag
создает HTML-код, который большинство браузеров и агрегаторов новостей могут использовать для определения наличия каналов RSS, Atom или JSON лент. Он принимает тип ссылки (:rss
, :atom
или :json
), хэш опций, которые передаются через url_for, и хэш опций для тега:
<%= auto_discovery_link_tag(:rss, {action: "feed"},
{title: "RSS Feed"}) %>
Вот три опции тега, доступные для auto_discovery_link_tag
:
:rel
определяет значение rel
в ссылке. Значение по умолчанию "alternate"
:type
определяет явный тип MIME. Rails генерирует подходящий тип MIME автоматически
:title
определяет заголовок ссылки. Значение по умолчанию это значение :type
в верхнем регистре, например, "ATOM" или "RSS".
javascript_include_tag
Хелпер javascript_include_tag
возвращает HTML-тег script
для каждого предоставленного источника.
При использовании Rails с включенным Asset Pipeline, этот хелпер сгенерирует ссылку на /assets/javascripts/
, а не на public/javascripts
, которая использовалась в более ранних версиях Rails. Затем эта ссылка обслуживается конвейером ресурсов (asset pipeline).
Файл JavaScript в приложении Rails или Rails Engine размещается в одном из трех мест расположения: app/assets
, lib/assets
или vendor/assets
. Эти места расположения детально описаны в разделе про организацию ресурсов в руководстве по Asset Pipeline.
Можно определить полный путь относительно корня документа или URL, по желанию. Например, сослаться на файл JavaScript, находящийся в директории с именем javascripts
в одной из app/assets
, lib/assets
или vendor/assets
, можно так:
<%= javascript_include_tag "main" %>
Rails тогда выдаст такой тег script
:
<script src='/assets/main.js'></script>
Затем запрос к этому ассету будет обслужен гемом Sprockets.
Чтобы включить несколько файлов, таких как app/assets/javascripts/main.js
и app/assets/javascripts/columns.js
за один раз:
<%= javascript_include_tag "main", "columns" %>
Чтобы включить app/assets/javascripts/main.js
и app/assets/javascripts/photos/columns.js
:
<%= javascript_include_tag "main", "/photos/columns" %>
Чтобы включить http://example.com/main.js
:
<%= javascript_include_tag "http://example.com/main.js" %>
stylesheet_link_tag
Хелпер stylesheet_link_tag
возвращает HTML-тег <link>
для каждого предоставленного источника.
При использовании Rails с включенным "Asset Pipeline", этот хелпер сгенерирует ссылку на /assets/stylesheets/
. Эта ссылка будет затем обработана гемом Sprockets. Файл таблицы стилей может быть размещен в одном из трех мест расположения: app/assets
, lib/assets
или vendor/assets
.
Можно определить полный путь относительно корня документа или URL. Например, на файл таблицы стилей в директории stylesheets
, размещенной в одной из app/assets
, lib/assets
или vendor/assets
, можно сослаться так:
<%= stylesheet_link_tag "main" %>
Чтобы включить app/assets/stylesheets/main.css
и app/assets/stylesheets/columns.css
:
<%= stylesheet_link_tag "main", "columns" %>
Чтобы включить app/assets/stylesheets/main.css
и app/assets/stylesheets/photos/columns.css
:
<%= stylesheet_link_tag "main", "/photos/columns" %>
Чтобы включить http://example.com/main.css
:
<%= stylesheet_link_tag "http://example.com/main.css" %>
По умолчанию stylesheet_link_tag
создает ссылки с rel="stylesheet"
. Можно переопределить любое из этих дефолтных значений, указав соответствующую опцию (:rel
):
<%= stylesheet_link_tag "main_print", media: "print" %>
image_tag
Хелпер image_tag
создает HTML-тег <img />
для определенного файла. По умолчанию файлы загружаются из public/images
.
Обратите внимание, что нужно указывать расширение изображения.
<%= image_tag "header.png" %>
Вы можете предоставить путь к изображению, если желаете:
<%= image_tag "icons/delete.gif" %>
Вы можете предоставить хэш дополнительных опций HTML:
<%= image_tag "icons/delete.gif", {height: 45} %>
Или альтернативный текст, если пользователь отключил показ изображений в браузере. Если вы не определили явно тег alt, по умолчанию будет указано имя файла с большой буквы и без расширения. Например, эти два тега изображения возвратят одинаковый код:
<%= image_tag "home.gif" %>
<%= image_tag "home.gif", alt: "Home" %>
Можете указать специальный тег size в формате "{width}x{height}":
<%= image_tag "home.gif", size: "50x20" %>
В дополнение к вышеописанным специальным тегам, можно предоставить итоговый хэш стандартных опций HTML, таких как :class
или :id
, или :name
:
<%= image_tag "home.gif", alt: "Go Home",
id: "HomeImage",
class: "nav_bar" %>
video_tag
Хелпер video_tag
создает тег HTML5 <video>
для определенного файла. По умолчанию файлы загружаются из public/videos
.
<%= video_tag "movie.ogg" %>
Создаст
<video src="/videos/movie.ogg" />
Подобно image_tag
, можно предоставить путь или абсолютный, или относительный к директории public/videos
. Дополнительно можно определить опцию size: "#{width}x#{height}"
, как и в image_tag
. Теги видео также могут иметь любые опции HTML, определенные в конце (id
, class
и др.).
Тег видео также поддерживает все HTML-опции <video>
через хэш HTML-опций, включая:
poster: "image_name.png"
, предоставляет изображение, которое будет отображаться вместо видео прежде, чем оно начнет проигрываться.
autoplay: true
, запускает проигрывание видео при загрузке страницы.
loop: true
, запускает видео сначала, как только оно достигает конца.
controls: true
, предоставляет пользователю поддерживаемую браузером панель управления для взаимодействия с видео.
autobuffer: true
, файл видео предварительно загружается для пользователя при загрузке страницы.
Также можно определить несколько видео для проигрывания, передав массив видео в video_tag
:
<%= video_tag ["trailer.ogg", "movie.ogg"] %>
Это создаст:
<video>
<source src="/videos/trailer.ogg" />
<source src="/videos/movie.ogg" />
</video>
audio_tag
Хелпер audio_tag
создает тег HTML5 <audio>
для определенного файла. По умолчанию файлы загружаются из public/audios
.
<%= audio_tag "music.mp3" %>
Если хотите, можете предоставить путь к аудио файлу:
<%= audio_tag "music/first_song.mp3" %>
Также можно предоставить хэш дополнительных опций, таких как :id
, :class
и т.д.
Подобно video_tag
, audio_tag
имеет специальные опции:
autoplay: true
, начинает воспроизведение аудио при загрузке страницы
controls: true
, предоставляет пользователю поддерживаемую браузером панель управления для взаимодействия с аудио.
autobuffer: true
, файл аудио предварительно загружается для пользователя при загрузке страницы.
yield
В контексте макета, yield
определяет раздел, где должно быть вставлено содержимое из вью. Самый простой способ его использования - это иметь один yield
там, куда вставится все содержимое вью, которая в настоящий момент рендерится:
<html>
<head>
</head>
<body>
<%= yield %>
</body>
</html>
Также можете создать макет с несколькими разделами yield:
<html>
<head>
<%= yield :head %>
</head>
<body>
<%= yield %>
</body>
</html>
Основное тело вью всегда рендериться в неименованный yield
. Чтобы рендерить содержимое в именованный yield
, используйте метод content_for
.
content_for
Метод content_for
позволяет вставлять содержимое в именованный блок yield
в макете. Например, эта вью будет работать с макетом, который вы только что видели:
<% content_for :head do %>
<title>A simple page</title>
<% end %>
<p>Hello, Rails!</p>
Результат рендеринга этой страницы в макет будет таким HTML:
<html>
<head>
<title>A simple page</title>
</head>
<body>
<p>Hello, Rails!</p>
</body>
</html>
Метод content_for
может помочь, когда макет содержит отдельные разделы, такие как боковые панели или футеры, в которые нужно вставить свои блоки содержимого. Это также полезно при вставке тегов, загружающих специфичные для страницы файлы JavaScript или CSS в хедер макета в целом.
Частичные шаблоны - также называемые "партиалы" - являются еще одним подходом к разделению процесса рендеринга на более управляемые кусочки. С партиалами можно перемещать код для рендеринга определенных частей отклика в свои отдельные файлы.
Чтобы отрендерить партиал как часть вью, используем метод render
внутри вью:
<%= render "menu" %>
Это отрендерит файл, названный _menu.html.erb
в этом месте в пределах рендерящейся вью. Обратите внимание на начальный символ подчеркивания: файлы партиалов начинаются со знака подчеркивания, чтобы отличать их от обычных вью, несмотря на то, что в вызове они указаны без подчеркивания. Это справедливо даже тогда, когда партиалы вызываются из другой папки:
<%= render "shared/menu" %>
Этот код вытянет партиал из app/views/shared/_menu.html.erb
.
Один из способов применения партиалов это использоваться их как эквивалент подпрограмм: способ переместить часть разметки из вью так, чтобы можно было легче понять, что там происходит. Например, у вас может быть такая вью:
<%= render "shared/ad_banner" %>
<h1>Products</h1>
<p>Here are a few of our fine products:</p>
...
<%= render "shared/footer" %>
Здесь партиалы _ad_banner.html.erb
и _footer.html.erb
могут содержать контент, общий для многих страниц приложения. Нет необходимости видеть код этих разделов, чтобы сконцентрироваться на определенной странице.
Как видно из предыдущих разделов данного руководства, yield
является очень мощным инструментом для очистки ваших макетов. Имейте в виду, что это чистый Ruby, так что можно использовать его практически везде. Например, его можно использовать для соблюдения принципа DRY при определении макета формы для нескольких похожих ресурсов:
users/index.html.erb
<%= render "shared/search_filters", search: @q do |form| %>
<p>
Name contains: <%= form.text_field :name_contains %>
</p>
<% end %>
roles/index.html.erb
<%= render "shared/search_filters", search: @q do |form| %>
<p>
Title contains: <%= form.text_field :title_contains %>
</p>
<% end %>
shared/_search_filters.html.erb
<%= form_for(search) do |form| %>
<h1>Search form:</h1>
<fieldset>
<%= yield form %>
</fieldset>
<p>
<%= form.submit "Search" %>
</p>
<% end %>
Для содержимого, общего для всех страниц приложения, можно использовать партиалы прямо в макетах.
Партиал может использовать свой собственный файл макета, подобно тому, как вью может использовать макет. Например, можете вызвать подобный партиал:
<%= render partial: "link_area", layout: "graybar" %>
Это найдет партиал с именем _link_area.html.erb
и отрендерит его, используя макет _graybar.html.erb
. Отметьте, что макеты для партиалов также начинаются с подчеркивания, как и обычные партиалы, и размещаются в той же папке с партиалами, которым они принадлежат (не в основной папке layouts
).
Также отметьте, что явное указание partial
необходимо, когда передаются дополнительные опции, такие как layout
В партиалы также можно передавать локальные переменные, что делает их более мощными и гибкими. Например, можете использовать такую технику для уменьшения дублирования между страницами new и edit, сохранив немного различающееся содержимое:
new.html.erb
<h1>New zone</h1>
<%= render partial: "form", locals: {zone: @zone} %>
edit.html.erb
<h1>Editing zone</h1>
<%= render partial: "form", locals: {zone: @zone} %>
_form.html.erb
<%= form_for(zone) do |form| %>
<p>
<b>Zone name</b><br>
<%= form.text_field :name %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Хотя тот же самый партиал будет отрендерен в обоих вью, Action View отправит хелпер, который возвратит "Create Zone" для экшна new и "Update Zone" для экшна edit.
Для передачи локальной переменной в партиал только в особых случаях, используйте local_assigns
.
index.html.erb
<%= render user.articles %>
show.html.erb
<%= render article, full: true %>
_article.html.erb
<h2><%= article.title %></h2>
<% if local_assigns[:full] %>
<%= simple_format article.body %>
<% else %>
<%= truncate article.body %>
<% end %>
Таким образом, можно использовать партиал без необходимости объявления всех локальных переменных.
Каждый партиал также имеет локальную переменную с именем, как у партиала (без начального символа подчеркивания). Можете передать объект в эту локальную переменную через опцию :object
:
<%= render partial: "customer", object: @new_customer %>
В партиале customer
переменная customer
будет указывать на @new_customer
из родительской вью.
Если есть экземпляр модели для рендеринга в партиале, можно использовать сокращенный синтаксис:
<%= render @customer %>
Предположим, что переменная экземпляра @customer
содержит экземпляр модели Customer
. Эта переменная будет использовать _customer.html.erb
для рендеринга модели и передаст локальную переменную customer
в партиал, на который будет ссылаться на переменная экземпляра @customer
в родительской вью.
Партиалы часто используют для рендеринга коллекций. Когда коллекция передается в партиал с помощью опции :collection
, партиал будет вставлен один раз для каждого члена коллекции:
index.html.erb
<h1>Products</h1>
<%= render partial: "product", collection: @products %>
_product.html.erb
<p>Product Name: <%= product.name %></p>
Когда партиал вызывается с коллекцией во множественном числе, то каждый отдельный экземпляр партиала имеет доступ к члену коллекции, подлежащей рендерингу, через переменную с именем партиала. В нашем случает партиал _product
, и в партиале _product
можете обращаться к product
для получения экземпляра, который рендерится.
Имеется также сокращенная запись для этого. Предположив, что @products
является коллекцией экземпляров Product
, можно просто написать так в index.html.erb
и получить аналогичный результат:
<h1>Products</h1>
<%= render @products %>
Rails определяет имя партиала, изучая имя модели в коллекции. Фактически, можно даже создать гетерогенную коллекцию и рендерить ее таким образом, и Rails подберет подходящий партиал для каждого члена коллекции:
index.html.erb
<h1>Contacts</h1>
<%= render [customer1, employee1, customer2, employee2] %>
customers/_customer.html.erb
<p>Customer: <%= customer.name %></p>
employees/_employee.html.erb
<p>Employee: <%= employee.name %></p>
В этом случае Rails использует партиалы customer или employee по мере необходимости для каждого члена коллекции.
В случае, если коллекция пустая, render
возвратит nil, поэтому очень просто предоставить альтернативное содержимое.
<h1>Products</h1>
<%= render(@products) || "There are no products available." %>
Чтобы использовать пользовательские имена локальных переменных в партиале, определите опцию :as
в вызове партиала:
<%= render partial: "product", collection: @products, as: :item %>
С этим изменением можете получить доступ к экземпляру коллекции @products
через локальную переменную item
в партиале.
Также можно передавать произвольные локальные переменные в любой партиал, который рендерится с помощью опции locals: {}
:
<%= render partial: "product", collection: @products,
as: :item, locals: {title: "Products Page"} %>
В этом случае, партиал имеет доступ к локальной переменной title
со значением "Products Page".
Rails также создает переменную счетчика, доступную в партиале, вызываемом коллекцией. Переменная именуется заголовком партиала с добавленным _counter
. Например, при рендеринге коллекции @products
партиал _product.html.erb
может получить доступ к переменной product_counter
. Переменная индексирует количество раз, которое партиал был отрендерен во внешнюю вью, начиная со значения 0
при первом рендере.
# index.html.erb
<%= render partial: "product", collection: @products %>
# _product.html.erb
<%= product_counter %> # 0 для первого product, 1 для второго product...
Это также работает, когда имя партиала было изменено с помощью опции as:
. Таким образом, если есть as: :item
, переменная счетчика будет item_counter
.
Также можно определить второй партиал, который будет отрендерен между экземплярами главного партиала, используя опцию :spacer_template
:
<%= render partial: @products, spacer_template: "product_ruler" %>
Rails отрендерит партиал _product_ruler
(без переданных в него данных) между каждой парой партиалов _product
.
При рендеринге коллекций также возможно использовать опцию :layout
:
<%= render partial: "product", collection: @products, layout: "special_layout" %>
Макет будет отрендерен вместе с партиалом для каждого элемента коллекции. Переменные текущего объекта и object_counter также будут доступны в макете, как это происходит в партиале.
Возможно, ваше приложение потребует макет, немного отличающийся от обычного макета приложения, для поддержки одного определенного контроллера. Вместо повторения главного макета и редактирования его, можете выполнить это с помощью вложенных макетов (иногда называемых подшаблонами). Вот пример:
Предположим, имеется макет ApplicationController
:
app/views/layouts/application.html.erb
<html>
<head>
<title><%= @page_title or "Page Title" %></title>
<%= stylesheet_link_tag "layout" %>
<style><%= yield :stylesheets %></style>
</head>
<body>
<div id="top_menu">Top menu items here</div>
<div id="menu">Menu items here</div>
<div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div>
</body>
</html>
На страницах, сгенерированных NewsController
, допустим, нужно спрятать верхнее меню и добавить правое меню:
app/views/layouts/news.html.erb
<% content_for :stylesheets do %>
#top_menu {display: none}
#right_menu {float: right; background-color: yellow; color: black}
<% end %>
<% content_for :content do %>
<div id="right_menu">Right menu items here</div>
<%= content_for?(:news_content) ? yield(:news_content) : yield %>
<% end %>
<%= render template: "layouts/application" %>
Вот и все. Вью News будут использовать новый макет, прячущий верхнее меню и добавляющий новое правое меню в "content" div.
Существует несколько способов получения похожих результатов с различными подшаблонными схемами, используя эту технику. Отметьте, что нет ограничений на уровень вложенности. Можно использовать метод ActionView::render
через render template: 'layouts/news'
, чтобы создать новый макет на основе макета News. Если есть уверенность, что не понадобятся подшаблоны для макета News
, можно заменить строку content_for?(:news_content) ? yield(:news_content) : yield
простым yield
.