Today I discovered a small bug / feature of rails 3.1.3.
Having the following structure:
class Item < ActiveRecord::Base has_many :tags end class Tag < ActiveRecord::Base # Tag has a boolean flag 'enabled' belongs_to :item end
It's possible to completely replace the has_many collection
with a new collection. Make this collection:
item = Item.find(1) tags = [ item.tags.create( :enabled => true, :name => "tag1" ) ]
All fine for the moment. As expected the enabled flag of the first item is set:
tags[0].name # => "tag1" tags[0].enabled? # => true
Now replacing the existing tags:
item.tags.replace( tags ) item.tags[0].name # => "tag1" item.tags[0].enabled? # => false
What?? It just forgot the enabled flag!!
The way to replace the items is by assigning:
item.tags = tags item.tags[0].name # => "tag1" item.tags[0].enabled? # => true
So remember when replacing collections of has_many do not replace them but assign them....
I found this in the Rails source, in collection_association.rb
Let’s play “spot the bug”; I don’t see it, do you?
305 # Replace this collection with +other_array+
306 # This will perform a diff and delete/add only records that have changed.
307 def replace(other_array)
308 other_array.each { |val| raise_on_type_mismatch(val) }
309 original_target = load_target.dup
310
311 if owner.new_record?
312 replace_records(other_array, original_target)
313 else
314 transaction { replace_records(other_array, original_target) }
315 end
316 end
Wel I also checked the rails code and it seems the assign operation is identical to the replace method :S
def writer(records)
replace(records)
end
Strange! I guess I need to review recheck my own code again! Thanks for the response!