Ruby-on-rails – Rails accepts_nested_attributes_for validation on transactional object

nestedruby-on-railsvalidation

I'm struggling since some hours in order to make validations of nested attributes work in my rails app. A small caveat is that I have to validate nested attributes dynamically based off of their parent's attributes, as the amount of info required changes over time according to where in the process the parent is.

So here's my setup: I have a parent with many different associated models and I want to validate subsequently nested attributes of those every time I save the parent. Given the fact that validations change dynamically, I had to write a custom validation method in the model:

class Parent < ActiveRecord::Base
  attr_accessible :children_attributes, :status
  has_many :children
  accepts_nested_attributes_for :children
  validate :validate_nested_attributes
  def validate_nested_attributes
    children.each do |child|
      child.descriptions.each do |description|
        errors.add(:base, "Child description value cant be blank") if description.value.blank? && parent.status == 'validate_children'
      end
    end
  end
end


class Child < ActiveRecord::Base
  attr_accessible :descriptions_attributes, :status
  has_many :descriptions
  belongs_to :parent
  accepts_nested_attributes_for :descriptions
end

In my controller I call update_attributes on the parent when I want to save. Now the problem is that, apparently, rails runs the validations against the database and not against the object that was modified by the user or the controller. So what might happen is that a child's value is erased by a user and the validations will pass, while later validations will not pass because the item in the database is not valid.

Here's a quick example of this scenario:

parent = Parent.create({:status => 'validate_children', :children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => 'Not blank!'}}}})
  #true
parent.update_attributes({:children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => nil}}}})
  #true!! / since child.value.blank? reads the database and returns false
parent.update_attributes({:children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => 'Not blank!'}}}})
  #false, same reason as above

The validation works for first-level associations, e.g. if a Child has a 'value' attribute, I could run a validation the way I do. The problem is with deep associations that apparently cannot be validated before saving.

Could anyone point me in the right direction of how to solve this? The only way I currently see is by saving records, validating them afterwards and deleting / reverting them if the validation fails, but I am honestly hoping for something more clean.

Thank you all in advance!

SOLUTION

So it turns out I was running validations on deep nested models by referencing those directly in the custom validation, this way:

class Parent < ActiveRecord::Base
  [...]
  has_many :descriptions, :through => :children
  [...]
  def validate_nested_attributes
    descriptions.each do |description|
      [...]
    end
  end
end

Which for some reason leads to the problems I was having above. Thanks Santosh for testing my example code and reporting it was working, this pointed me in the right direction to figure this out.

For future reference, the code in the original question works for this sort of dynamic, deeply-nested validations.

Best Answer

I think you should use validates_associated for this

with the following validation in Child

validates :value, :presence => true, :if => "self.parent.status == 'validate_children'"