asibs asibs - 1 year ago 71
Ruby Question

Rails ActiveRecord: Saving nested models is rolled back

Using Rails 5:

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

I've created the simplest example I can think of to demonstrate the issue:


class Parent < ApplicationRecord
has_many :children
accepts_nested_attributes_for :children


class Child < ApplicationRecord
belongs_to :parent

Create parent, save, create child, save (works)

rails console
, creating a new parent, then saving, then building a child from the parent, then saving the parent, works fine:

irb(main):004:0> parent =
=> #<Parent id: nil, created_at: nil, updated_at: nil>
(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
=> #<Child id: nil, parent_id: 1, created_at: nil, updated_at: nil>
(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

Create parent, create child, save (doesn't work)

However, if I try to create a new parent, then build the child without saving the parent, and finally save the parent at the end, the transaction fails and is rolled back:

irb(main):008:0> parent =
=> #<Parent id: nil, created_at: nil, updated_at: nil>
=> #<Child id: nil, parent_id: nil, created_at: nil, updated_at: nil>
(0.5ms) BEGIN
(0.4ms) ROLLBACK
=> false

Can anyone explain why, and how to fix?


Creating both parent and child, then saving does work if you pass
validate: false
, so this points to the issue being validation of the child failing, because it requires the parent_id to be set - but presumably the child validation must be running before the parent is saved then, or it wouldn't fail?

irb(main):001:0> parent =
=> #<Parent id: nil, created_at: nil, updated_at: nil>
=> #<Child id: nil, parent_id: nil, created_at: nil, updated_at: nil>
irb(main):003:0> 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


It also works using
(without the
validation: false
) if I remove the
belongs_to :parent
line from
, since then no validation takes place that
is valid before being persisted - however, then you lose ability to get at the parent from the child (via
). You can still get to the child from the parent (via

Ren Ren
Answer Source

Try it with this:

class Child < ApplicationRecord
  belongs_to :parent, optional: true

After doing some research I discovered that Rails 5 now requires an associated id to be present in the child by default. Otherwise Rails triggers a validation error.

Check out this article for a great explanation and the relevant pull request

...and the official Rails guide make a very brief mention of it: :optional

If you set the :optional option to true, then the presence of the associated object won't be validated. By default, this option is set to false.

So you can turn off this new behavior by adding optional: true after the belongs_to object.

So in your example you would have to create/save Parent first before building the child, or use optional: true

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download