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?
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
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
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
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.
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
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".
Related
In my Rails 6 application I have this association:
class Project < ApplicationRecord
has_many :tasks, :dependent => :destroy, :autosave => true
accepts_nested_attributes_for :tasks, :allow_destroy => true
end
Right now, when I save a project together with its nested tasks, the tasks only get updated when at least one of their attributes changes.
For reasons that are very specific to my application / use case, I want the tasks to always get updated in the database, though (even if none of them changed at all!) when I hit save.
How can this be achieved?
I was hoping that adding :autosave => true would make the difference, but unfortunately it doesn't.
You can do a callback on Project:
class Project < ApplicationRecord
before_save :touch_tasks
def touch_tasks
tasks.all.touch_all
end
end
https://apidock.com/rails/ActiveRecord/Relation/touch_all
I have an issue creating a new record with nested associations in a clean way. Here's the controller code:
#listing = current_user.listings.build(params[:listing].permit(ATTRIBUTES_FOR_CREATE))
This builds an entity with several nested associations, like this:
class ListingDataField < ActiveRecord::Base
belongs_to :listing
validates_presence_of :listing
end
However, when I do #listing.save in controller, I get validation errors on those nested ListingDataField entities that 'listing can't be blank'. If I understand correctly, AutosaveAssociation first validates and saves nested associations, and eventually saves top-level entity. Thus, it fails validating ListingDataField, because Listing is not yet saved.
But I believe it's right having :listing validation in ListingDataField, so I wouldn't consider removing this. I can see 2 solutions:
in transaction - save Listing record, then build nested associations
one by one
#listing.save(:validate => false) but this is too ugly
Both aren't as much elegant as current_user.listings.build(...), so my question is - what is the proper Rails way for this?
P.S. I searched SO for similar question but I couldn't find any, hopefully this is not a duplicate :)
Have you tried adding:
class ListingDataField < ActiveRecord::Base
belongs_to :listing, inverse_of: :listing_data_fields
validates :listing, presence: true
end
and
class Listing < ActiveRecord::Base
has_many :listing_data_fields, inverse_of: :listing
end
This should make validation of presence work.
I need some help getting my factory_girl settings correct on this has one through many with nested attributes. Here's the three models for reference.
location.rb
class Location < ActiveRecord::Base
has_many :person_locations
has_many :people, through: :person_locations
end
person_location.rb
class PersonLocation < ActiveRecord::Base
belongs_to :person
belongs_to :location
accepts_nested_attributes_for :location, reject_if: :all_blank
end
person.rb
class Person < ActiveRecord::Base
has_many :person_locations
has_many :locations, through: :person_locations
accepts_nested_attributes_for :person_locations, reject_if: :all_blank
end
Notice that locations is nested under the person record, but it needs to go through two models to be nested. I can get the tests working like this:
it "creates the objects and can be called via rails syntax" do
Location.all.count.should == 0
#person = FactoryGirl.create(:person)
#location = FactoryGirl.create(:location)
#person_location = FactoryGirl.create(:person_location, person: #person, location: #location)
#person.locations.count.should == 1
#location.people.count.should == 1
Location.all.count.should == 1
end
I should be able to create all three of these records within one line but haven't figured out how to yet. Here's the structure I would like to have work correctly :
factory :person do
...
trait :location_1 do
person_locations_attributes { location_attributes { FactoryGirl.attributes_for(:location, :location_1) } }
end
end
I have other models which are able to create via a similar syntax, but it has only one nested attribute versus this deeper nesting.
As entered above, I get the following error:
FactoryGirl.create(:person, :location_1)
undefined method `location_attributes' for #<FactoryGirl::SyntaxRunner:0x007fd65102a380>
Furthermore, I want to be able to test properly my controller setup for creating a new user with nested location. It will be tough to do this if I can't get the call down to one line.
Thanks for your help!! Hopefully I provided enough above to help others as well when they are creating a has many through relationship with nested attributes.
A couple of days later I figured it out after reading blog 1 and blog 2. In the process of refactoring all of my FactoryGirl code now.
FactoryGirl should look as follows:
factory :person do
...
trait :location_1 do
after(:create) do |person, evaluator|
create(:person_location, :location_1, person: person)
end
end
end
The person_location factory should be pretty straight forward then following the above code. You can either do the location_attributes which is in the original question or create a similar block to this answer to handle it there.
I am trying to build a polymorphic relationship from a nested form that's backwards to all the examples I've found. I am hoping someone to point out the error of my ways.
class Container < ActiveRecord::Base
belongs_to :content, :polymorphic => true
end
class Notice < ActiveRecord::Base
has_one :container, :as => :content
end
class Form < ActiveRecord::Base
has_one :container, :as => :content
end
It seems most people would build a Container from a Notice or Form, but in my case the notice or form contains a small amount of content (file location or a couple db fields) so it's much dry'er to build the Notice or Form from the Container.
I thought I could solve by adding accepts_nested_attributes_for :content but that gives me an unrecognized attribute :notice when I try to create a Container with a nested Notice (looking for content, not the polymorphic association)
I can do it manually and explicitly exclude the nested fields like
if params[:container].has_key('notice')
#c = Container.new(params[:container].except(:notice))
and then build, but isn't that a smell? Is there a better way?
Thank you for reading!
Nested attributes are designed to work from the parent down to the children, not the other way around. Moreover, in this scenario, how would nested attributes know whether you are trying to create a Notice or Form object?
If you find it DRYer to build the content from the container, you probably have your associations inside out - try changing your schema to:
class Container < ActiveRecord::Base
has_one :notice
has_one :form
end
class Notice < ActiveRecord::Base
belongs_to :container
end
class Form < ActiveRecord::Base
belongs_to :container
end
You can use validation to ensure only one child (:notice or :form) is actually associated if need be.
I've been struggling with this for some time now. I'm just trying to get nested attributes to validate on Rails 3.2 with no luck. It's like it's just completely ignoring validations for the nested attributes. Below is an example validation that's not working:
class Invoice < ActiveRecord::Base
validates :description, :presence => true
belongs_to :client_branch
has_many :invoice_items
accepts_nested_attributes_for :invoice_items, :allow_destroy => true
end
class InvoiceItem < ActiveRecord::Base
belongs_to :invoice
validate :thisisatest
def thisisatest
errors.add(:qty, 'QTY NOT VALIDATING TEST.')
end
end
When saving an Invoice with some InvoiceItems, it saves it successfully, even though the custom validation is clearly adding an error for the :qty attribute. Is there something I should be adding to my models for nested validation to work, or am I perhaps missing something else?
Actually, I'm being daft. I changed the model name, along with all references to it and it took me this long to miss one reference still pointing to the old model in the javascript. Thus, items being added dynamically weren't named correctly, causing the validation not to trigger. :/