Reputation: 343
Using Rails 5:
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
I've created the simplest example I can think of to demonstrate the issue:
parent.rb
class Parent < ApplicationRecord
has_many :children
accepts_nested_attributes_for :children
end
child.rb
class Child < ApplicationRecord
belongs_to :parent
end
Create parent, save, create child, save (works)
Using 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.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
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.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
Can anyone explain why, and how to fix?
UPDATE
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.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
UPDATE 2
It also works using save
(without the validation: false
) if I remove the belongs_to :parent
line from child.rb
, since then no validation takes place that parent_id
is valid before being persisted - however, then you lose ability to get at the parent from the child (via child.parent
). You can still get to the child from the parent (via parent.child
).
Upvotes: 2
Views: 2095
Reputation: 2293
Both sides of the association need to be marked with inverse_of
. See Rails Guides: Bi-directional Associations.
inverse_of
lets Rails know what association keeps the opposite reference from the other model. If set, when you call parent.children.build
, the new Child will have its #parent
set automatically. That lets it pass the validation check!
Example:
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 id: nil, created_at: nil, updated_at: nil>
> parent.children.build
=> #<Child id: nil, parent_id: nil, created_at: nil, updated_at: nil>
> 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
Upvotes: 1
Reputation: 1374
Try it with this:
class Child < ApplicationRecord
belongs_to :parent, optional: true
end
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:
4.1.2.11 :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
Upvotes: 5