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
请注意,对于使用 accepts_nested_attributes_for 的所有关联,都会自动启用 :autosave 选项。
一对一¶ ↑
考虑一个拥有一个 Avatar 的 Member 模型
class Member < ActiveRecord::Base has_one :avatar accepts_nested_attributes_for :avatar end
在一对一关联上启用嵌套属性可以让您一次性创建 member 和 avatar
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } } member = Member.create(params[:member]) member.avatar.id # => 2 member.avatar.icon # => 'smiling'
它还允许您通过 member 更新 avatar
params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } } member.update params[:member] member.avatar.icon # => 'sad'
如果您想在不提供 ID 的情况下更新当前 avatar,则必须添加 :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,否则该模型不会被销毁。
一对多¶ ↑
考虑一个拥有多个帖子(post)的 member
class Member < ActiveRecord::Base has_many :posts accepts_nested_attributes_for :posts end
现在,您可以通过 member 的属性哈希来设置或更新关联帖子的属性:在哈希中包含 :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 过程,以静默忽略任何未能通过您标准的で新记录哈希。例如,前面的示例可以重写为
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 键,并且该 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
关联集合的嵌套属性也可以采用哈希的哈希(hash of hashes)形式传递,而不是哈希的数组。
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 参数生成的哈希传递关联集合的属性,因为在这种情况下没有自然的方式来提交哈希数组。
保存¶ ↑
对模型的所有更改,包括标记为销毁的模型,都会在父模型保存时自动且原子地保存和销毁。这发生在父对象的 save 方法启动的事务中。请参阅 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) Link
为指定的关联定义属性写入器。
支持的选项
- :allow_destroy
-
如果为 true,则销毁属性哈希中具有
_destroy键且值为 true(例如 1、'1'、true 或 'true')的成员。此选项默认值为 false。 - :reject_if
-
允许您指定一个过程(Proc)或指向一个方法的符号(Symbol),用于检查是否应该为某个属性哈希构建记录。该哈希将被传递给提供的过程或方法,并且应该返回 true 或 false。当没有指定
:reject_if时,对于所有没有_destroy值为 true 的属性哈希,都会构建一个记录。传递:all_blank而不是过程(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