Tom Rossi
Tom Rossi

Reputation: 12066

Raise an exception when parent fails to save

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

Answers (3)

Amro Abdalla
Amro Abdalla

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

AbM
AbM

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

Jacob Vanus
Jacob Vanus

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

Related Questions