Reputation: 12066
I've run into a gotcha when trying to create a parent and child at the same time. I would think an exception should be raised when the associated parent fails to save, but it doesn't.
class Child < ApplicationRecord
belongs_to :parent
validates_presence_of :parent, :name
end
class Parent < ApplicationRecord
has_one :child
validates_presence_of :name
end
Notice the child saves and their is no visibility into the save issue with the parent:
parent = Parent.new
parent.valid? # => false
child = Child.create!(name: 'something', parent: parent) # => true
child.parent_id # => nil
child.reload.valid? # => false
An an invalid child has been created. Why is the create!
method not calling create!
on the parent as well so an exception is raised?
Its worth noting that When the same process is followed, but instead we start from the parent, we get the behavior I expect:
child = child.new
child.valid? # => false
parent = Parent.create!(name: 'something', child: child) # => ActiveRecord::Exception
parent.valid? # => false
I know some work arounds (e.g. validates_associated :parent
on the child), but I'm trying to understand why Rails is behaving the way it is.
Upvotes: 3
Views: 314
Reputation: 584
change children to
class Child < ApplicationRecord
belongs_to :parent
validates_presence_of :parent, :id
end
It is as @AbM said you should put the constraint on some field, This will give you ActiveRecord::RecordInvalid: Validation failed: Id can't be blank
exception.
Upvotes: 0
Reputation: 7779
Why would the exception be raised? You are setting the parent
and as far as the child
is concerned, the parent
exists (passing the validation) and it will not call the create!
on the parent and IMO nor should it.
Your workaround is to validate on the parent_id
instead of the parent
or create the child via parent.children.create!
. The latter will raise ActiveRecord::RecordNotSaved: You cannot call create unless the parent is saved
if the parent is not persisted
Upvotes: 1
Reputation: 569
Rails doesn't know you're trying to save them at the same time. If you want to save them both at once, try nested attributes.
I would expect this to do what you want:
class Child < ApplicationRecord
belongs_to :parent
validates_presence_of :parent
end
class Parent < ApplicationRecord
has_many :children
validates_presence_of :name
accepts_nested_attributes_for :children, allow_destroy: true
end
parent = Parent.new children: [child]
parent.save!
Upvotes: 1