使用 fields_for
帮助方法也可创建类似的绑定,但不会生成 <form>
标签。在同一表单中编辑多个模型对象时经常使用 fields_for
方法。例如,有个 Person
模型,和 ContactDetail
模型关联,编写如下的表单可以同时创建两个模型的对象:
<%=
form_for @person ,
url: {action: "create" }
do
|person_form| %> <%=
person_form.text_field :name
%> <%=
fields_for @person .contact_detail
do
|contact_details_form| %> <%=
contact_details_form.text_field :phone_number
%> <%
end
%> <%
end
%> |
生成的 HTML 如下:
< form
accept-charset = "UTF-8"
action = "/people/create"
class = "new_person"
id = "new_person"
method = "post" > < input
id = "person_name"
name = "person[name]"
type = "text"
/> < input
id = "contact_detail_phone_number"
name = "contact_detail[phone_number]"
type = "text"
/> </ form > |
fields_for
方法拽入的对象和 form_for
方法一样,都是表单构造器(其实在代码内部 form_for
会调用 fields_for
方法)。
从前几节可以看出,表单提交的数据可以直接保存在 params
Hash
中,或者嵌套在子 Hash 中。例如,在 Person
模型对应控制器的 create
动作中,params[:person]
一般是一个
Hash,保存创建 Person
实例的所有属性。params
Hash
中也可以保存数组,或由 Hash 组成的数组,等等。
HTML 表单基本上不能处理任何结构化数据,提交的只是由普通的字符串组成的键值对。在程序中使用的数组参数和 Hash 参数是通过 Rails 的参数命名约定生成的。
如果想快速试验本节中的示例,可以在控制台中直接调用 Rack 的参数解析器。例如: T> rubyTIP:
Rack::Utils.parse_query "name=fred&phone=0123456789"TIP: # => {"name"=>"fred", "phone"=>"0123456789"}TIP:
7.1 基本结构
数组和 Hash 是两种基本结构。获取 Hash 中值的方法和 params
一样。如果表单中包含以下控件:
< input
id = "person_name"
name = "person[name]"
type = "text"
value = "Henry" /> |
得到的 params
值为:
{'person'
=> {'name' => 'Henry'}} |
在控制器中可以使用 params[:person][:name]
获取提交的值。
Hash 可以随意嵌套,不限制层级,例如:
< input
id = "person_address_city"
name = "person[address][city]"
type = "text"
value = "New
York" /> |
得到的 params
值为:
{ 'person'
=> { 'address'
=> { 'city'
=> 'New
York' }}} |
一般情况下 Rails 会忽略重复的参数名。如果参数名中包含空的方括号([]
),Rails
会将其组建成一个数组。如果想让用户输入多个电话号码,在表单中可以这么做:
< input
name = "person[phone_number][]"
type = "text" /> < input
name = "person[phone_number][]"
type = "text" /> < input
name = "person[phone_number][]"
type = "text" /> |
得到的 params[:person][:phone_number]
就是一个数组。
7.2 结合在一起使用
上述命名约定可以结合起来使用,让 params
的某个元素值为数组(如前例),或者由
Hash 组成的数组。例如,使用下面的表单控件可以填写多个地址:
< input
name = "addresses[][line1]"
type = "text" /> < input
name = "addresses[][line2]"
type = "text" /> < input
name = "addresses[][city]"
type = "text" /> |
得到的 params[:addresses]
值是一个由
Hash 组成的数组,Hash 中的键包括 line1
、line2
和city
。如果
Rails 发现输入框的 name
属性值已经存在于当前
Hash 中,就会新建一个 Hash。
不过有个限制,虽然 Hash 可以嵌套任意层级,但数组只能嵌套一层。如果需要嵌套多层数组,可以使用 Hash 实现。例如,如果想创建一个包含模型对象的数组,可以创建一个 Hash,以模型对象的 ID、数组索引或其他参数为键。
数组类型参数不能很好的在 check_box
帮助方法中使用。根据
HTML 规范,未选中的复选框不应该提交值。但是不管是否选中都提交值往往更便于处理。为此 check_box
方法额外创建了一个同名的隐藏 input
元素。如果没有选中复选框,只会提交隐藏 input
元素的值,如果选中则同时提交两个值,但复选框的值优先级更高。处理数组参数时重复提交相同的参数会让
Rails 迷惑,因为对 Rails 来说,见到重复的 input
值,就会创建一个新数组元素。所以更推荐使用 check_box_tag
方法,或者用
Hash 代替数组。
7.3 使用表单帮助方法
前面几节并没有使用 Rails 提供的表单帮助方法。你可以自己创建 input
元素的 name
属性,然后直接将其传递给 text_field_tag
等帮助方法。但是
Rails 提供了更高级的支持。本节介绍 form_for
和 fields_for
方法的 name
参数以及 :index
选项。
你可能会想编写一个表单,其中有很多字段,用于编辑某人的所有地址。例如:
<%=
form_for @person
do
|person_form| %> <%=
person_form.text_field :name
%> <%
@person .addresses. each
do
|address| %> <%=
person_form.fields_for address, index: address.id do
|address_form| %> <%=
address_form.text_field :city
%> <%
end
%> <%
end
%> <%
end
%> |
假设这个人有两个地址,ID 分别为 23 和 45。那么上述代码生成的 HTML 如下:
< form
accept-charset = "UTF-8"
action = "/people/1"
class = "edit_person"
id = "edit_person_1"
method = "post" > < input
id = "person_name"
name = "person[name]"
type = "text"
/> < input
id = "person_address_23_city"
name = "person[address][23][city]"
type = "text"
/> < input
id = "person_address_45_city"
name = "person[address][45][city]"
type = "text"
/> </ form > |
得到的 params
Hash
如下:
{ 'person'
=> { 'name'
=> 'Bob' ,
'address'
=> { '23'
=> { 'city'
=> 'Paris' },
'45'
=> { 'city'
=> 'London' }}}} |
Rails 之所以知道这些输入框中的值是 person
Hash
的一部分,是因为我们在第一个表单构造器上调用了 fields_for
方法。指定 :index
选项的目的是告诉
Rails,其中的输入框 name
属性值不是 person[address][city]
,而要在 address
和 city
索引之间插入 :index
选项对应的值(放入方括号中)。这么做很有用,因为便于分辨要修改的 Address
记录是哪个。:index
选项的值可以是具有其他意义的数字、字符串,甚至是 nil
(此时会新建一个数组参数)。
如果想创建更复杂的嵌套,可以指定 name
属性的第一部分(前例中的 person[address]
):
<%=
fields_for 'person[address][primary]' ,
address, index: address do
|address_form| %> <%=
address_form.text_field :city
%> <%
end
%> |
生成的 HTML 如下:
< input
id = "person_address_primary_1_city"
name = "person[address][primary][1][city]"
type = "text"
value = "bologna"
/> |
一般来说,最终得到的 name
属性值是 fields_for
或 form_for
方法的第一个参数加 :index
选项的值再加属性名。:index
选项也可直接传给 text_field
等帮助方法,但在表单构造器中指定可以避免代码重复。
为了简化句法,还可以不使用 :index
选项,直接在第一个参数后面加上 []
。这么做和指定 index:
address
选项的作用一样,因此下面这段代码
<%=
fields_for 'person[address][primary][]' ,
address do
|address_form| %> <%=
address_form.text_field :city
%> <%
end
%> |
生成的 HTML 和前面一样。