This project is archived and is in readonly mode.

#5115 ✓invalid

accepts_nested_attributes_for on has_many does not update or delete if you also set validates_associated

Reported by swalterd | July 15th, 2010 @ 12:49 AM

Tested to be a problem in Rails 2.3.8 and working in Rails 2.3.5

If you have a has_many association with validation and then try to update that association with update_attributes on a newly loaded parent object, the validation will overload the objects updated in memory before the save.

 class Address < ActiveRecord::Base
  belongs_to :contact
  validates_inclusion_of :is_current, :in => [true, false], :allow_nil => true  #any validation will do 

class Contact < ActiveRecord::Base has_many :addresses, :dependent => :destroy validates_associated :addresses
accepts_nested_attributes_for :addresses, :allow_destroy => true end

#now create a new contact with an address company = Contact.create :name => "test" newaddress = company.addresses.create :city => "Austin", :is_current => true #now reload the company from the db company = Contact.find #now update attributes company.update_attributes(:addresses_attributes => [{:id =>, :city => "Dallas", :is_current => true }] #now check the address city #Will print out Austin
This is because the code in Rails 2.3.8 does not load the addresses association for the nested attributes update. Instead it tries to be clever and it loads only the objects for which it has IDs. This saves time, but when validation runs in sees that company.addresses.loaded? is false and it reloads the association, and wipes out the changes.
In activerecord/lib/active_record/nested_attributes.rb the code:
 existing_records = if association.loaded?

     attribute_ids = {|a| a['id'] || a[:id] }.compact
     attribute_ids.present? ? association.all(:conditions =&gt; {association.primary_key =&gt; attribute_ids}) : []

Seems to be the problem. If I just change it to
 existing_records = association.to_a
I have no problems.
In Rails 2.3.5 when it loops over the attributes array it uses this code to find the object
 existing_record = send(association_name).detect { |record| == attributes['id'].to_s }
This will load the association the first time the detect is run, and avoid the problem with validation.

Comments and changes to this ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href=""></a>

People watching this ticket

Referenced by