класс ActionView::Helpers::FormBuilder
Объект FormBuilder связан с конкретным объектом модели и позволяет генерировать поля, связанные с этим объектом модели. Объект FormBuilder передаётся в качестве результата при использовании form_for или fields_for. Например:
<%= form_for @person do |person_form| %> Name: <%= person_form.text_field :name %> Admin: <%= person_form.check_box :admin %> <% end %>
В данном блоке объект FormBuilder передаётся как переменная person_form. Это позволяет генерировать поля text_field и check_box путём вызова соответствующих методов, которые изменяют шаблон и связывают объект модели +@person+ с формой.
Объект FormBuilder можно рассматривать как прокси для методов в модуле FormHelper. Однако этот класс позволяет вызывать методы с объектом модели, для которого создаётся форма.
Вы можете создавать собственные пользовательские шаблоны FormBuilder, наследуя этот класс. Например:
class MyFormBuilder < ActionView::Helpers::FormBuilder
def div_radio_button(method, tag_value, options = {})
@template.content_tag(:div,
@template.radio_button(
@object_name, method, tag_value, objectify_options(options)
)
)
end
end
Вышеприведённый код создаёт новый метод div_radio_button, который обёртывает новый радиокнопку в div. Обратите внимание, что при передаче опций необходимо вызвать objectify_options, чтобы объект модели был правильно передан методу. Если objectify_options не вызывается, новый помощник не будет связан с моделью.
Теперь код div_radio_button из примера может быть использован следующим образом:
<%= form_for @person, :builder => MyFormBuilder do |f| %> I am a child: <%= f.div_radio_button(:admin, "child") %> I am an adult: <%= f.div_radio_button(:admin, "adult") %> <% end -%>
Стандартный набор методов-помощников для создания форм находится в атрибуте класса field_helpers.
Атрибуты
Публичные методы класса
# File actionview/lib/action_view/helpers/form_helper.rb, line 1661 def self._to_partial_path @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "") end
# File actionview/lib/action_view/helpers/form_helper.rb, line 1673
def initialize(object_name, object, template, options)
@nested_child_index = {}
@object_name, @object, @template, @options = object_name, object, template, options
@default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
convert_to_legacy_options(@options)
if @object_name.to_s.match(/\[\]$/)
if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
@auto_index = object.to_param
else
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
end
end
@multipart = nil
@index = options[:index] || options[:child_index]
end Публичные методы экземпляра
# File actionview/lib/action_view/helpers/form_helper.rb, line 2259
def button(value = nil, options = {}, &block)
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
@template.button_tag(value, options, &block)
end Добавляет кнопку отправки для заданной формы. Если значение не указано, проверяется, является ли объект новым ресурсом, чтобы создать соответствующую метку:
<%= form_for @post do |f| %> <%= f.button %> <% end %>
В примере выше, если @post — новый элемент, используется метка «Создать пост», в противном случае — «Обновить пост».
Эти метки могут быть настраиваются с помощью I18n, в ключе helpers.submit (таким же, как помощник submit) и принимают %{model} в качестве интерполяции перевода:
en:
helpers:
submit:
create: "Create a %{model}"
update: "Confirm changes to %{model}" Также ищет ключ, специфичный для данного объекта:
en:
helpers:
submit:
post:
create: "Add %{model}" Примеры
button("Create post")
# => <button name='button' type='submit'>Create post</button>
button do
content_tag(:strong, 'Ask me!')
end
# => <button name='button' type='submit'>
# <strong>Ask me!</strong>
# </button>
# File actionview/lib/action_view/helpers/form_helper.rb, line 2101
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
end Возвращает тег checkbox, настроенный для доступа к заданному атрибуту (определённому method) объекта, назначенного шаблону (определённому object). Этот объект должен быть объектом экземпляра (@object), а не локальным объектом. Предполагается, что method возвращает целое число, и если это целое число больше нуля, то checkbox отмечен. Дополнительные опции тега input могут быть переданы в виде хэша с options. Значение checked_value по умолчанию равно 1, а значение unchecked_value по умолчанию равно 0, что удобно для булевых значений.
Важно
Спецификация HTML гласит, что неотмеченные checkboxes не отправляются, и поэтому веб-браузеры их не отправляют. К сожалению, это приводит к проблеме: если модель Invoice имеет флаг paid, и в форме редактирования платного счета пользователь сбрасывает флажок, параметр paid не отправляется. Таким образом, любой idiom массового назначения, например
@invoice.update(params[:invoice])
не обновит флаг.
Чтобы предотвратить это, помощник генерирует вспомогательное скрытое поле перед самим check box. Скрытое поле имеет то же имя, а его атрибуты имитируют неотмеченный check box.
Таким образом, клиент отправляет либо только скрытое поле (представляющее неотмеченный check box), либо оба поля. Поскольку спецификация HTML гласит, что пары ключ/значение должны отправляться в том же порядке, в котором они появляются в форме, а извлечение параметров получает последнее вхождение любого повторяющегося ключа в строке запроса, это работает для обычных форм.
К сожалению, этот обходной путь не работает, когда check box находится внутри массивоподобного параметра, как в
<%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> <%= form.check_box :paid %> ... <% end %>
потому что Rails пытается отличить элементы массива именно по повторению имён параметров. Для каждого элемента с отмеченным check box вы получаете дополнительный элемент-призрак с только этим атрибутом, присвоенным «0».
В этом случае предпочтительнее использовать check_box_tag или использовать хэши вместо массивов.
# Let's say that @post.validated? is 1:
check_box("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("gooddog", {}, "yes", "no")
# => <input name="puppy[gooddog]" type="hidden" value="no" />
# <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
# Let's say that @eula.accepted is "no":
check_box("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 1945
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
fields_options[:namespace] = options[:namespace]
fields_options[:parent_builder] = self
case record_name
when String, Symbol
if nested_attributes_association?(record_name)
return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
end
else
record_object = record_name.is_a?(Array) ? record_name.last : record_name
record_name = model_name_from_record_or_class(record_object).param_key
end
object_name = @object_name
index = if options.has_key?(:index)
options[:index]
elsif defined?(@auto_index)
object_name = object_name.to_s.sub(/\[\]$/, "")
@auto_index
end
record_name = if index
"#{object_name}[#{index}][#{record_name}]"
elsif record_name.to_s.end_with?("[]")
record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
"#{object_name}#{record_name}"
else
"#{object_name}[#{record_name}]"
end
fields_options[:child_index] = index
@template.fields_for(record_name, record_object, fields_options, &block)
end Создаёт область действия вокруг определённого объекта модели, как form_for, но не создаёт теги формы сами по себе. Это делает #fields_for подходящим для указания дополнительных объектов модели в той же форме.
Несмотря на сходство в использовании и назначении с fields_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 и отвечает на метод записи 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 как коллекцию, и для правильного установления индексов в разметке формы.
Если проекты уже являются ассоциацией в 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 %> Когда используется коллекция, вы можете захотеть знать индекс каждого объекта в массиве. Для этой цели доступен метод index в объекте FormBuilder.
<%= 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 2183
def file_field(method, options = {})
self.multipart = true
@template.file_field(@object_name, method, objectify_options(options))
end Возвращает тег ввода для загрузки файлов, настроенный для доступа к указанному атрибуту (определяемому method) объекта, присвоенного шаблону (определяемому object). Дополнительные опции для тега ввода можно передать в виде хэша с options. Эти опции будут добавлены в HTML в качестве атрибутов элемента HTML, как показано в примере.
Использование этого метода внутри блока form_for установит кодировку окружающей формы на multipart/form-data.
Опции
-
Создаёт стандартные атрибуты HTML для тега.
-
:disabled— Если установлено в true, пользователь не сможет использовать этот элемент ввода. -
:multiple— Если установлено в true, *в большинстве современных браузеров* пользователю будет разрешено выбирать несколько файлов. -
:accept— Если установлено на один или несколько типов MIME, пользователю будет предложен фильтр при выборе файла. Вам всё равно нужно настроить валидацию модели.
Примеры
# Let's say that @user has avatar: file_field(:avatar) # => <input type="file" id="user_avatar" name="user[avatar]" /> # Let's say that @post has image: file_field(:image, :multiple => true) # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" /> # Let's say that @post has attached: file_field(:attached, accept: 'text/html') # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> # Let's say that @post has image: file_field(:image, accept: 'image/png,image/gif,image/jpeg') # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> # Let's say that @attachment has file: file_field(:file, class: 'file_input') # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
# File actionview/lib/action_view/helpers/form_options_helper.rb, line 840
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
@template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
end Обертывает ActionView::Helpers::FormOptionsHelper#grouped_collection_select для формообразующих элементов:
<%= form_for @city do |f| %> <%= f.grouped_collection_select :country_id, @continents, :countries, :name, :id, :name %> <%= f.submit %> <% end %>
Для получения подробной информации обратитесь к документации основного помощника.
Возвращает тег скрытого элемента ввода, настроенный для доступа к указанному атрибуту (определяемому method) объекта, присвоенного шаблону (определяемому object). Дополнительные опции для тега ввода можно передать в виде хэша с options. Эти опции будут добавлены в HTML в качестве атрибутов элемента HTML, как показано в примере.
Примеры
# Let's say that @signup.pass_confirm returns true: hidden_field(:pass_confirm) # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" /> # Let's say that @post.tag_list returns "blog, ruby": hidden_field(:tag_list) # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" /> # Let's say that @user.token returns "abcde": hidden_field(:token) # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
# File actionview/lib/action_view/helpers/form_helper.rb, line 2040
def label(method, text = nil, options = {}, &block)
@template.label(@object_name, method, text, objectify_options(options), &block)
end Возвращает тег метки, настроенный для подписи поля ввода для указанного атрибута (определённого как method) объекта, назначенного шаблону (определённого как object). Текст метки по умолчанию будет именем атрибута, если перевод не найден в текущем локали I18n (через helpers.label.<modelname>.<attribute>) или вы не указали его явно. Дополнительные опции тега метки могут быть переданы в виде хэша с options. Эти опции будут добавлены в HTML как атрибуты HTML-элемента, как показано в примере, за исключением опции :value, предназначенной для целевых меток для тегов #radio_button (где значение используется в идентификаторе тега ввода).
Примеры
label(:title) # => <label for="post_title">Title</label>
Вы можете локализовать свои метки на основе модели и имён атрибутов. Например, вы можете определить следующее в своём локали (например, en.yml)
helpers:
label:
post:
body: "Write your entire text here" Что затем приведёт к
label(:body) # => <label for="post_body">Write your entire text here</label>
Локализация также может быть основана исключительно на переводе имени атрибута (если вы используете ActiveRecord):
activerecord:
attributes:
post:
cost: "Total cost"
label(:cost)
# => <label for="post_cost">Total cost</label>
label(:title, "A short title")
# => <label for="post_title">A short title</label>
label(:title, "A short title", class: "title_label")
# => <label for="post_title" class="title_label">A short title</label>
label(:privacy, "Public Post", value: "public")
# => <label for="post_privacy_public">Public Post</label>
label(: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 1653
def multipart=(multipart)
@multipart = multipart
if parent_builder = @options[:parent_builder]
parent_builder.multipart = multipart
end
end # File actionview/lib/action_view/helpers/form_helper.rb, line 2123
def radio_button(method, tag_value, options = {})
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
end Возвращает тег радиокнопки для доступа к указанному атрибуту (определённому как method) объекта, назначенного шаблону (определённого как object). Если текущее значение method равно tag_value, радиокнопка будет отмечена.
Для принудительного выбора радиокнопки передайте checked: true в хэш options. Вы также можете передавать HTML-опции.
# Let's say that @post.category returns "rails":
radio_button("category", "rails")
radio_button("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" />
# Let's say that @user.receive_newsletter returns "no":
radio_button("receive_newsletter", "yes")
radio_button("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_options_helper.rb, line 816
def select(method, choices = nil, options = {}, html_options = {}, &block)
@template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block)
end Оборачивает ActionView::Helpers::FormOptionsHelper#select для билдеров форм:
<%= form_for @post do |f| %>
<%= f.select :person_id, Person.all.collect { |p| [ p.name, p.id ] }, include_blank: true %>
<%= f.submit %>
<% end %> Для получения подробностей обратитесь к документации базового помощника.
# File actionview/lib/action_view/helpers/form_helper.rb, line 2215
def submit(value = nil, options = {})
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
@template.submit_tag(value, options)
end Добавляет кнопку отправки для данной формы. Если значение не указано, проверяется, является ли объект новым ресурсом, чтобы создать соответствующую метку:
<%= form_for @post do |f| %> <%= f.submit %> <% end %>
В приведенном выше примере, если @post — новый запис, будет использоваться «Создать пост», в противном случае — «Обновить пост».
Эти метки можно настроить с помощью I18n, в ключе helpers.submit, и принять %{model} в качестве интерполяции перевода:
en:
helpers:
submit:
create: "Create a %{model}"
update: "Confirm changes to %{model}" Также ищется ключ, специфичный для данного объекта:
en:
helpers:
submit:
post:
create: "Add %{model}" # File actionview/lib/action_view/helpers/form_options_helper.rb, line 852
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end Оборачивает ActionView::Helpers::FormOptionsHelper#time_zone_select для билдеров форм:
<%= form_for @user do |f| %> <%= f.time_zone_select :time_zone, nil, include_blank: true %> <%= f.submit %> <% end %>
Для получения подробностей обратитесь к документации базового помощника.
# File actionview/lib/action_view/helpers/form_helper.rb, line 1669 def to_model self end
# File actionview/lib/action_view/helpers/form_helper.rb, line 1665 def to_partial_path self.class._to_partial_path end
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.