Nested Attributes 嵌套属性
提供类方法 accepts_nested_attributes_for(*attr_names)
attr_names 由:一个或多个属性(association_name) 和 一个或多个可选参数(option)组成。
只接受 options:
:allow_destroy
:reject_if
:limit
:update_only
:limit
在单个 record 构建时不执行;指量构建时,在校验之前执行,并且超过限制的话直接抛错误。实际项目中,不推荐使用。
当你声明嵌套属性时,Rails 会自动帮你定义属性的写方法。
# 摘录部分代码
def #{association_name}_attributes=(attributes)
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
end
association_name
就是你声明的属性,例如:
class Book < ActiveRecord::Base
has_one :author
has_many :pages
accepts_nested_attributes_for :author, :pages
end
生成 author_attributes=(attributes)
和 pages_attributes=(attributes)
对于关联对象,会自动设置 :autosave
# 摘录部分代码
reflection.autosave = true # 自动保存
add_autosave_association_callbacks(reflection) # 回调在自动保存时仍然有效
对于嵌套的属性,默认你可以执行写操作,但不能删除它们。
如果你真的要这么做,也可以通过 :allow_destroy
来设置。如:
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar, allow_destroy: true
end
# 然后
member.avatar_attributes = { id: '2', _destroy: '1' }
member.avatar.marked_for_destruction? # => true
member.save
member.reload.avatar # => nil
上面举例是一对一,下面的一对多关系类似:
params = { member: {
name: 'joe', posts_attributes: [
{ title: 'Kari, the awesome Ruby documentation browser!' },
{ title: 'The egalitarian assumption of the modern citizen' },
{ title: '', _destroy: '1' } # this will be ignored
]
}}
member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
自动保存多个嵌套属性,有的可能不符合校验。
为了处理这种情况。你可以设置 :reject_if
:
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: proc do |attributes|
attributes['title'].blank?
end
end
params = { member: {
name: 'joe', posts_attributes: [
{ title: 'Kari, the awesome Ruby documentation browser!' },
{ title: 'The egalitarian assumption of the modern citizen' },
{ title: '' } # this will be ignored because of the :reject_if proc
]
}}
member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
在这里,效果和上面使用 _destroy: '1'
有类似之处。
重现 autosave 创建过程
Book has_many :pages
reflection = Book._reflect_on_association(:pages)
Book.send(:add_autosave_association_callbacks, reflection)
Book.reflect_on_all_autosave_associations
update_only - 解决更新关联对象时的困扰
之前的写法:
# alias.rb
class Alias < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
end
举例:更新关联对象
# 传递 id
Alias.first.user.name
>> "Alice"
Alias.first.update_attributes(
{
:user_attributes => {
:id => 1, # <- 这行
:name => "Bob"
}
})
Alias.first.user.name
>> "Bob"
# 外键用的是传递过来的 id
Alias.first.user_id
>> 1
# 不传递 id
Alias.first.update_attributes(
{
:user_attributes => {
:name => "Bob"
}
})
# 没有传递外键,则会先删除,然后重新创建关联对象
Alias.first.user_id
>> 2
上述情况,虽然文档上已经写明了。但这违背了我们的直觉,建议加上 :update_only
参数。
之后的写法:
# alias.rb
class Alias < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user, update_only: true
end
update_only 仅作用于单一关系,对 collection 使用无效。
当然,除上述方法外,还有解决办法就是: 直接获取,然后操作被关联的对象作。或者,直接通过关联对象进行赋值,然后保存(利用 auto_save 进行自动更新)。
invert_of 的另一个作用 accepts_nested_attributes_for with Has-Many-Through Relations
allow_destroy 选项的使用 【Rails】fields_for と accepts_nested_attributes_for 和此方法配套使用的是 fields_for
方法。
最后更新于
这有帮助吗?