Raise an exception when parent fails to save - ruby-on-rails

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.

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!

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

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.

Related

ActiveRecord Joins get child objects instead of parent

My problem:
I have Three models: Company, Parent, and Child.
Child belongs_to Parent which belongs_to Company
I need to get all children where a certain attribute is set to false, within one company. (The Parent model has a company_id while the Child model does not.)
What I'm trying:
I have the following join:
#objects = Parent.joins(:childs).where('parents.company_id' => current_user.company_id, 'childs.foo' => false)
In my view:
<!-- This should be a list of child objects -->
<% #objects.each do |obj| %>
<%= obj.foo %>
<% end %>
(foo being the attribute of the child object)
Models:
class Company < ActiveRecord::Base
has_many :parents, :dependent => :destroy
...
end
class Parent < ActiveRecord::Base
has_many :childs, :dependent => :destroy
belongs_to :company
...
end
class Child < ActiveRecord::Base
belongs_to :parent
...
end
However, writing the Parent.joins(:childs)... returns an ActiveRecord relation of Parent objects. (Throwing an error when I try to access the child attributes) I need the end list to be of child objects. But I am finding it difficult to do so.
A good answer to this question would be one that:
Solved this problem in another way that made more sense while not being too computationally intensive.
Or something that shows how to get a list/relation of child objects instead of the parent objects.
Simple, start with the Child class:
Child.joins(:parent).where(parents: {company_id: current_user.company_id}, foo: false)
I would probably advise using scopes/class methods to accomplish this in a cleaner fashion:
class Parent < ActiveRecord::Base
def self.for_user(user)
where(company_id: user.company_id)
end
end
class Child < ActiveRecord::Base
scope :fooless, ->{where foo: false}
end
Now you can do this:
Child.joins(:parent).merge(Parent.for_user(current_user)).fooless

Validates presence of each other in two associated models

I have the following two models:
class Parent < ActiveRecord::Base
has_one :child, dependent: :destroy
validates :child, presence: true
end
class Child < ActiveRecord::Base
belongs_to :parent
validates :parent, presence: true
end
I want to create Parent object.
If I do the following:
Parent.create! or Factory(:parent)
Exception raises: ActiveRecord::RecordInvalid: Validation failed: Child can't be blank
But I can't create Child object without Parent object for the same reason - I need to create Parent object first in order to pass presence validation.
As it appears I have some kind of infinite recursion here.
How to solve it?
UPDATE:
The code below works well in my environment ( Rails3.2.2, ruby 1.8.7)
# parent.rb
class Parent < ActiveRecord::Base
has_one :child
validates :child, :presence => true
end
# child.rb
class Child < ActiveRecord::Base
belongs_to :parent
validate :parent, :presence => true
end
# parent_test.rb
require 'test_helper'
class ParentTest < ActiveSupport::TestCase
test "should be saved" do
parent = Parent.new(:name => "111")
child = Child.new(:name => "222", :parent => parent)
parent.child = child
parent.save!
puts "after saved, parent: #{parent.inspect}"
puts "after saved, child: #{child.inspect}"
assert parent.id > 0
assert child.id > 0
end
end
run this test and got:
Started
after saved, parent: #<Parent id: 980190963, name: "111", created_at: "2012-04-05 23:19:31", updated_at: "2012-04-05 23:19:31">
after saved, child: #<Child id: 980190963, name: "222", parent_id: 980190963, created_at: "2012-04-05 23:19:31", updated_at: "2012-04-05 23:19:31">
.
Finished in 0.172716 seconds.
1 tests, 2 assertions, 0 failures, 0 errors
PREVIOUS ANSWER ================
try to initialize them separately, then add the association, at last save them.
parent = FactoryGirl.build(:parent)
child = FactoryGirl.build(:child, :parent => parent)
parent.child = child
parent.save
child.save # seems this line of code is redundant? I am not sure.
for more details of "build, create", see its official website: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md

Validating models against the parent model

I have two models, one is the parent of the other, and the parent accepts_nested_attributes_for and validates_associated the children.
However, some of my validations have an :if that needs to check one of the properties of the parent.
I was thinking that I could do something like this:
validates_presence_of :blah, :if => Proc.new{|thing| thing.parent.some_value.present?}
However, the 'parent' relationship doesn't appear to be setup at the time of validation (I would assume the children get instantiated and validated first.
Therefore is there any way of doing what I'm thinking of? Is it possible?
You can use before_update or before_create callbacks as per your need like this..
def before_update
self.errors.add("Error Message") if self.parent.some_value.present?
return false if self.errors.count > 0
end
def before_create
self.errors.add("Error Message") if self.parent.some_value.present?
return false if self.errors.count > 0
end
This kind of validation should work:
validates_associated :children
But it won't
The reason is, as far as I understand, beause using acceptes_nested_attributes_for is creating nested objects straight to database via one transaction without passing any children validations.
What you can do here: write your own validation in parent model and validate creating children objects.
Use the :inverse_of option for the association on the parent, so the children will have a reference to the parent when they are built.
class Parent < ActiveRecord::Base
has_many :children, :inverse_of => :parent
accepts_nested_attributes_for :children
end
class Child < ActiveRecord::Base
belongs_to :parent
end
p = Parent.new :children_attributes => { 0 => { :child_attribute => 'value' } }
p.children.first.parent #=> shouldn't be nil anymore

ActiveRecord still save parent object if validations on children fail

With a relationship setup like:
class Parent < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :parent
validates_presence_of :first_name
end
p = Parent.new
p.children.build
p.save
=> false
p.errors
=> {:children => ["is invalid"]}
Is there a way to keep the validations on the child object, but not have their failed validation block the save of the parent?
Take a look at save(options={}) in ActiveRecord::Validations.
You can pass in :validate => false to save(), which will skip the call to valid?.
This will also skip any validations on the parent object, so you may have to do something more involved if the parent has validations as well.
Source
It's not rails style, but it answers your question. So just manage association by yourself:
p = Parent.new
p.save
c = Children.new(:parent_id => p.id)
c.save => 'first name can't be blank"

Rails accepts_nested_attributes_for child doesn't have parent set when validating

I'm trying to access my parent model in my child model when validating. I found something about an inverse property on the has_one, but my Rails 2.3.5 doesn't recognize it, so it must have never made it into the release. I'm not sure if it's exactly what I need though.
I want to validate the child conditionally based on parent attributes. My Parent model has already been created. If the child hasn't been created when I update_attributes on the parent, then it doesn't have access to the parent. I'm wondering how I can access this parent. It should be easy, something like parent.build_child sets the parent_id of the child model, why is it not doing it when building the child for accepts_nested_attributes_for?
For Example:
class Parent < AR
has_one :child
accepts_nested_attributes_for :child
end
class Child < AR
belongs_to :parent
validates_presence_of :name, :if => :some_method
def some_method
return self.parent.some_condition # => undefined method `some_condition' for nil:NilClass
end
end
My form is standard:
<% form_for #parent do |f| %>
<% f.fields_for :child do |c| %>
<%= c.name %>
<% end %>
<% end %>
With an update method
def update
#parent = Parent.find(params[:id])
#parent.update_attributes(params[:parent]) # => this is where my child validations take place
end
I had basically the same problem with Rails 3.2. As suggested in the question, adding the inverse_of option to the parent's association fixed it for me.
Applied to your example:
class Parent < AR
has_one :child, inverse_of: :parent
accepts_nested_attributes_for :child
end
class Child < AR
belongs_to :parent, inverse_of: :child
validates_presence_of :name, :if => :some_method
def some_method
return self.parent.some_condition # => undefined method `some_condition' for nil:NilClass
end
end
I had a similar problem: Ruby on Rails - nested attributes: How do I access the parent model from child model
This is how I solved it eventually; by setting parent on callback
class Parent < AR
has_one :child, :before_add => :set_nest
accepts_nested_attributes_for :child
private
def set_nest(child)
child.parent ||= self
end
end
You cannot do this because in-memory child doesn't know the parent its assigned to. It only knows after save. For example.
child = parent.build_child
parent.child # => child
child.parent # => nil
# BUT
child.parent = parent
child.parent # => parent
parent.child # => child
So you can kind of force this behavior by doing reverse association manually. For example
def child_with_inverse_assignment=(child)
child.parent = self
self.child_without_inverse_assignment = child
end
def build_child_with_inverse_assignment(*args)
build_child_without_inverse_assignment(*args)
child.parent = self
child
end
def create_child_with_inverse_assignment(*args)
create_child_without_inverse_assignment(*args)
child.parent = self
child
end
alias_method_chain :"child=", :inverse_assignment
alias_method_chain :build_child, :inverse_assignment
alias_method_chain :create_child, :inverse_assignment
If you really find it necessary.
P.S. The reason it's not doing it now is because it's not too easy. It needs to be explicitly told how to access parent/child in each particular case. A comprehensive approach with identity map would've solved it, but for newer version there's :inverse_of workaround. Some discussions like this one took place on newsgroups.
check these sites, maybe they'll help you...
Rails Nested Attributes Association Validation Failing
accepts_nested_attributes_for child association validation failing
http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
it seems, rails will assign parent_id after child validation succeeds.
(as parent has an id after it's saved)
maybe worth trying this:
child.parent.some_condition
instead of self.parent.some_condition ... who knows...

Resources