Active Record 嵌套属性
嵌套属性允许你通过父级保存关联记录的属性。默认情况下,嵌套属性更新处于关闭状态,你可以使用 accepts_nested_attributes_for
类方法启用它。当你启用嵌套属性时,将在模型上定义一个属性写入器。
属性写入器以关联命名,这意味着在以下示例中,将向你的模型添加两个新方法
author_attributes=(attributes)
和 pages_attributes=(attributes)
。
class Book < ActiveRecord::Base
has_one :author
has_many :pages
accepts_nested_attributes_for :author, :pages
end
请注意,:autosave
选项会自动启用在每个关联上,其中使用了 accepts_nested_attributes_for
。
一对一
考虑一个具有一个头像的成员模型
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar
end
在一对一关联上启用嵌套属性允许你一次创建成员和头像
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'
它还允许你通过成员更新头像
params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
member.update params[:member]
member.avatar.icon # => 'sad'
如果你想更新当前头像而不提供 id,你必须添加 :update_only
选项。
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar, update_only: true
end
params = { member: { avatar_attributes: { icon: 'sad' } } }
member.update params[:member]
member.avatar.id # => 2
member.avatar.icon # => 'sad'
默认情况下,你只能设置和更新关联模型上的属性。如果你想通过属性哈希销毁关联模型,你必须首先使用 :allow_destroy
选项启用它。
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar, allow_destroy: true
end
现在,当你将 _destroy
键添加到属性哈希中,并使用评估为 true
的值时,你将销毁关联模型
member.avatar_attributes = { id: '2', _destroy: '1' }
member.avatar.marked_for_destruction? # => true
member.save
member.reload.avatar # => nil
请注意,该模型在父级保存之前不会被销毁。
还要注意,除非你在更新的哈希中指定其 id,否则该模型不会被销毁。
一对多
考虑一个具有多个帖子的成员
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
现在,你可以通过成员的属性哈希设置或更新关联帖子的属性:包含键 :posts_attributes
,其值为帖子的属性哈希数组。
对于不具有 id
键的每个哈希,将实例化一个新记录,除非哈希还包含一个 _destroy
键,该键计算为 true
。
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
proc,以静默忽略任何新记录哈希(如果它们未通过你的条件)。例如,前面的示例可以改写为
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
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'
或者,:reject_if
也接受一个用于使用方法的符号
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: :new_record?
end
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: :reject_posts
def reject_posts(attributes)
attributes['title'].blank?
end
end
如果哈希包含与已关联记录匹配的 id
键,则将修改匹配的记录
member.attributes = {
name: 'Joe',
posts_attributes: [
{ id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
{ id: 2, title: '[UPDATED] other post' }
]
}
member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
member.posts.second.title # => '[UPDATED] other post'
但是,如果父模型也在更新,则适用上述内容。例如,如果你想创建一个名为 joe 的 member
,并希望同时更新 posts
,这将产生一个 ActiveRecord::RecordNotFound
错误。
默认情况下,关联记录不受销毁保护。如果你想通过属性哈希销毁任何关联记录,则必须首先使用 :allow_destroy
选项启用它。这将允许你使用 _destroy
键销毁现有记录
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, allow_destroy: true
end
params = { member: {
posts_attributes: [{ id: '2', _destroy: '1' }]
}}
member.attributes = params[:member]
member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
member.posts.length # => 2
member.save
member.reload.posts.length # => 1
关联集合的嵌套属性也可以以哈希哈希的形式传递,而不是哈希数组
Member.create(
name: 'joe',
posts_attributes: {
first: { title: 'Foo' },
second: { title: 'Bar' }
}
)
与
Member.create(
name: 'joe',
posts_attributes: [
{ title: 'Foo' },
{ title: 'Bar' }
]
)
具有相同的效果。在这种情况下,将忽略 :posts_attributes
值的哈希的键。但是,不允许对其中一个键使用 'id'
或 :id
,否则哈希将被包装在一个数组中,并解释为单个帖子的属性哈希。
以哈希哈希的形式传递关联集合的属性可与从 HTTP/HTML 参数生成的哈希一起使用,其中可能没有自然的方法来提交哈希数组。
保存
当保存父模型时,对模型的所有更改(包括标记为要销毁的模型的销毁)都会自动且原子地保存和销毁。这发生在由父级的保存方法启动的事务中。请参阅 ActiveRecord::AutosaveAssociation
。
验证父模型的存在
belongs_to
关联默认验证父模型的存在。你可以通过指定 optional: true
来禁用此行为。例如,在有条件地验证父模型的存在时可以使用此功能
class Veterinarian < ActiveRecord::Base
has_many :patients, inverse_of: :veterinarian
accepts_nested_attributes_for :patients
end
class Patient < ActiveRecord::Base
belongs_to :veterinarian, inverse_of: :patients, optional: true
validates :veterinarian, presence: true, unless: -> { awaiting_intake }
end
请注意,如果你未指定 :inverse_of
选项,则 Active Record 将尝试根据启发式方法自动猜测反向关联。
对于一对一嵌套关联,如果你在分配之前自己构建了新的(内存中)子对象,则此模块不会覆盖它,例如
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar
def avatar
super || build_avatar(width: 200)
end
end
member = Member.new
member.avatar_attributes = {icon: 'sad'}
member.avatar.width # => 200
创建具有嵌套属性的表单
使用 ActionView::Helpers::FormHelper#fields_for
为嵌套属性创建表单元素。
Integration
测试参数应反映表单的结构。例如
post members_path, params: {
member: {
name: 'joe',
posts_attributes: {
'0' => { title: 'Foo' },
'1' => { title: 'Bar' }
}
}
}
常量
REJECT_ALL_BLANK_PROC | = | proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } } |
实例公共方法
accepts_nested_attributes_for(*attr_names) 链接
为指定的关联定义属性写入器。
支持的选项
- :allow_destroy
-
如果为 true,则销毁属性哈希中具有
_destroy
键且值评估为true
(例如 1、‘1’、true 或 ‘true’)的任何成员。此选项默认为 false。 - :reject_if
-
允许您指定一个 Proc 或指向检查是否应为某个属性哈希构建记录的方法的
Symbol
。哈希被传递给提供的 Proc 或方法,它应该返回true
或false
。当未指定:reject_if
时,将为所有没有_destroy
值(评估为 true)的属性哈希构建记录。传递:all_blank
(而不是 Proc)将创建一个 proc,该 proc 将拒绝所有属性(除了_destroy
的任何值)都为空的记录。 - :limit
-
允许您指定可以使用嵌套属性处理的最大关联记录数。Limit 也可以指定为一个 Proc 或指向应该返回数字的方法的
Symbol
。如果嵌套属性数组的大小超过指定的限制,则会引发NestedAttributes::TooManyRecords
异常。如果省略,则可以处理任意数量的关联。请注意,:limit
选项仅适用于一对多关联。 - :update_only
-
对于一对一关联,此选项允许您指定在关联记录已存在时如何使用嵌套属性。一般来说,现有记录可以使用新的一组属性值进行更新,或者可以用包含这些值的新记录替换。默认情况下,
:update_only
选项为 false,并且仅当嵌套属性包含记录的:id
值时,才会使用嵌套属性更新现有记录。否则,将实例化一个新记录并用它替换现有记录。但是,如果:update_only
选项为 true,则无论是否存在:id
,嵌套属性都将始终用于更新记录的属性。对于集合关联,将忽略此选项。
示例
# creates avatar_attributes=
accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
# creates avatar_attributes=
accepts_nested_attributes_for :avatar, reject_if: :all_blank
# creates avatar_attributes= and posts_attributes=
accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/nested_attributes.rb, line 351 def accepts_nested_attributes_for(*attr_names) options = { allow_destroy: false, update_only: false } options.update(attr_names.extract_options!) options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only) options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank attr_names.each do |association_name| if reflection = _reflect_on_association(association_name) reflection.autosave = true define_autosave_validation_callbacks(reflection) nested_attributes_options = self.nested_attributes_options.dup nested_attributes_options[association_name.to_sym] = options self.nested_attributes_options = nested_attributes_options type = (reflection.collection? ? :collection : :one_to_one) generate_association_writer(association_name, type) else raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" end end end