编写嵌套表单

很多程序已经复杂到在一个表单中编辑一个对象已经无法满足需求了。例如,创建 Person 对象时还想让用户在同一个表单中创建多个地址(家庭地址,工作地址,等等)。以后编辑这个 Person 时,还想让用户根据需要添加、删除或修改地址。

9.1 设置模型

Active Record 为此种需求在模型中提供了支持,通过 accepts_nested_attributes_for 方法实现:

class Person < ActiveRecord::Base
   has_many :addresses
   accepts_nested_attributes_for :addresses
end
 
class Address < ActiveRecord::Base
   belongs_to :person
end

这段代码会在 Person 对象上创建 addresses_attributes= 方法,用于创建、更新和删除地址(可选操作)。

9.2 嵌套表单

使用下面的表单可以创建 Person 对象及其地址:

<%= form_for @person do |f| %>
   Addresses:
   < ul >
     <%= f.fields_for :addresses do |addresses_form| %>
       < li >
         <%= addresses_form.label :kind %>
         <%= addresses_form.text_field :kind %>
 
         <%= addresses_form.label :street %>
         <%= addresses_form.text_field :street %>
         ...
       </ li >
     <% end %>
   </ ul >
<% end %>

如果关联支持嵌套属性,fields_for 方法会为关联中的每个元素执行一遍代码块。如果没有地址,就不执行代码块。一般的作法是在控制器中构建一个或多个空的子属性,这样至少会有一组字段显示出来。下面的例子会在新建 Person 对象的表单中显示两组地址字段。

def new
   @person = Person. new
   2 .times { @person .addresses.build}
end

fields_for 方法拽入一个表单构造器,参数的名字就是 accepts_nested_attributes_for 方法期望的。例如,如果用户填写了两个地址,提交的参数如下:

{
   'person' => {
     'name' => 'John Doe' ,
     'addresses_attributes' => {
       '0' => {
         'kind' => 'Home' ,
         'street' => '221b Baker Street'
       },
       '1' => {
         'kind' => 'Office' ,
         'street' => '31 Spooner Street'
       }
     }
   }
}

:addresses_attributes Hash 的键是什么不重要,但至少不能相同。

如果关联的对象已经存在于数据库中,fields_for 方法会自动生成一个隐藏字段,value 属性的值为记录的 id。把 include_id: false 选项传递给 fields_for 方法可以禁止生成这个隐藏字段。如果自动生成的字段位置不对,导致 HTML 无法通过验证,或者在 ORM 关系中子对象不存在 id 字段,就可以禁止自动生成这个隐藏字段。

9.3 控制器端

像往常一样,参数传递给模型之前,在控制器中要过滤参数

def create
   @person = Person. new (person_params)
   # ...
end
 
private
   def person_params
     params.require( :person ).permit( :name , addresses_attributes: [ :id , :kind , :street ])
   end
9.4 删除对象

如果允许用户删除关联的对象,可以把 allow_destroy: true 选项传递给 accepts_nested_attributes_for 方法:

class Person < ActiveRecord::Base
   has_many :addresses
   accepts_nested_attributes_for :addresses , allow_destroy: true
end

如果属性组成的 Hash 中包含 _destroy 键,且其值为 1 或 true,就会删除对象。下面这个表单允许用户删除地址:

<%= form_for @person do |f| %>
   Addresses:
   < ul >
     <%= f.fields_for :addresses do |addresses_form| %>
       < li >
         <%= addresses_form.check_box :_destroy %>
         <%= addresses_form.label :kind %>
         <%= addresses_form.text_field :kind %>
         ...
       </ li >
     <% end %>
   </ ul >
<% end %>

别忘了修改控制器中的参数白名单,允许使用 _destroy

def person_params
   params.require( :person ).
     permit( :name , addresses_attributes: [ :id , :kind , :street , :_destroy])
end
9.5 避免创建空记录

如果用户没有填写某些字段,最好将其忽略。此功能可以通过 accepts_nested_attributes_for方法的 :reject_if 选项实现,其值为 Proc 对象。这个 Proc 对象会在通过表单提交的每一个属性 Hash 上调用。如果返回值为 false,Active Record 就不会为这个 Hash 构建关联对象。下面的示例代码只有当 kind 属性存在时才尝试构建地址对象:

class Person < ActiveRecord::Base
   has_many :addresses
   accepts_nested_attributes_for :addresses , reject_if: lambda {|attributes| attributes[ 'kind' ].blank?}
end

为了方便,可以把 reject_if 选项的值设为 :all_blank,此时创建的 Proc 会拒绝为 _destroy之外其他属性都为空的 Hash 构建对象。

9.6 按需添加字段

我们往往不想事先显示多组字段,而是当用户点击“添加新地址”按钮后再显示。Rails 并没有内建这种功能。生成新的字段时要确保关联数组的键是唯一的,一般可在 JavaScript 中使用当前时间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值