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.
Example:
class Address < ActiveRecord::Base
belongs_to :contact
validates_inclusion_of :is_current, :in => [true, false], :allow_nil => true #any validation will do
end
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 company.id
#now update attributes
company.update_attributes(:addresses_attributes => [{:id => newaddress.id, :city => "Dallas", :is_current => true }]
#now check the address city
company.addresses.first.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?
association.to_a
else
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : []
end</code>
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| record.id.to_s == attributes['id'].to_s }
This will load the association the first time the detect is run,
and avoid the problem with validation.