accepts_nested_attributes_for child association validation failing
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?
Solution 1:
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:
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
Solution 3:
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