accepts_nested_attributes_for child association validation failing

Ruby on-RailsValidationActiverecordNested

Ruby on-Rails Problem Overview


I'm using accepts_nested_attributes_for in one of my Rails models, and I want to save the children after creating the parent.

The form works perfectly, but the validation is failing. For simplicity's sake imagine the following:

class Project < ActiveRecord::Base
  has_many :tasks
  accepts_nested_attributes_for :tasks
end

class Task < ActiveRecord::Base
  belongs_to :project

  validates_presence_of :project_id
  validates_associated :project
end

And I am running:

Project.create!(
  :name => 'Something',
  :task_attributes => [ { :name => '123' }, { :name => '456' } ]
)

Upon saving the project model, the validation is failing on the tasks because they don't have a project_id (since the project hasn't been saved).

It seems like Rails is following the pattern below:

  • Validate Project
  • Validate Tasks
  • Save Project
  • Save Tasks

The pattern should be:

  • Validate Project
  • On Pass: Save Project and continue...
  • Validate Tasks
    • On Pass: Save Tasks
    • On Fail: Delete Project (rollback maybe?)

So my question boils down to: How can I get Rails to run the project_id= (or project=) method and validation on the children (tasks) AFTER the parent (project) has been saved, but NOT save the parent (project) model if any child (task) is invalid?

Any ideas?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Use :inverse_of and validates_presence_of :parent. This should fix your validation problem.

   class Dungeon < ActiveRecord::Base
     has_many :traps, :inverse_of => :dungeon
   end

   class Trap < ActiveRecord::Base
     belongs_to :dungeon, :inverse_of => :traps
     validates_presence_of :dungeon
   end

http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_presence_of

https://github.com/rails/rails/blob/73f2d37505025a446bb5314a090f412d0fceb8ca/activerecord/test/cases/nested_attributes_test.rb

Solution 2 - Ruby on-Rails

Use this answer for Rails 2, otherwise see below for the :inverse_of answer

You can work around this by not checking for the project_id if the associated project is valid.


class Task < ActiveRecord::Base
belongs_to :project




validates_presence_of :project_id, :unless => lambda {|task| task.project.try(:valid?)}
validates_associated :project
end

validates_presence_of :project_id, :unless => lambda {|task| task.project.try(:valid?)} validates_associated :project end

Solution 3 - Ruby on-Rails

Only validate the relationship, not the ID:

class Task < ActiveRecord::Base
  belongs_to :project

  validates_presence_of :project
end

As soon as the association is populated, ActiveRecord will consider the validation to have succeeded, whether or not the model is saved. You might want to investigate autosaving as well, to ensure the task's project is always saved:

class Task < ActiveRecord::Base
  belongs_to :project, :autosave => true

  validates_presence_of :project
end

Solution 4 - Ruby on-Rails

Unfortunately none of the above suggestions work for me with Rails 2.3.5.

In my case, the project in a task is always nil if both are created using nested attributes. Only if I remove the validates_presence_of, the create goes through successfully. The unit test and the log show that all is created correctly.

So I'd now tend to add constraints to the DB instead of Rails as that seems to be more reliable in the first place.

Solution 5 - Ruby on-Rails

You could just create the project and only add the projects if it passes validation:

tasks = params.delete(:task_attributes)
if Project.create(params)
  Project.update_attributes(:task_attributes => tasks)
end

Ciao

Solution 6 - Ruby on-Rails

Contrary to what bigo suggests, it's not always acceptable to save the parent object first and then the children. Usually you want to make sure all objects validate before you start saving them. That gives the user the chance to re-edit the input form and correct any errors.

The problem you describe will be fixed in Rails 3.0. I would have posted a link to the Lighthouse ticket, but stackoverflow.com does not allow this because I'm a new user (#fail). But for the time being, you can use the plugin "parental_control", which will fix your "bug".

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRyan TownsendView Question on Stackoverflow
Solution 1 - Ruby on-RailsboblinView Answer on Stackoverflow
Solution 2 - Ruby on-RailsThe WhoView Answer on Stackoverflow
Solution 3 - Ruby on-RailsFrançois BeausoleilView Answer on Stackoverflow
Solution 4 - Ruby on-RailsMichael ReinschView Answer on Stackoverflow
Solution 5 - Ruby on-Railsuser119264View Answer on Stackoverflow
Solution 6 - Ruby on-RailsThomas WatsonView Answer on Stackoverflow