Обзор Action View

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

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

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

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

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

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

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

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

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

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

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

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

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

3.1. Шаблоны

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

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

3.1.1. ERB

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

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

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

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

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

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

3.1.2. Builder

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

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

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

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

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

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

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

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

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

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

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

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

3.1.3. Jbuilder

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

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

gem 'jbuilder'

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

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

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

что создаст:

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

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

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

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

3.2. Партиалы

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

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

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

<%= render "menu" %>

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

<%= render "shared/menu" %>

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

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

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

<%= render "shared/ad_banner" %>

<h1>Products</h1>

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

<%= render "shared/footer" %>

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

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

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

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

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

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

3.2.4. Опции as и object

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

<%= render partial: "product" %>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<%= render @products %>

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

3.2.6. Spacer Templates

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

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

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

3.3. Макеты

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

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

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

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

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

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

articles/show.html.erb

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

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

articles/_box.html.erb

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

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

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

articles/show.html.erb

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

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

5. Пути вьюх

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

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

5.1. Prepend view path

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

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

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

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

5.2. Append view path

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

append_view_path "app/views/direct"

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

6. Обзор хелперов, предоставленных Action View

WIP: Тут перечислены не все хелперы. За полным списком можно обратиться к документации API

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

6.1. AssetTagHelper

Этот модуль предоставляет методы для генерации HTML, связывающего вьюхи с ресурсами, такими как картинки, файлы JavaScript, таблицы стилей и новостные ленты.

По умолчанию Rails связывает с ресурсами на текущем хосте в папке public, но можно направить Rails связывать с ресурсами на выделенном сервере ресурсов, установив config.action_controller.asset_host в конфигурации приложения, обычно в config/environments/production.rb. Например, допустим хост ваших ресурсов assets.example.com:

config.action_controller.asset_host = "assets.example.com"
image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />

Возвращает тег ссылки, которую могут использовать браузеры и агрегаторы новостей для автоматического определения RSS или ленты Atom.

auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", { title: "RSS Feed" }) # =>
  <link rel="alternate" type="application/rss+xml" title="RSS Feed" href="http://www.example.com/feed" />

6.1.2. image_path

Вычисляет путь до ресурса картинки в директории app/assets/images. Будут переданы полные пути от корня документа. Используется внутренне image_tag для создания пути к картинке.

image_path("edit.png") # => /assets/edit.png

К имени файла будет добавлена метка, если config.assets.digest установлен true.

image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png

6.1.3. image_url

Вычисляет URL ресурса картинки в директории app/assets/images. Он вызовет image_path и соединит с вашим текущим хостом или хостом ресурсов.

image_url("edit.png") # => http://www.example.com/assets/edit.png

6.1.4. image_tag

Возвращает тег картинки HTML для источника. Источником может быть полный путь или файл, существующий в директории app/assets/images.

image_tag("icon.png") # => <img src="/assets/icon.png" alt="Icon" />

6.1.5. javascript_include_tag

Возвращает тег скрипта HTML для каждого представленного источника. Можно передать имя файла (расширение .js опционально) или файлы JavaScript, существующие в директории app/assets/javascripts для включения на текущую страницу, или передать полный путь относительно корня документа.

javascript_include_tag "common" # => <script src="/assets/common.js"></script>

Если приложение не использует файлопровод, чтобы включить JavaScript библиотеку jQuery, передайте :defaults как источник. При использовании :defaults, если существует application.js в директории app/assets/javascripts, он также будет включен.

javascript_include_tag :defaults

Также можно включить все файлы JavaScript в директории app/assets/javascripts с помощью :all в качестве источника.

javascript_include_tag :all

Также можно кэшировать несколько файлов JavaScript в один файл, что требует меньше соединений HTTP для скачивания и может быть лучше сжато gzip (что сделает передачу быстрее). Кэширование произойдет, только если ActionController::Base.perform_caching установлена true (по умолчанию для окружения production, но не для development).

javascript_include_tag :all, cache: true # =>
  <script src="/javascripts/all.js"></script>

6.1.6. javascript_path

Вычисляет путь до ресурса JavaScript в директории app/assets/javascripts. Если у имени файла источника нет расширения, будет добавлено .js. Будут переданы полные пути от корня документа. Используется внутренне javascript_include_tag для создания пути к скрипту.

javascript_path "common" # => /assets/common.js

6.1.7. javascript_url

Вычисляет URL ресурса JavaScript в директории app/assets/javascripts. Он вызовет javascript_path и соединит с вашим текущим хостом или хостом ресурсов.

javascript_url "common" # => http://www.example.com/assets/common.js

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

stylesheet_link_tag "application" # => <link href="/assets/application.css" media="screen" rel="stylesheet" />

Также можно включить все стили из директории стилей, используя :all в качестве источника:

stylesheet_link_tag :all

Также можно кэшировать несколько таблиц стилей в один файл, что требует меньше соединений HTTP для скачивания и может быть лучше сжато gzip (что сделает передачу быстрее). Кэширование произойдет, только если ActionController::Base.perform_caching установлена true (по умолчанию для окружения production, но не для development).

stylesheet_link_tag :all, cache: true
# => <link href="/assets/all.css" media="screen" rel="stylesheet" />

6.1.9. stylesheet_path

Вычисляет путь до ресурса таблицы стилей в директории app/assets/stylesheets. Если у имени файла источника нет расширения, будет добавлено .css. Будут переданы полные пути от корня документа. Используется внутренне stylesheet_link_tag для создания пути к таблице стилей.

stylesheet_path "application" # => /assets/application.css

6.1.10. stylesheet_url

Вычисляет URL ресурса таблицы стилей в директории app/assets/stylesheets. Он вызовет stylesheet_path и соединит с вашим текущим хостом или хостом ресурсов.

stylesheet_url "application" # => http://www.example.com/assets/application.css

6.2. AtomFeedHelper

6.2.1. atom_feed

Этот хелпер позволяет с легкостью создать новостную ленту Atom. Вот пример полного использования:

config/routes.rb

resources :articles

app/controllers/articles_controller.rb

def index
  @articles = Article.all

  respond_to do |format|
    format.html
    format.atom
  end
end

app/views/articles/index.atom.builder

atom_feed do |feed|
  feed.title("Articles Index")
  feed.updated(@articles.first.created_at)

  @articles.each do |article|
    feed.entry(article) do |entry|
      entry.title(article.title)
      entry.content(article.body, type: 'html')

      entry.author do |author|
        author.name(article.author_name)
      end
    end
  end
end

6.3. BenchmarkHelper

6.3.1. benchmark

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

<% benchmark "Process data files" do %>
  <%= expensive_files_operation %>
<% end %>

Это добавит в лог что-то вроде "Process data files (0.34523)", затем это можно использовать для сравнения времени при оптимизации кода.

6.4. CacheHelper

6.4.1. cache

Метод для кэширования фрагмента вьюхи, в отличие от целого экшна или страницы. Эта техника полезна для кэширования кусочков, таких как меню, списки заголовков, статичные фрагменты HTML и так далее. Этот метод принимает блок, содержащий код, который вы хотите закэшировать. Подробности смотрите в AbstractController::Caching::Fragments.

<% cache do %>
  <%= render "shared/footer" %>
<% end %>

6.5. CaptureHelper

6.5.1. capture

Метод capture позволяет извлечь часть шаблона в переменную. Эту переменную потом можно использовать в любом месте шаблона или макета.

<% @greeting = capture do %>
  <p>Welcome! The date and time is <%= Time.now %></p>
<% end %>

Захваченная переменная может быть потом где-то использована.

<html>
  <head>
    <title>Welcome!</title>
  </head>
  <body>
    <%= @greeting %>
  </body>
</html>

6.5.2. content_for

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

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

app/views/layouts/application.html.erb

<html>
  <head>
    <title>Welcome!</title>
    <%= yield :special_script %>
  </head>
  <body>
    <p>Welcome! The date and time is <%= Time.now %></p>
  </body>
</html>

app/views/articles/special.html.erb

<p>This is a special page.</p>

<% content_for :special_script do %>
  <script>alert('Hello!')</script>
<% end %>

6.6. DateHelper

6.6.1. date_select

Возвращает набор тегов select (по одному для года, месяца и дня), предзаполненных для доступа к определенному атрибуту даты.

date_select("article", "published_on")

6.6.2. datetime_select

Возвращает набор тегов select (по одному для года, месяца, дня, часа и минуты), предзаполненных для доступа к определенному атрибуту даты-времени.

datetime_select("article", "published_on")

6.6.3. distance_of_time_in_words

Возвращает приблизительный промежуток времени между двумя объектами Time или Date или числами-секундами. Установите include_seconds в true, если хотите более детальное приближение.

distance_of_time_in_words(Time.now, Time.now + 15.seconds)        # => less than a minute
distance_of_time_in_words(Time.now, Time.now + 15.seconds, include_seconds: true)  # => less than 20 seconds

6.6.4. select_date

Возвращает набор тегов HTML select (по одному для года, месяца и дня), предзаполненных предоставленной date.

# Создает select для даты со предоставленной датой как значение по умолчанию (шесть дней от сегодня)
select_date(Time.today + 6.days)

# Создает select для даты с сегодняшней датой как значение по умолчанию (без аргумента)
select_date()

6.6.5. select_datetime

Возвращает набор тегов HTML select (по одному для года, месяца, дня, часа и минуты), предзаполненных предоставленным datetime.

# Создает select для datetime со предоставленным значением по умолчанию (четыре дня от сегодня)
select_datetime(Time.now + 4.days)

# Создает select для datetime с сегодняшней датой как значение по умолчанию (без аргумента)
select_datetime()

6.6.6. select_day

Возвращает тег select с опциями для каждого дня с 1 до 31 с выбранным текущим днем.

# Создает поле select для дней с предоставленной датой как значение по умолчанию
select_day(Time.today + 2.days)

# Создает поле select для дней с данным числом как значение по умолчанию
select_day(5)

6.6.7. select_hour

Возвращает тег select с опциями для каждого часа с 0 до 23 с выбранным текущим часом.

# Создает поле select для часов с предоставленным временем как значение по умолчанию
select_hour(Time.now + 6.hours)

6.6.8. select_minute

Возвращает тег select с опциями для каждой минуты с 0 до 59 с выбранной текущей минутой.

# Создает поле select для минут с предоставленным временем как значение по умолчанию
select_minute(Time.now + 10.minutes)

6.6.9. select_month

Возвращает тег select с опциями для каждого месяца с January до December с выбранным текущим месяцем.

# Создает поле select для месяцев с текущим месяцем как значение по умолчанию
select_month(Date.today)

6.6.10. select_second

Возвращает тег select с опциями для каждой секунды с 0 до 59 с выбранной текущей секундой.

# Создает поле select для секунд с предоставленным временем как значение по умолчанию
select_second(Time.now + 16.seconds)

6.6.11. select_time

Возвращает набор тегов HTML select (по одному для часа и минуты).

# Создает поля select с предоставленным временем как значение по умолчанию
select_time(Time.now)

6.6.12. select_year

Возвращает тег select с опциями для каждого года из пяти от и до выбранного текущего. Пятилетний радиус может быть изменен с помощью опциональных ключей :start_year и :end_year.

# Создает поле select для пяти лет в обе стороны от Date.today, являющаяся значением по умолчанию для текущего года
select_year(Date.today)

# Создает поле select от 1900 до 2016 со значением по умолчанию текущим годом
select_year(Date.today, start_year: 1900, end_year: 2016)

6.6.13. time_ago_in_words

Подобен distance_of_time_in_words, где to_time установлен Time.now.

time_ago_in_words(3.minutes.from_now)  # => 3 minutes

6.6.14. time_select

Возвращает набор тегов select (по одному для часа, минуты и, опционально, секунды), предзаполненных для доступа к определенному атрибуту времени. Селекты подготовлены к многопараметровому назначению в объекте Active Record.

# Создает тег select для времени, который при POST будет сохранен в переменную order отправленного атрибута
time_select("order", "submitted")

6.7. DebugHelper

Возвращает тег pre с объектом, выгруженным в YAML. Это создает удобочитаемый способ просмотра объекта.

my_hash = { 'first' => 1, 'second' => 'two', 'third' => [1,2,3] }
debug(my_hash)

<pre class='debug_dump'>---
first: 1
second: two
third:
- 1
- 2
- 3
</pre>

6.8. FormHelper

Хелперы форм разработаны для более простой работы с моделями по сравнению со стандартными элементами HTML, предоставляя набор методов для создания форм на основе ваших моделей. Этот хелпер создает HTML для форм, предоставляя метод для каждого типа полей (например text, password, select и так далее). Когда форма подтверждается (т.е. когда пользователь нажимает кнопку подтверждения или form.submit вызывается в JavaScript), поля формы будут встроены в объект params и переданы в контроллер.

Имеется два типа хелперов форм: те, которые работают с атрибутами модели, и те, которые нет. Этот хелпер имеет дело с теми, которые работают с атрибутами; чтобы посмотреть примеры хелперов форм, которые не работают с атрибутами модели, обратитесь к документации ActionView::Helpers::FormTagHelper.

Основной метод этого хелпера, form_for, дает возможность создавать форму для экземпляра модели; например, допустим, что имеется модель Person, и мы хотим создать ее новый экземпляр:

# Note: переменная @person была создана в контроллере (т.е. @person = Person.new)
<%= form_for @person, url: { action: "create" } do |f| %>
  <%= f.text_field :first_name %>
  <%= f.text_field :last_name %>
  <%= submit_tag 'Create' %>
<% end %>

Созданным HTML будет:

<form action="/people/create" method="post">
  <input id="person_first_name" name="person[first_name]" type="text" />
  <input id="person_last_name" name="person[last_name]" type="text" />
  <input name="commit" type="submit" value="Create" />
</form>

Объект params, созданный после отправки этой формы, будет выглядеть так:

{ "action" => "create", "controller" => "people", "person" => { "first_name" => "William", "last_name" => "Smith" } }

В хэше params будет вложенное значение person, к которому можно получить доступ в контроллере с помощью params[:person].

6.8.1. check_box

Возвращает тэг чекбокса с учетом доступа к определенному атрибуту.

# Допустим, что @article.validated? равен 1:
check_box("article", "validated")
# => <input type="checkbox" id="article_validated" name="article[validated]" value="1" />
#    <input name="article[validated]" type="hidden" value="0" />

6.8.2. fields_for

Создает пространство имен вокруг определенного объекта модели наподобие form_for, но не создает сами теги формы. Это делает fields_for подходящим для указания дополнительных объектов модели в той же форме:

<%= form_for @person, url: { action: "update" } do |person_form| %>
  First name: <%= person_form.text_field :first_name %>
  Last name : <%= person_form.text_field :last_name %>

  <%= fields_for @person.permission do |permission_fields| %>
    Admin?  : <%= permission_fields.check_box :admin %>
  <% end %>
<% end %>

6.8.3. file_field

Возвращает поле для загрузки файла с учетом доступа к определенному атрибуту.

file_field(:user, :avatar)
# => <input type="file" id="user_avatar" name="user[avatar]" />

6.8.4. form_for

Создает форму и пространство имен вокруг определенного объекта модели, используемого как основа для запроса значений для полей.

<%= form_for @article do |f| %>
  <%= f.label :title, 'Title' %>:
  <%= f.text_field :title %><br>
  <%= f.label :body, 'Body' %>:
  <%= f.text_area :body %><br>
<% end %>

6.8.5. hidden_field

Возвращает тег скрытого поля с учетом доступа к определенному атрибуту.

hidden_field(:user, :token)
# => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />

6.8.6. label

Возвращает тег label с учетом поля ввода для определенного атрибута.

label(:article, :title)
# => <label for="article_title">Title</label>

6.8.7. password_field

Возвращает тег input типа "password" с учетом доступа к определенному атрибуту..

password_field(:login, :pass)
# => <input type="text" id="login_pass" name="login[pass]" value="#{@login.pass}" />

6.8.8. radio_button

Возвращает тег радио кнопки с учетом доступа к определенному атрибуту.

# Let's say that @article.category returns "rails":
radio_button("article", "category", "rails")
radio_button("article", "category", "java")
# => <input type="radio" id="article_category_rails" name="article[category]" value="rails" checked="checked" />
#    <input type="radio" id="article_category_java" name="article[category]" value="java" />

6.8.9. text_area

Возвращает набор открывающего и закрывающего тега textarea с учетом доступа к определенному атрибуту.

text_area(:comment, :text, size: "20x30")
# => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
#      #{@comment.text}
#    </textarea>

6.8.10. text_field

Возвращает тег input типа "text" с учетом доступа к определенному атрибуту.

text_field(:article, :title)
# => <input type="text" id="article_title" name="article[title]" value="#{@article.title}" />

6.8.11. email_field

Возвращает тег input типа "email" с учетом доступа к определенному атрибуту.

email_field(:user, :email)
# => <input type="email" id="user_email" name="user[email]" value="#{@user.email}" />

6.8.12. url_field

Возвращает тег input типа "url" с учетом доступа к определенному атрибуту.

url_field(:user, :url)
# => <input type="url" id="user_url" name="user[url]" value="#{@user.url}" />

6.9. FormOptionsHelper

Представляет ряд методов для превращения различного рода контейнеров в набор тегов option.

6.9.1. collection_select

Возвращает теги select и option для коллекции значений, возвращаемых method для класса object.

Пример структуры объекта для использования с этим методом:

class Article < ApplicationRecord
  belongs_to :author
end

class Author < ApplicationRecord
  has_many :articles
  def name_with_initial
    "#{first_name.first}. #{last_name}"
  end
end

Пример использования (выбор связанного Author для экземпляра Article, @article):

collection_select(:article, :author_id, Author.all, :id, :name_with_initial, { prompt: true })

Если @article.author_id — 1, это вернет:

<select name="article[author_id]">
  <option value="">Please select</option>
  <option value="1" selected="selected">D. Heinemeier Hansson</option>
  <option value="2">D. Thomas</option>
  <option value="3">M. Clark</option>
</select>

6.9.2. collection_radio_buttons

Возвращает теги radio_button для коллекции значений, возвращаемых method для класса object.

Пример структуры объекта для использования с этим методом:

class Article < ApplicationRecord
  belongs_to :author
end

class Author < ApplicationRecord
  has_many :articles
  def name_with_initial
    "#{first_name.first}. #{last_name}"
  end
end

Пример использования (выбор связанного Author для экземпляра Article, @article):

collection_radio_buttons(:article, :author_id, Author.all, :id, :name_with_initial)

Если @article.author_id — 1, это вернет:

<input id="article_author_id_1" name="article[author_id]" type="radio" value="1" checked="checked" />
<label for="article_author_id_1">D. Heinemeier Hansson</label>
<input id="article_author_id_2" name="article[author_id]" type="radio" value="2" />
<label for="article_author_id_2">D. Thomas</label>
<input id="article_author_id_3" name="article[author_id]" type="radio" value="3" />
<label for="article_author_id_3">M. Clark</label>

6.9.3. collection_check_boxes

Возвращает теги check_box для коллекции значений, возвращаемых method для класса object.

Пример структуры объекта для использования с этим методом:

class Article < ApplicationRecord
  has_and_belongs_to_many :authors
end

class Author < ApplicationRecord
  has_and_belongs_to_many :articles
  def name_with_initial
    "#{first_name.first}. #{last_name}"
  end
end

Пример использования (выбор связанного Author для экземпляра Article, @article):

collection_check_boxes(:article, :author_ids, Author.all, :id, :name_with_initial)

Если @article.author_id — [1], это вернет:

<input id="article_author_ids_1" name="article[author_ids][]" type="checkbox" value="1" checked="checked" />
<label for="article_author_ids_1">D. Heinemeier Hansson</label>
<input id="article_author_ids_2" name="article[author_ids][]" type="checkbox" value="2" />
<label for="article_author_ids_2">D. Thomas</label>
<input id="article_author_ids_3" name="article[author_ids][]" type="checkbox" value="3" />
<label for="article_author_ids_3">M. Clark</label>
<input name="article[author_ids][]" type="hidden" value="" />

6.9.4. option_groups_from_collection_for_select

Возвращает строку с тегами option, подобно options_from_collection_for_select, но группирует их тегами optgroup на основе отношений аргументов.

Пример структуры объекта для использования с этим методом:

class Continent < ApplicationRecord
  has_many :countries
  # attribs: id, name
end

class Country < ApplicationRecord
  belongs_to :continent
  # attribs: id, name, continent_id
end

Пример использования:

option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)

Возможный результат:

<optgroup label="Africa">
  <option value="1">Egypt</option>
  <option value="4">Rwanda</option>
  ...
</optgroup>
<optgroup label="Asia">
  <option value="3" selected="selected">China</option>
  <option value="12">India</option>
  <option value="5">Japan</option>
  ...
</optgroup>

Note: Возвращаются только теги optgroup и option, вам все еще нужно обернуть результат в подходящий тег select.

6.9.5. options_for_select

Принимает контейнер (hash, array, enumerable, ваш тип) и возвращает строку тегов option.

options_for_select([ "VISA", "MasterCard" ])
# => <option>VISA</option> <option>MasterCard</option>

Note: Возвращаются только теги option, вам все еще нужно обернуть результат в обычный тег HTML select.

6.9.6. options_from_collection_for_select

Возвращает строку тегов option, собранную с помощью итерации по collection и назначая результат вызова value_method как значение option и text_method как текст option.

# options_from_collection_for_select(collection, value_method, text_method, selected = nil)

Например, представим цикл, проходящий по каждому человеку в @project.people для создания тега ввода:

options_from_collection_for_select(@project.people, "id", "name")
# => <option value="#{person.id}">#{person.name}</option>

Note: Возвращаются только теги option, вам все еще нужно обернуть результат в обычный тег HTML select.

6.9.7. select

Создает тег select и ряд связанных тегов option для предоставленного объекта и метода.

Пример:

select("article", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })

Если @article.person_id — 1, это выдаст:

<select name="article[person_id]">
  <option value=""></option>
  <option value="1" selected="selected">David</option>
  <option value="2">Eileen</option>
  <option value="3">Rafael</option>
</select>

6.9.8. time_zone_options_for_select

Возвращает строку тегов option для практически всех временных зон мира.

6.9.9. time_zone_select

Возвращает теги select и option для заданного объекта и метода с помощью time_zone_options_for_select для создания списка тегов option.

time_zone_select( "user", "time_zone")

6.9.10. date_field

Создает тег input типа "date", созданного с учетом доступа к определенному атрибуту.

date_field("user", "dob")

6.10. FormTagHelper

Предоставляет ряд методов для создания тегов форм, не полагающихся на объект Active Record, назначенный шаблону, как делает FormHelper. Вместо этого вы предоставляете имена и значения вручную.

6.10.1. check_box_tag

Создает поле ввода в виде чек-бокса.

check_box_tag 'accept'
# => <input id="accept" name="accept" type="checkbox" value="1" />

6.10.2. field_set_tag

Создает fieldset для группировки элементов форм HTML.

<%= field_set_tag do %>
  <p><%= text_field_tag 'name' %></p>
<% end %>
# => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>

6.10.3. file_field_tag

Создает поле загрузки файла.

<%= form_tag({ action:"post" }, multipart: true) do %>
  <label for="file">File to Upload</label> <%= file_field_tag "file" %>
  <%= submit_tag %>
<% end %>

Примерный результат:

file_field_tag 'attachment'
# => <input id="attachment" name="attachment" type="file" />

6.10.4. form_tag

Начинает тег form, указывающий action URL, настроенный с помощью url_for_options, как в ActionController::Base#url_for.

<%= form_tag '/articles' do %>
  <div><%= submit_tag 'Save' %></div>
<% end %>
# => <form action="/articles" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>

6.10.5. hidden_field_tag

Создает скрытое поле input, используемое для передачи данных, которые могли бы быть потеряны из-за особенностей HTTP, или данные, которые должны быть скрыт от пользователя.

hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@'
# => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />

6.10.6. image_submit_tag

Отображает изображение, при нажатии на котором будет отправлена форма.

image_submit_tag("login.png")
# => <input src="/images/login.png" type="image" />

6.10.7. label_tag

Создает тег label.

label_tag 'name'
# => <label for="name">Name</label>

6.10.8. password_field_tag

Создает поле для ввода пароля, поле маскированного текста, которое спрячет то, что вводит пользователь символами маски.

password_field_tag 'pass'
# => <input id="pass" name="pass" type="password" />

6.10.9. radio_button_tag

Создает радио-кнопку; используйте группу радио-кнопок с одинаковым именем, чтобы разрешить пользователям выбирать из группы опций.

radio_button_tag 'gender', 'male'
# => <input id="gender_male" name="gender" type="radio" value="male" />

6.10.10. select_tag

Создает выпадающее поле выбора.

select_tag "people", "<option>David</option>"
# => <select id="people" name="people"><option>David</option></select>

6.10.11. submit_tag

Создает кнопку подтверждения с текстом-надписью.

submit_tag "Publish this article"
# => <input name="commit" type="submit" value="Publish this article" />

6.10.12. text_area_tag

Создает поле ввода текста; используйте textarea для длинного ввода текста, такого как статьи или описания.

text_area_tag 'article'
# => <textarea id="article" name="article"></textarea>

6.10.13. text_field_tag

Создает обычное поле ввода текста; используйте их для ввода небольших кусочков текста, таких как имя пользователя или поисковый запрос.

text_field_tag 'name'
# => <input id="name" name="name" type="text" />

6.10.14. email_field_tag

Создает стандартное поле ввода с типом email.

email_field_tag 'email'
# => <input id="email" name="email" type="email" />

6.10.15. url_field_tag

Создает стандартное поле ввода с типом url.

url_field_tag 'url'
# => <input id="url" name="url" type="url" />

6.10.16. date_field_tag

Создает стандартное поле ввода с типом date.

date_field_tag "dob"
# => <input id="dob" name="dob" type="date" />

6.11. JavaScriptHelper

Предоставляет функционал для работы с JavaScript в ваших вьюхах.

6.11.1. escape_javascript

Экранирует переводы строк и одиночные и двойные кавычки во фрагментах JavaScript.

6.11.2. javascript_tag

Возвращает тег JavaScript, оборачивающий предоставленный код.

javascript_tag "alert('All is good')"

<script>
//<![CDATA[
alert('All is good')
//]]>
</script>

6.12. NumberHelper

Представляет методы для конвертации чисел в форматированные строки. Методы предоставлены для телефонных номеров, валют, процентов, позиционных систем исчисления и размеров файла.

6.12.1. number_to_currency

Форматирует число в строку с валютой (например, $13.65).

number_to_currency(1234567890.50) # => $1,234,567,890.50

6.12.2. number_to_human_size

Форматирует размер в байтах в более понимаемое представление; полезно для показа размеров файла пользователям.

number_to_human_size(1234)          # => 1.2 KB
number_to_human_size(1234567)       # => 1.2 MB

6.12.3. number_to_percentage

Форматирует число в строку с процентом.

number_to_percentage(100, precision: 0)        # => 100%

6.12.4. number_to_phone

Форматирует число в телефонный номер (по умолчанию США).

number_to_phone(1235551234) # => 123-555-1234

6.12.5. number_with_delimiter

Группирует тысячи в числе с помощью разделителя.

number_with_delimiter(12345678) # => 12,345,678

6.12.6. number_with_precision

Форматирует число с помощью определенного уровня точности, по умолчанию 3.

number_with_precision(111.2345)                # => 111.235
number_with_precision(111.2345, precision: 2)  # => 111.23

6.13. SanitizeHelper

Модуль SanitizeHelper представляет набор методов для очистки текста от нежелательных элементов HTML.

6.13.1. sanitize

Хелпер sanitize экранирует все теги HTML и удалит все атрибуты, которые не разрешены явно.

sanitize @article.body

Если переданы опции или :attributes, или :tags, разрешены только упомянутые теги и атрибуты, и ничего более.

sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style)

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

class Application < Rails::Application
  config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
end

6.13.2. sanitize_css(style)

Экранирует блок кода CSS.

Обрезает все теги ссылок в тексте, оставляя только текст ссылки.

strip_links('<a href="http://rubyonrails.org">Ruby on Rails</a>')
# => Ruby on Rails

strip_links('emails to <a href="mailto:me@email.com">me@email.com</a>.')
# => emails to me@email.com.

strip_links('Blog: <a href="http://myblog.com/">Visit</a>.')
# => Blog: Visit.

6.13.4. strip_tags(html)

Обрезает все теги HTML из html, включая комментарии. Эта функция доступна, если подключен гем rails-html-sanitizer.

strip_tags("Strip <i>these</i> tags!")
# => Strip these tags!

strip_tags("<b>Bold</b> no more!  <a href='more.html'>See more</a>")
# => Bold no more!  See more

NB: Результат все еще может содержать неэкранированные символы '<', '>', '&' и путать браузеры.

6.14. CsrfHelper

Возвращает метатеги "csrf-param" и "csrf-token" с, соответственно, именами параметра и токена против межсайтовой подделки запросов.

<%= csrf_meta_tags %>

Обычные формы создают скрытые поля, поэтому они не используют эти теги. Подробнее в Руководстве Rails по безопасности.

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

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

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

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

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

before_action :set_expert_locale

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

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

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