AcitveRecord 不会accepts_nested_attributes_for具有嵌套现有记录的新关联记录



ActiveRecord似乎不明白,为具有嵌套属性的现有记录指定一组参数,它可以创建一个新的嵌套记录,该记录本身具有嵌套的现有记录。(关系树:Existing -> New -> Existing

这是一个bug,还是我遗漏了什么?

让我给你看一个简单的例子。

以下是我的型号:

class User < ActiveRecord::Base
  has_many :posts
  attr_accessible :name, :posts_attributes
  accepts_nested_attributes_for :posts
end
class Post < ActiveRecord::Base
  belongs_to :group
  belongs_to :user
  attr_accessible :content, :title, :group_attributes
  accepts_nested_attributes_for :group
end
class Group < ActiveRecord::Base
  has_many :posts
  attr_accessible :name
end

我在每个表中创建了一个记录,并相应地将它们关联起来,所以每个表中都有一个带有id=1的记录——这是已知的。现在,如果我有一个现有的User、一个新Post和一个现有Group,并尝试使用accepts_nested_attributes_for更新该记录,它不喜欢:

1.9.3-p125 :044 > params
{
                  :id => 1,
                :name => "Billy",
    :posts_attributes => [
        [0] {
                          :title => "Title",
                        :content => "Some magnificent content for you!",
            :group_attributes => {
                  :id => 1,
                :name => "Group 1"
            }
        }
    ]
}
1.9.3-p125 :045 > u
#<User:0x00000002f7f380> {
            :id => 1,
          :name => "Billy",
    :created_at => Fri, 03 Aug 2012 20:21:37 UTC +00:00,
    :updated_at => Fri, 03 Aug 2012 20:21:37 UTC +00:00
}
1.9.3-p125 :046 > u.update_attributes params
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
ActiveRecord::RecordNotFound: Couldn't find Group with ID=1 for Post with ID=
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/nested_attributes.rb:462:in `raise_nested_attributes_record_not_found'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/nested_attributes.rb:332:in `assign_nested_attributes_for_one_to_one_association'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/nested_attributes.rb:288:in `group_attributes='
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/attribute_assignment.rb:94:in `block in assign_attributes'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/attribute_assignment.rb:93:in `each'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/attribute_assignment.rb:93:in `assign_attributes'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/base.rb:498:in `initialize'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/reflection.rb:183:in `new'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/reflection.rb:183:in `build_association'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/associations/association.rb:233:in `build_record'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/associations/collection_association.rb:112:in `build'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/nested_attributes.rb:405:in `block in assign_nested_attributes_for_collection_association'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/nested_attributes.rb:400:in `each'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/nested_attributes.rb:400:in `assign_nested_attributes_for_collection_association'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/nested_attributes.rb:288:in `posts_attributes='
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/attribute_assignment.rb:85:in `block in assign_attributes'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/attribute_assignment.rb:78:in `each'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/attribute_assignment.rb:78:in `assign_attributes'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/persistence.rb:216:in `block in update_attributes'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/transactions.rb:295:in `block in with_transaction_returning_status'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/connection_adapters/abstract/database_statements.rb:192:in `transaction'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/transactions.rb:208:in `transaction'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/transactions.rb:293:in `with_transaction_returning_status'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.7/lib/active_record/persistence.rb:215:in `update_attributes'
  from (irb):15
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.7/lib/rails/commands/console.rb:47:in `start'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.7/lib/rails/commands/console.rb:8:in `start'
  from /home/trevor/.rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.7/lib/rails/commands.rb:41:in `<top (required)>'
  from script/rails:6:in `require'
  from script/rails:6:in `<main>'1.9.3-p125 :047 > 

它认为找不到与新帖子相关的群组(具有已知ID)。当我从group_attributes中删除ID时,它就起作用了(但它会创建一个新的组记录)。当我给posts_attributes一个ID,并从group_attributes中删除该ID(然后再次创建一个新组)时,它就起作用了。当他们都有ID时,它也会起作用。

这种关系正在发挥作用:

1.9.3-p125 :059 > p = Post.new( { group_attributes: { name: 'Testing' } } )
#<Post:0x00000004212380> {
            :id => nil,
         :title => nil,
       :content => nil,
      :group_id => nil,
       :user_id => nil,
    :created_at => nil,
    :updated_at => nil
}
1.9.3-p125 :060 > p.group
[
    [0] #<Group:0x00000004211868> {
                :id => nil,
              :name => "Testing",
        :created_at => nil,
        :updated_at => nil
    }
]

如果所有记录都是新的,那么在创建User期间使用posts_attributesgroup_attributes时,它也完全有效。

它在第一个例子中不应该仍然有效吗?ActiveRecord应该足够聪明,能够解决这个问题。。。!

以下是我认为正在发生的事情:您正在传递一个组的ID,向ActiveRecord指示该组存在。ActiveRecord正在尝试查找该组,以便使用group_attributes中的其他数据对其进行更新。由于您在post_attributes内部执行,ActiveRecord正试图通过post之间的关联来查找该组。也就是说,ActiveRecord首先查找关联的组,其中id=post.group_id,然后从该结果中查找id=1的组。对于父母关系来说,这可能看起来有点奇怪和笨拙,就像在你的情况下一样,但我相信你可以看到,当朝着另一个方向发展时,这是一种有用的行为,嵌套的属性代表了潜在的许多孩子中的一个或多个。

但是,根据post_attributes中的数据创建的post对象还没有与组相关联——post.group_id为nil。因此,当ActiveRecord进行第一次搜索以获取关联的组时,它会显示为空。相应地,它在(空)结果中找不到ID为1的组。从技术上讲,记录是存在的,但就与帖子的关联而言,它不存在。

您可以通过在post_attributes中包含group_id=>1来证明这一点。我相信,如果你这样做,ActiveRecord会根据关联找到组,然后从结果中选择ID为1的组,成功,然后用group_attributes中的额外数据更新该组。

另外请注意,像您这样在帖子中包含组的嵌套属性的唯一原因是,如果您允许用户在创建新帖子的同时更新组名称。如果你想做的只是将新帖子链接到现有的组,那么你只需要在post_attributes中包含group_id,就可以去掉group_attribute。

最新更新