Rails ActiveRecord:回滚保存嵌套模型

使用Rails 5:

gem 'rails', '~> 5.0.0', '>= 5.0.0.1' 

我已经创建了一个我能想到的最简单的例子来演示这个问题:

parent.rb

 class Parent < ApplicationRecord has_many :children accepts_nested_attributes_for :children end 

child.rb

 class Child < ApplicationRecord belongs_to :parent end 

创建父,保存,创建子,保存(工作)

使用rails console ,创建一个新的父级,然后保存,然后从父级构建子级,然后保存父级,工作正常:

 irb(main):004:0> parent = Parent.new => # irb(main):005:0> parent.save (0.5ms) BEGIN SQL (0.4ms) INSERT INTO `parents` (`created_at`, `updated_at`) VALUES ('2016-09-25 13:05:44', '2016-09-25 13:05:44') (3.2ms) COMMIT => true irb(main):006:0> parent.children.build => # irb(main):007:0> parent.save (0.5ms) BEGIN Parent Load (0.5ms) SELECT `parents`.* FROM `parents` WHERE `parents`.`id` = 1 LIMIT 1 SQL (0.7ms) INSERT INTO `children` (`parent_id`, `created_at`, `updated_at`) VALUES (1, '2016-09-25 13:05:52', '2016-09-25 13:05:52') (1.3ms) COMMIT => true 

创建父,创建子,保存(不起作用)

但是,如果我尝试创建一个新的父项,然后在保存父项的情况下构建子项,最后在最后保存父项,则事务将失败并回滚:

 irb(main):008:0> parent = Parent.new => # irb(main):009:0> parent.children.build => # irb(main):010:0> parent.save (0.5ms) BEGIN (0.4ms) ROLLBACK => false 

任何人都可以解释为什么,以及如何解决?

UPDATE

如果你传递了validate: false ,那么创建父节点和子节点都会validate: false ,所以这指的是validation子节点失败的问题,因为它需要设置parent_id – 但可能是子节点validation必须在父节点之前运行然后被保存,或者它不会失败?

 irb(main):001:0> parent = Parent.new => # irb(main):002:0> parent.children.build => # irb(main):003:0> parent.save(validate: false) (0.7ms) BEGIN SQL (0.9ms) INSERT INTO `parents` (`created_at`, `updated_at`) VALUES ('2016-09-25 15:02:20', '2016-09-25 15:02:20') SQL (0.8ms) INSERT INTO `children` (`parent_id`, `created_at`, `updated_at`) VALUES (3, '2016-09-25 15:02:20', '2016-09-25 15:02:20') (1.6ms) COMMIT => true 

更新2

如果我从child.rb删除belongs_to :parent行,它也可以使用save (没有validation: false ),因为在持久化之前没有validationparent_id是有效的 – 然而,你失去了获取父级的能力来自孩子(通过child.parent )。 你仍然可以从父母那里找到孩子(通过parent.child )。

试试吧:

 class Child < ApplicationRecord belongs_to :parent, optional: true end 

在做了一些研究后,我发现Rails 5现在需要一个关联的id 默认存在于子节点中 。 否则Rails会触发validation错误。

查看这篇文章,获得一个很好的解释和相关的拉取请求

......官方的Rails 指南简要提一下:

4.1.2.11:可选

如果将:optional选项设置为true,则不会validation关联对象的存在。 默认情况下,此选项设置为false。

因此,您可以通过在belongs_to对象之后添加optional: true来关闭此新行为。

因此,在您的示例中,您必须在构建子项之前先创建/保存Parent,或使用optional: true

协会的双方都需要用inverse_of标记。 请参阅Rails指南:双向关联 。

inverse_of让Rails知道哪个关联与另一个模型保持相反的引用。 如果设置,当您调用parent.children.build ,新的Child将自动设置其#parent 。 这让它通过了validation检查!

例:

 class Parent < ApplicationRecord has_many :children, inverse_of: :parent accepts_nested_attributes_for :children end class Child < ApplicationRecord belongs_to :parent, inverse_of: :children end > parent = Parent.new => # > parent.children.build => # > parent.save (0.1ms) begin transaction SQL (0.4ms) INSERT INTO "parents" ("created_at", "updated_at") VALUES (?, ?) [["created_at", 2016-09-26 00:46:42 UTC], ["updated_at", 2016-09-26 00:46:42 UTC]] SQL (0.1ms) INSERT INTO "children" ("parent_id", "created_at", "updated_at") VALUES (?, ?, ?) [["parent_id", 2], ["created_at", 2016-09-26 00:46:42 UTC], ["updated_at", 2016-09-26 00:46:42 UTC]] (1.8ms) commit transaction => true