Rails ActiveRecord:已回滚保存嵌套模型

asi*_*ibs 2 ruby activerecord ruby-on-rails ruby-on-rails-5

使用 Rails 5:

gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
Run Code Online (Sandbox Code Playgroud)

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

父母.rb

class Parent < ApplicationRecord
  has_many :children
  accepts_nested_attributes_for :children
end
Run Code Online (Sandbox Code Playgroud)

孩子.rb

class Child < ApplicationRecord
  belongs_to :parent
end
Run Code Online (Sandbox Code Playgroud)

创建父项,保存,创建子项,保存(有效)

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

irb(main):004:0> parent = Parent.new
=> #<Parent id: nil, created_at: nil, updated_at: nil>
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
=> #<Child id: nil, parent_id: 1, created_at: nil, updated_at: nil>
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
Run Code Online (Sandbox Code Playgroud)

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

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

irb(main):008:0> parent = Parent.new
=> #<Parent id: nil, created_at: nil, updated_at: nil>
irb(main):009:0> parent.children.build
=> #<Child id: nil, parent_id: nil, created_at: nil, updated_at: nil>
irb(main):010:0> parent.save
   (0.5ms)  BEGIN
   (0.4ms)  ROLLBACK
=> false
Run Code Online (Sandbox Code Playgroud)

谁能解释为什么,以及如何解决?

更新

创建父级和子级,然后如果通过,则保存确实有效validate: false,因此这表明子级验证失败的问题,因为它需要设置 parent_id - 但大概子级验证必须在父级保存之前运行然后,或者它不会失败?

irb(main):001:0> parent = Parent.new
=> #<Parent id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> parent.children.build
=> #<Child id: nil, parent_id: nil, created_at: nil, updated_at: nil>
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
Run Code Online (Sandbox Code Playgroud)

更新 2

如果我从 中删除该行,它也可以使用save(不带validation: false),因为这样在持久化之前不会发生有效的验证- 但是,那么您将失去从子级(通过)获取父级的能力。您仍然可以从父级(通过)访问子级。belongs_to :parentchild.rbparent_idchild.parentparent.child

Ren*_*Ren 5

试试这个:

class Child < ApplicationRecord
  belongs_to :parent, optional: true
end
Run Code Online (Sandbox Code Playgroud)

在做了一些研究之后,我发现 Rails 5 现在需要一个关联的 id 在默认情况下存在于孩子中。否则 Rails 会触发验证错误。

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

...并且官方的 Rails指南非常简短地提到了它:

4.1.2.11 : 可选

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

所以你可以通过optional: truebelongs_to对象后面添加来关闭这个新行为。

因此,在您的示例中,您必须先创建/保存 Parent,然后再构建孩子,或使用 optional: true