модуль ActionView::Helpers::FormHelper
Помощники для форм разработаны, чтобы сделать работу с ресурсами намного проще по сравнению с использованием обычного HTML.
Обычно форма, предназначенная для создания или обновления ресурса, отражает идентичность ресурса несколькими способами: (i) URL, на который отправляется форма (атрибут элемента формы action) должен приводить к маршрутизации запроса к соответствующему действию контроллера (с соответствующим параметром :id в случае существующего ресурса), (ii) поля ввода должны быть именованы таким образом, чтобы в контроллере их значения отображались в соответствующих местах в хэше params, и (iii) для существующего записей, когда форма первоначально отображается, поля ввода, соответствующие атрибутам ресурса, должны отображать текущие значения этих атрибутов.
В Rails это обычно достигается путём создания формы с использованием form_for и ряда связанных методов-помощников. form_for генерирует соответствующий тег form и возвращает объект-помощник формы, который знает модель, к которой относится форма. Поля ввода создаются путём вызова методов, определённых в объекте-помощнике формы, что позволяет генерировать соответствующие имена и значения по умолчанию, соответствующие атрибутам модели, а также удобные идентификаторы и т.д. Конвенции в именах сгенерированных полей позволяют контроллерам получать данные формы в виде хорошо структурированного хэша params, без каких-либо усилий с вашей стороны.
Например, чтобы создать новую запись о человеке, обычно создаётся новая инстанция Person в действии PeopleController#new, @person, и во шаблоне представления передаётся этот объект в form_for:
<%= form_for @person do |f| %> <%= f.label :first_name %>: <%= f.text_field :first_name %><br /> <%= f.label :last_name %>: <%= f.text_field :last_name %><br /> <%= f.submit %> <% end %>
Сгенерированный HTML будет выглядеть следующим образом (с учётом форматирования):
<form action="/people" class="new_person" id="new_person" method="post"> <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> <label for="person_first_name">First name</label>: <input id="person_first_name" name="person[first_name]" type="text" /><br /> <label for="person_last_name">Last name</label>: <input id="person_last_name" name="person[last_name]" type="text" /><br /> <input name="commit" type="submit" value="Create Person" /> </form>
Как вы видите, HTML отражает знания о ресурсе в нескольких местах, таких как путь, по которому форма должна быть отправлена, или имена полей ввода.
В частности, благодаря соглашениям, используемым в именах сгенерированных полей, контроллер получает вложенный хэш params[:person] с атрибутами человека, установленными в форме. Этот хэш готов к передаче в Person.new:
@person = Person.new(params[:person]) if @person.save # success else # error handling end
Интересно, что тот же код представления в предыдущем примере можно использовать для редактирования человека. Если @person — это существующая запись с именем «Иван Иванов» и ID 256, то код выше будет генерировать:
<form action="/people/256" class="edit_person" id="edit_person_256" method="post"> <input name="_method" type="hidden" value="patch" /> <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> <label for="person_first_name">First name</label>: <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br /> <label for="person_last_name">Last name</label>: <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br /> <input name="commit" type="submit" value="Update Person" /> </form>
Обратите внимание, что конечная точка, значения по умолчанию и текст кнопки отправки настроены для @person. Это происходит потому, что используемые помощники знают, является ли ресурс новой записью или нет, и генерируют HTML соответственно.
Контроллер снова получит данные формы в хэше params[:person], готовом к передаче в Person#update:
if @person.update(params[:person]) # success else # error handling end
Вот как обычно работают с ресурсами.
Публичные методы экземпляра
# File actionview/lib/action_view/helpers/form_helper.rb, line 951
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
end Возвращает тег checkbox, настроенный для доступа к указанному атрибуту (определяемому method) объекта, присвоенного шаблону (определяемого object). Этот объект должен быть объектом экземпляра (@object), а не локальным объектом. Предполагается, что method возвращает целое число, и если это целое число больше нуля, то checkbox отмечен. Дополнительные параметры тега ввода могут быть переданы в виде хэша с options. Значение checked_value по умолчанию равно 1, а значение unchecked_value по умолчанию равно 0, что удобно для булевых значений.
Проблема
Спецификация HTML гласит, что неотмеченные чекбоксы не отправляются, и поэтому веб-браузеры их не отправляют. К сожалению, это приводит к проблеме: если модель Invoice имеет флаг paid, и в форме для редактирования платёжного счета пользователь снимает чекбокс, параметр paid не отправляется. Таким образом, любое выражение присваивания по массе, такое как
@invoice.update(params[:invoice])
не обновит флаг.
Чтобы предотвратить это, помощник генерирует дополнительное скрытое поле перед чекбоксом. Скрытое поле имеет то же имя, и его атрибуты имитируют неотмеченный чекбокс.
Таким образом, клиент отправляет либо только скрытое поле (что соответствует тому, что чекбокс не отмечен), либо оба поля. Поскольку спецификация HTML требует, чтобы пары ключ/значение отправлялись в том же порядке, в котором они появляются в форме, а извлечение параметров получает последнее вхождение любого повторяющегося ключа в строке запроса, это работает для обычных форм.
К сожалению, это решение не работает, когда чекбокс находится внутри массивоподобного параметра, как в
<%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> <%= form.check_box :paid %> ... <% end %>
потому что повторение имени параметра именно то, что Rails пытается отличить элементы массива. Для каждого элемента с отмеченным чекбоксом вы получаете дополнительный «призрачный» элемент только с этим атрибутом, присвоенным «0».
В этом случае предпочтительно либо использовать check_box_tag или использовать хэши вместо массивов.
# Let's say that @post.validated? is 1:
check_box("post", "validated")
# => <input name="post[validated]" type="hidden" value="0" />
# <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
# Let's say that @puppy.gooddog is "no":
check_box("puppy", "gooddog", {}, "yes", "no")
# => <input name="puppy[gooddog]" type="hidden" value="no" />
# <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
# => <input name="eula[accepted]" type="hidden" value="no" />
# <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 980
def color_field(object_name, method, options = {})
Tags::ColorField.new(object_name, method, self, options).render
end Возвращает #text_field типа «цвет».
color_field("car", "color")
# => <input id="car_color" name="car[color]" type="color" value="#000000" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1044
def date_field(object_name, method, options = {})
Tags::DateField.new(object_name, method, self, options).render
end Возвращает #text_field типа «дата».
date_field("user", "born_on")
# => <input id="user_born_on" name="user[born_on]" type="date" />
Значение по умолчанию генерируется путем попытки вызова strftime с «%Y-%m-%d» на значении объекта, что обеспечивает ожидаемое поведение для экземпляров DateTime и ActiveSupport::TimeWithZone. Вы по-прежнему можете переопределить это, явно передав параметр «value», например:
@user.born_on = Date.new(1984, 1, 27)
date_field("user", "born_on", value: "1984-05-12")
# => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
Вы можете создавать значения для атрибутов «min» и «max», передавая экземпляры Date или Time в хэш параметров.
date_field("user", "born_on", min: Date.today)
# => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
В качестве альтернативы, вы можете передать строку String в формате ISO8601 даты как значения для «min» и «max».
date_field("user", "born_on", min: "2014-05-20")
# => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1102
def datetime_field(object_name, method, options = {})
Tags::DatetimeLocalField.new(object_name, method, self, options).render
end Возвращает #text_field типа «datetime-local».
datetime_field("user", "born_on")
# => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
Значение по умолчанию генерируется путем попытки вызова strftime с «%Y-%m-%dT%T» на значении объекта, что обеспечивает ожидаемое поведение для экземпляров DateTime и ActiveSupport::TimeWithZone.
@user.born_on = Date.new(1984, 1, 12)
datetime_field("user", "born_on")
# => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
Вы можете создавать значения для атрибутов «min» и «max», передавая экземпляры Date или Time в хэш параметров.
datetime_field("user", "born_on", min: Date.today)
# => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
В качестве альтернативы, вы можете передать строку String в формате ISO8601 datetime как значения для «min» и «max».
datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
# => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1156
def email_field(object_name, method, options = {})
Tags::EmailField.new(object_name, method, self, options).render
end Возвращает #text_field типа «email».
email_field("user", "address")
# => <input id="user_address" name="user[address]" type="email" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 718
def fields_for(record_name, record_object = nil, options = {}, &block)
builder = instantiate_builder(record_name, record_object, options)
capture(builder, &block)
end Создаёт область действия вокруг определённого объекта модели, как #form_for, но не создаёт теги формы сами по себе. Это делает #fields_for подходящим для указания дополнительных объектов модели в той же форме.
Хотя использование и назначение fields_for аналогично form_for, его сигнатура метода немного отличается. Как и form_for, он передаёт объект FormBuilder, связанный с определённым объектом модели, в блок, и внутри блока можно вызывать методы для генерации полей, связанных с объектом модели. Поля могут отражать объект модели двумя способами — как они названы (следовательно, как отправленные значения отображаются в хэше params в контроллере) и какие значения по умолчанию отображаются, когда форма полей отображается впервые. Для того, чтобы оба этих свойства были указаны независимо, в метод можно передать как имя объекта (представленное символом или строкой), так и сам объект —
<%= form_for @person do |person_form| %>
First name: <%= person_form.text_field :first_name %>
Last name : <%= person_form.text_field :last_name %>
<%= fields_for :permission, @person.permission do |permission_fields| %>
Admin? : <%= permission_fields.check_box :admin %>
<% end %>
<%= person_form.submit %>
<% end %> В этом случае поле флажка будет представлено тегом HTML input с атрибутом name permission[admin], а отправленное значение будет отображаться в контроллере как params[:permission][:admin]. Если @person.permission — это существующий запись с атрибутом admin, начальное состояние флажка при первом отображении будет отражать значение @person.permission.admin.
Часто это можно упростить, передав только имя объекта модели в fields_for —
<%= fields_for :permission do |permission_fields| %> Admin?: <%= permission_fields.check_box :admin %> <% end %>
…в этом случае, если :permission также является именем переменной экземпляра @permission, начальное состояние поля ввода будет отражать значение атрибута этой переменной @permission.admin.
В качестве альтернативы можно передать только сам объект модели (если первый аргумент не является строкой или символом, fields_for поймёт, что имя опущено) —
<%= fields_for @person.permission do |permission_fields| %> Admin?: <%= permission_fields.check_box :admin %> <% end %>
и fields_for получит необходимое имя поля из класса объекта модели, например, если @person.permission, принадлежит классу Permission, поле всё равно будет названо permission[admin].
Примечание: Это также работает для методов в FormOptionHelper и DateHelper, которые предназначены для работы с объектом в качестве основы, например, FormOptionHelper#collection_select и ActionView::Helpers::DateHelper#datetime_select.
Примеры вложенных атрибутов
Когда объект, принадлежащий текущей области видимости, имеет метод записи для вложенного атрибута, #fields_for создаст новую область видимости для этого атрибута. Это позволяет создавать формы, которые устанавливают или изменяют атрибуты родительского объекта и его ассоциаций за один раз.
Методы записи для вложенных атрибутов — это обычные методы записи, названные в соответствии с ассоциацией. Наиболее распространённый способ определения этих методов записи — с помощью accepts_nested_attributes_for в определении модели или путём определения метода с соответствующим именем. Например: метод записи для ассоциации :address называется address_attributes=.
Тип создаваемого формы — один-к-одному или один-ко-многим — зависит от того, возвращает ли обычный метод чтения один объект или массив объектов.
Один-к-одному
Рассмотрим класс Person, который возвращает один адрес из метода чтения address и реагирует на метод записи address_attributes=:
class Person
def address
@address
end
def address_attributes=(attributes)
# Process the attributes hash
end
end
Теперь эту модель можно использовать с вложенным #fields_for, например:
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :address do |address_fields| %>
Street : <%= address_fields.text_field :street %>
Zip code: <%= address_fields.text_field :zip_code %>
<% end %>
...
<% end %> Если адрес уже является ассоциацией в Person, можно использовать accepts_nested_attributes_for для определения метода записи за вас:
class Person < ActiveRecord::Base has_one :address accepts_nested_attributes_for :address end
Если нужно удалить связанную модель через форму, сначала нужно включить это с помощью опции :allow_destroy для accepts_nested_attributes_for:
class Person < ActiveRecord::Base has_one :address accepts_nested_attributes_for :address, allow_destroy: true end
Теперь, когда вы используете элемент формы с параметром _destroy, со значением, которое оценивается как true, вы удалите связанную модель (например, 1, '1', true или 'true'):
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :address do |address_fields| %>
...
Delete: <%= address_fields.check_box :_destroy %>
<% end %>
...
<% end %> Один-ко-многим
Рассмотрим класс Person, который возвращает массив экземпляров Project из метода чтения projects и реагирует на метод записи projects_attributes=:
class Person
def projects
[@project1, @project2]
end
def projects_attributes=(attributes)
# Process the attributes hash
end
end
Обратите внимание, что метод записи projects_attributes= фактически необходим для #fields_for, чтобы правильно идентифицировать :projects как коллекцию, и правильно устанавливать индексы в разметке формы.
Если projects уже является ассоциацией в Person, можно использовать accepts_nested_attributes_for для определения метода записи за вас:
class Person < ActiveRecord::Base has_many :projects accepts_nested_attributes_for :projects end
Теперь эту модель можно использовать с вложенными fields_for. Блок, переданный вызову вложенного #fields_for, будет повторяться для каждого экземпляра в коллекции:
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_fields| %>
<% if project_fields.object.active? %>
Name: <%= project_fields.text_field :name %>
<% end %>
<% end %>
...
<% end %> Также можно указать используемый экземпляр:
<%= form_for @person do |person_form| %>
...
<% @person.projects.each do |project| %>
<% if project.active? %>
<%= person_form.fields_for :projects, project do |project_fields| %>
Name: <%= project_fields.text_field :name %>
<% end %>
<% end %>
<% end %>
...
<% end %> Или использовать коллекцию:
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :projects, @active_projects do |project_fields| %>
Name: <%= project_fields.text_field :name %>
<% end %>
...
<% end %> Если вы хотите удалить любые связанные модели через форму, сначала нужно включить это с помощью опции :allow_destroy для accepts_nested_attributes_for:
class Person < ActiveRecord::Base has_many :projects accepts_nested_attributes_for :projects, allow_destroy: true end
Это позволит указать, какие модели нужно удалить в хэше атрибутов, добавив элемент формы для параметра _destroy со значением, которое оценивается как true (например, 1, '1', true или 'true'):
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_fields| %>
Delete: <%= project_fields.check_box :_destroy %>
<% end %>
...
<% end %> Когда используется коллекция, может потребоваться знать индекс каждого объекта в массиве. Для этого в объекте FormBuilder доступен метод index.
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_fields| %>
Project #<%= project_fields.index %>
...
<% end %>
...
<% end %> Обратите внимание, что #fields_for автоматически сгенерирует скрытое поле для хранения ID записи. В некоторых случаях скрытое поле не нужно, и вы можете передать include_id: false для предотвращения автоматического рендеринга #fields_for.
# File actionview/lib/action_view/helpers/form_helper.rb, line 863
def file_field(object_name, method, options = {})
Tags::FileField.new(object_name, method, self, options).render
end Возвращает тег ввода для загрузки файлов, настроенный для доступа к указанному атрибуту (определяемому method) объекта, назначенного шаблону (определяемому object). Дополнительные опции для тега ввода могут быть переданы в виде хэша с options. Эти опции будут добавлены к HTML как атрибуты HTML-элемента, как показано в примере.
Использование этого метода внутри блока form_for задаст кодировку окружающей формы multipart/form-data.
Опции
-
Создаёт стандартные атрибуты HTML для тега.
-
:disabled- Если установлено в true, пользователь не сможет использовать этот элемент ввода. -
:multiple- Если установлено в true, *в большинстве современных браузеров* пользователю будет разрешено выбрать несколько файлов. -
:accept- Если установлено на один или несколько MIME-типов, пользователю будет предложен фильтр при выборе файла. Вам всё равно нужно настроить проверки модели.
Примеры
file_field(:user, :avatar) # => <input type="file" id="user_avatar" name="user[avatar]" /> file_field(:post, :image, multiple: true) # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" /> file_field(:post, :attached, accept: 'text/html') # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> file_field(:attachment, :file, class: 'file_input') # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 428
def form_for(record, options = {}, &block)
raise ArgumentError, "Missing block" unless block_given?
html_options = options[:html] ||= {}
case record
when String, Symbol
object_name = record
object = nil
else
object = record.is_a?(Array) ? record.last : record
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
html_options[:data] = options.delete(:data) if options.has_key?(:data)
html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
html_options[:method] = options.delete(:method) if options.has_key?(:method)
html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
html_options[:authenticity_token] = options.delete(:authenticity_token)
builder = instantiate_builder(object_name, object, options)
output = capture(builder, &block)
html_options[:multipart] ||= builder.multipart?
html_options = html_options_for_form(options[:url] || {}, html_options)
form_tag_with_body(html_options, output)
end Создаёт форму, позволяющую пользователю создавать или обновлять атрибуты конкретного объекта модели.
Метод может использоваться несколькими слегка разными способами, в зависимости от того, насколько вы хотите полагаться на Rails для автоматического вывода из модели того, как должна быть построена форма. Для обобщенного объекта модели форму можно создать, передав form_for строку или символ, представляющий объект, который нас интересует:
<%= form_for :person do |f| %> First name: <%= f.text_field :first_name %><br /> Last name : <%= f.text_field :last_name %><br /> Biography : <%= f.text_area :biography %><br /> Admin? : <%= f.check_box :admin %><br /> <%= f.submit %> <% end %>
Переменная f , переданная в блок, — это объект FormBuilder, который включает в себя знания об объекте модели, представленном :person , переданном в form_for. Методы, определённые в FormBuilder, используются для генерации полей, связанных с этой моделью. Таким образом, например,
<%= f.text_field :first_name %>
преобразуется в
<%= text_field :person, :first_name %>
, что приводит к тегу HTML <input>, у которого атрибут name равен person[first_name]. Это означает, что при отправке формы значение, введённое пользователем, будет доступно в контроллере как params[:person][:first_name].
Для полей, сгенерированных таким образом с помощью FormBuilder, если :person также является именем экземпляра переменной @person, значение поля по умолчанию, отображаемое при первоначальном отображении формы (например, в случае редактирования существующего записы), будет значением соответствующего атрибута @person.
Правый аргумент form_for — это необязательный хэш параметров:
-
:url— URL, на который должна быть отправлена форма. Это может быть представлено так же, как значения, передаваемые вurl_forилиlink_to. Например, вы можете использовать непосредственно именованный маршрут. Когда модель представлена строкой или символом, как в примере выше, если параметр:urlне указан, по умолчанию форма будет отправлена обратно на текущий URL (ниже мы опишем альтернативное использованиеform_forс ресурсно-ориентированным стилем, где URL не нужно указывать явно). -
:namespace— имя пространства имён для вашей формы, чтобы гарантировать уникальность атрибутов id элементов формы. Атрибут пространства имён будет предваряться подчёркиванием в сгенерированном HTML-id. -
:method— метод, используемый при отправке формы, обычно «get» или «post». Если используется «patch», «put», «delete» или другой глагол, скрытый элемент ввода с именем_methodдобавляется для имитации глагола над отправкой. -
:authenticity_token— маркер аутентификации, используемый в форме. Используйте только если вам нужно передать собственную строку маркера аутентификации или не добавлять поле authenticity_token вообще (передавfalse). Дистанционные формы могут опускать встроенный маркер аутентификации, установивconfig.action_view.embed_authenticity_token_in_remote_forms = false. Это полезно, когда вы кэшируете фрагмент формы. Дистанционные формы получают маркер аутентификации из тегаmeta, поэтому встраивание не нужно, если вы поддерживаете браузеры без JavaScript. -
:remote— если установлено в true, позволит драйверам Unobtrusive JavaScript контролировать поведение отправки. По умолчанию это поведение — отправка с помощью ajax. -
:enforce_utf8— если установлено в false, скрытый элемент ввода с именем utf8 не выводится. -
:html— необязательные HTML-атрибуты для тега формы.
Также обратите внимание, что form_for не создаёт изолированное пространство имён. По-прежнему возможно использовать как отдельные методы FormHelper, так и методы из FormTagHelper. Например:
<%= form_for :person do |f| %> First name: <%= f.text_field :first_name %> Last name : <%= f.text_field :last_name %> Biography : <%= text_area :person, :biography %> Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %> <%= f.submit %> <% end %>
Это также работает для методов в FormOptionHelper и DateHelper, которые предназначены для работы с объектом в качестве основы, например, FormOptionHelper#collection_select и ActionView::Helpers::DateHelper#datetime_select.
form_for с объектом модели
В примерах выше объект для создания или редактирования представлялся символом, переданным в form_for, и мы отметили, что строку можно использовать аналогично. Однако также можно передать сам объект модели в form_for. Например, если @post — это существующая запись, которую вы хотите отредактировать, вы можете создать форму с помощью
<%= form_for @post do |f| %> ... <% end %>
Это работает почти так же, как описано ранее, с парой небольших исключений. Во-первых, префикс, используемый для именования элементов ввода в форме (а следовательно, ключ, обозначающий их в хэше params ), фактически выводится из класса объекта, например params[:post] , если класс объекта — Post. Однако это можно переопределить с помощью параметра :as, например —
<%= form_for(@person, as: :client) do |f| %> ... <% end %>
что приведет к params[:client].
Во-вторых, значения полей, отображаемые при первоначальном отображении формы, берутся из атрибутов объекта, переданного form_for, независимо от того, является ли объект экземпляром переменной. Например, если у нас есть локальная переменная post, представляющая существующую запись,
<%= form_for post do |f| %> ... <% end %>
сгенерирует форму с полями, начальное состояние которых отражает текущие значения атрибутов post.
Ресурсно-ориентированный стиль
В только что показанных примерах, хотя и не указано явно, нам всё ещё нужно использовать параметр :url для указания места отправки формы. Однако дальнейшее упрощение возможно, если запись, переданная в form_for, — это ресурс, т. е. она соответствует набору RESTful маршрутов, например, определённых с помощью метода resources в config/routes.rb. В этом случае Rails просто выведет соответствующий URL из записи. Например,
<%= form_for @post do |f| %> ... <% end %>
эквивалентно примерно такому:
<%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %>
...
<% end %> А для новой записи
<%= form_for(Post.new) do |f| %> ... <% end %>
эквивалентно примерно такому:
<%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %>
...
<% end %> Однако вы можете переопределить отдельные соглашения, например:
<%= form_for(@post, url: super_posts_path) do |f| %> ... <% end %>
Также можно задать формат ответа, например:
<%= form_for(@post, format: :json) do |f| %> ... <% end %>
Для маршрутов с именованным пространством, например, admin_post_url:
<%= form_for([:admin, @post]) do |f| %> ... <% end %>
Если ваш ресурс имеет определённые ассоциации, например, вы хотите добавить комментарии к документу, учитывая, что маршруты заданы корректно:
<%= form_for([@document, @comment]) do |f| %> ... <% end %>
где @document = Document.find(params[:id]) и @comment = Comment.new.
Установка метода
Вы можете принудительно заставить форму использовать полный набор HTTP-глаголов, установив
method: (:get|:post|:patch|:put|:delete)
в хэше параметров. Если глагол не GET или POST, которые встроены в HTML-формы, форма будет установлена на POST, и скрытый элемент ввода с именем _method будет содержать предназначенный глагол, чтобы сервер мог его интерпретировать.
Unobtrusive JavaScript
Указание:
remote: true
в хэше параметров создаёт форму, которая позволит драйверам Unobtrusive JavaScript изменять её поведение. Ожидаемое поведение по умолчанию — использование XMLHttpRequest в фоновом режиме вместо обычной отправки POST, но в конечном итоге поведение зависит от реализации драйвера JavaScript. Даже если для сериализации элементов формы используется JavaScript, отправка формы будет работать так же, как и обычная отправка, с точки зрения стороны получения (все элементы доступны в params).
Пример:
<%= form_for(@post, remote: true) do |f| %> ... <% end %>
Сгенерированный HTML для этого будет:
<form action='http://www.example.com' method='post' data-remote='true'> <input name='_method' type='hidden' value='patch' /> ... </form>
Установка HTML-параметров
Вы можете задавать атрибуты данных непосредственно, передавая хэш данных, но все остальные HTML-параметры должны быть заключены в ключ HTML. Пример:
<%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %>
...
<% end %> Сгенерированный HTML для этого будет:
<form action='http://www.example.com' method='post' data-behavior='autosave' name='go'> <input name='_method' type='hidden' value='patch' /> ... </form>
Удаление скрытых id модели
Метод #form_for автоматически включает в форму скрытое поле с идентификатором модели. Это используется для сохранения корреляции между данными формы и связанной моделью. Некоторые ORM-системы не используют идентификаторы вложенных моделей, поэтому в этом случае вам нужно отключить скрытый идентификатор.
В следующем примере модель Post имеет множество комментариев, хранящихся в ней в NoSQL-базе данных, поэтому для комментариев нет первичного ключа.
Пример:
<%= form_for(@post) do |f| %>
<%= f.fields_for(:comments, include_id: false) do |cf| %>
...
<% end %>
<% end %> Настраиваемые билдеры форм
Вы также можете создавать формы с помощью настраиваемого класса FormBuilder. Подклассируйте FormBuilder и переопределите или определите дополнительные помощники, а затем используйте свой пользовательский билдер. Например, предположим, что вы создали помощник для автоматического добавления меток к полям формы.
<%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<%= f.text_area :biography %>
<%= f.check_box :admin %>
<%= f.submit %>
<% end %> В этом случае, если вы используете это:
<%= render f %>
Отрендеренный шаблон — people/_labelling_form, а локальная переменная, ссылающаяся на билдер формы, называется labelling_form.
Пользовательский класс FormBuilder автоматически объединяется с параметрами вложенного вызова #fields_for, если не указано иное.
Во многих случаях вы захотите обернуть это в другой помощник, поэтому вы можете сделать что-то вроде следующего:
def labelled_form_for(record_or_name_or_array, *args, &block) options = args.extract_options! form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block) end
Если вам не нужно привязывать форму к экземпляру модели, ознакомьтесь с ActionView::Helpers::FormTagHelper#form_tag.
Форма для внешних ресурсов
При создании форм для внешних ресурсов иногда вам нужно установить маркер аутентификации или просто отобразить форму без него, например, при отправке данных на платежный шлюз с ограниченным числом и типами полей.
Чтобы установить маркер аутентификации, вам необходимо передать параметр :authenticity_token
<%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| ... <% end %>
Если вы не хотите отображать поле маркера аутентификации вообще, просто передайте false:
<%= form_for @invoice, url: external_url, authenticity_token: false do |f| ... <% end %>
Возвращает скрытый тег input, настроенный для доступа к указанному атрибуту (идентифицированному по method) объекта, назначенного шаблону (идентифицированному по object). Дополнительные параметры тега input могут быть переданы в виде хэша с options. Эти параметры будут добавлены в HTML в качестве атрибутов HTML-элемента, как показано в примере.
Примеры
hidden_field(:signup, :pass_confirm)
# => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
hidden_field(:post, :tag_list)
# => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
hidden_field(:user, :token)
# => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 771 def label(object_name, method, content_or_options = nil, options = nil, &block) Tags::Label.new(object_name, method, self, content_or_options, options).render(&block) end
Возвращает тег label, настроенный для подписи поля ввода для указанного атрибута (идентифицированного по method) объекта, назначенного шаблону (идентифицированному по object). Текст метки по умолчанию устанавливается в имя атрибута, если нет перевода в текущем локали I18n (через helpers.label.<modelname>.<attribute>) или если вы явно его указываете. Дополнительные параметры тега label можно передать в виде хэша с options. Эти параметры будут добавлены в HTML в качестве атрибутов HTML-элемента, как в показанном примере, за исключением параметра :value, который предназначен для меток для тегов #radio_button (где значение используется в идентификаторе тега ввода).
Примеры
label(:post, :title) # => <label for="post_title">Title</label>
Вы можете локализовать свои метки на основе имени модели и атрибута. Например, вы можете определить следующее в вашем локали (например, en.yml):
helpers:
label:
post:
body: "Write your entire text here" Что затем приведет к:
label(:post, :body) # => <label for="post_body">Write your entire text here</label>
Локализация также может быть основана исключительно на переводе имени атрибута (если вы используете ActiveRecord):
activerecord:
attributes:
post:
cost: "Total cost"
label(:post, :cost)
# => <label for="post_cost">Total cost</label>
label(:post, :title, "A short title")
# => <label for="post_title">A short title</label>
label(:post, :title, "A short title", class: "title_label")
# => <label for="post_title" class="title_label">A short title</label>
label(:post, :privacy, "Public Post", value: "public")
# => <label for="post_privacy_public">Public Post</label>
label(:post, :terms) do
raw('Accept <a href="/terms">Terms</a>.')
end
# => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label> # File actionview/lib/action_view/helpers/form_helper.rb, line 1121
def month_field(object_name, method, options = {})
Tags::MonthField.new(object_name, method, self, options).render
end Возвращает #text_field типа “month”.
month_field("user", "born_on")
# => <input id="user_born_on" name="user[born_on]" type="month" />
Значение по умолчанию генерируется путем попытки вызова strftime с “%Y-%m” для значения объекта, что делает его ожидаемым для экземпляров DateTime и ActiveSupport::TimeWithZone.
@user.born_on = Date.new(1984, 1, 27)
month_field("user", "born_on")
# => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1164
def number_field(object_name, method, options = {})
Tags::NumberField.new(object_name, method, self, options).render
end Возвращает тег ввода типа “number”.
Параметры
-
Поддерживает те же параметры, что и number_field_tag
# File actionview/lib/action_view/helpers/form_helper.rb, line 813
def password_field(object_name, method, options = {})
Tags::PasswordField.new(object_name, method, self, options).render
end Возвращает тег ввода типа “password”, настроенный для доступа к указанному атрибуту (идентифицированному по method) объекта, назначенного шаблону (идентифицированному по object). Дополнительные параметры тега ввода могут быть переданы в виде хэша с options. Эти параметры будут добавлены в HTML в качестве атрибутов HTML-элемента, как показано в примере. По соображениям безопасности поле по умолчанию пустое; передайте значение через options, если это не нужно.
Примеры
password_field(:login, :pass, size: 20)
# => <input type="password" id="login_pass" name="login[pass]" size="20" />
password_field(:account, :secret, class: "form_input", value: @account.secret)
# => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
password_field(:user, :password, onchange: "if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }")
# => <input type="password" id="user_password" name="user[password]" onchange="if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }"/>
password_field(:account, :pin, size: 20, class: 'form_input')
# => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
является псевдонимом для #telephone_field
# File actionview/lib/action_view/helpers/form_helper.rb, line 972
def radio_button(object_name, method, tag_value, options = {})
Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end Возвращает тег радиокнопки для доступа к указанному атрибуту (идентифицированному по method) объекта, назначенного шаблону (идентифицированному по object). Если текущее значение method равно tag_value, радиокнопка будет отмечена.
Чтобы принудительно установить флажок радиокнопки, передайте checked: true в хэше options. Вы также можете передать HTML-параметры.
# Let's say that @post.category returns "rails":
radio_button("post", "category", "rails")
radio_button("post", "category", "java")
# => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
# <input type="radio" id="post_category_java" name="post[category]" value="java" />
radio_button("user", "receive_newsletter", "yes")
radio_button("user", "receive_newsletter", "no")
# => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
# <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1172
def range_field(object_name, method, options = {})
Tags::RangeField.new(object_name, method, self, options).render
end Возвращает тег ввода типа “range”.
Параметры
-
Поддерживает те же параметры, что и range_field_tag
# File actionview/lib/action_view/helpers/form_helper.rb, line 1003
def search_field(object_name, method, options = {})
Tags::SearchField.new(object_name, method, self, options).render
end Возвращает тег ввода типа “search” для доступа к указанному атрибуту (идентифицированному по method) объекта, назначенного шаблону (идентифицированному по object_name). В некоторых браузерах элементы ввода типа “search” могут иметь различный стиль.
search_field(:user, :name) # => <input id="user_name" name="user[name]" type="search" /> search_field(:user, :name, autosave: false) # => <input autosave="false" id="user_name" name="user[name]" type="search" /> search_field(:user, :name, results: 3) # => <input id="user_name" name="user[name]" results="3" type="search" /> # Assume request.host returns "www.example.com" search_field(:user, :name, autosave: true) # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" /> search_field(:user, :name, onsearch: true) # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> search_field(:user, :name, autosave: false, onsearch: true) # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> search_field(:user, :name, autosave: true, onsearch: true) # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1012
def telephone_field(object_name, method, options = {})
Tags::TelField.new(object_name, method, self, options).render
end Возвращает #text_field типа “tel”.
telephone_field("user", "phone")
# => <input id="user_phone" name="user[phone]" type="tel" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 891
def text_area(object_name, method, options = {})
Tags::TextArea.new(object_name, method, self, options).render
end Возвращает тег textarea, настроенный для доступа к указанному атрибуту (идентифицированному по method) объекта, назначенного шаблону (идентифицированному по object). Дополнительные параметры тега ввода могут быть переданы в виде хэша с options.
Примеры
text_area(:post, :body, cols: 20, rows: 40)
# => <textarea cols="20" rows="40" id="post_body" name="post[body]">
# #{@post.body}
# </textarea>
text_area(:comment, :text, size: "20x30")
# => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
# #{@comment.text}
# </textarea>
text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input')
# => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
# #{@application.notes}
# </textarea>
text_area(:entry, :body, size: "20x20", disabled: 'disabled')
# => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
# #{@entry.body}
# </textarea>
# File actionview/lib/action_view/helpers/form_helper.rb, line 792
def text_field(object_name, method, options = {})
Tags::TextField.new(object_name, method, self, options).render
end Возвращает тег ввода типа “text”, настроенный для доступа к указанному атрибуту (идентифицированному по method) объекта, назначенного шаблону (идентифицированному по object). Дополнительные параметры тега ввода могут быть переданы в виде хэша с options. Эти параметры будут добавлены в HTML в качестве атрибутов HTML-элемента, как в показанном примере.
Примеры
text_field(:post, :title, size: 20)
# => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
text_field(:post, :title, class: "create_input")
# => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
# => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
text_field(:snippet, :code, size: 20, class: 'code_input')
# => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1073
def time_field(object_name, method, options = {})
Tags::TimeField.new(object_name, method, self, options).render
end Возвращает #text_field типа “time”.
Значение по умолчанию генерируется путем попытки вызова strftime с “%T.%L” для значения объекта. Можно переопределить это, передав параметр “value”.
Параметры
-
Поддерживает те же параметры, что и time_field_tag
Пример
time_field("task", "started_at")
# => <input id="task_started_at" name="task[started_at]" type="time" />
Вы можете задать значения атрибутов “min” и “max”, передав экземпляры Date или Time в хэш параметров.
time_field("task", "started_at", min: Time.now)
# => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
В качестве альтернативы вы можете передать строку, отформатированную как ISO8601 время, в качестве значений для “min” и “max”.
time_field("task", "started_at", min: "01:00:00")
# => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1147
def url_field(object_name, method, options = {})
Tags::UrlField.new(object_name, method, self, options).render
end Возвращает #text_field типа “url”.
url_field("user", "homepage")
# => <input id="user_homepage" name="user[homepage]" type="url" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 1138
def week_field(object_name, method, options = {})
Tags::WeekField.new(object_name, method, self, options).render
end Возвращает #text_field типа “week”.
week_field("user", "born_on")
# => <input id="user_born_on" name="user[born_on]" type="week" />
Значение по умолчанию генерируется путем вызова strftime с “%Y-W%W” на значении объекта, что обеспечивает ожидаемое поведение для экземпляров DateTime и ActiveSupport::TimeWithZone.
@user.born_on = Date.new(1984, 5, 12)
week_field("user", "born_on")
# => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" />
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.