One of the problems I hit earlier today, which literally cost 2.5 hours to resolve, was that I have a Rails model which uses a serialized column in it. The column contains a hash of options for the object. Fairly simple stuff.
As it turns out, after overwriting one of the parameters in the hash and saving it, the object in the database would be out of sync with the ActiveRecord object still in memory (even though save returned true), which lead to hilarity the next time someone grabbed the object from the DB and got partially out-of-date data. (Inconsistent out-of-date data. Failure.)
As it turns out, the issue is a new feature Rails 2.1 — partial updates of database records. Rails keeps a list of which attributes are “dirty” and only updates those when you call ActiveRecord#save. Unfortunately, touching the contents of a hash does not mark it as dirty.
I was absolutely clueless how this was happening (I stupidly upgraded to 2.1 on a whim, thinking that I hadn’t written any significant code yet so incompatibilities wouldn’t bother me — true enough, the only incompatibility was with my mental model of how ActiveRecord worked), but I successfully diagnosed the why — a dirty bit wasn’t getting set for the serialized object. OK, no problem, the same thing happens at the day job with our Java system — which means I can use the same hacky solution to the problem.
def before_save
options_hash_clone = options.clone #shallow copy of options hash
options = {} # sets dirty bit for options hash
options = options_hash_clone # restores options hash to original content, ensuring save updates it in DB
end
which will, indeed, set the dirty bit.
As it turns out, there is a cleaner way to do this if partial updates aren’t a requirement for your model:
#there are a number of places you could put this — the model class itself strikes me as decent
ModelNameGoesHere.partial_updates = false
A big thanks to this post for putting me on the scent of the problem. Seems I missed quite a bit of discussion on Google about it since I was not hitting the right keywords apparently. The fact that this is on by default appears to be one of those opinionated software practices where “opinionated” means “it is our opinion that you should be as familiar with the change log as the core team before you install a new production release”. (Sorry if I sound bitter. 2.5 hours.)
I totally agree that this should not be on by default. I hit the same problem as you this week – object.some_text_attrib[x] = ‘Y’ does not register as a change to the attrib and does not get saved.
Frankly I think this is a bug in partial updates – the atrrib has been updated legitimately but ActiveRecord doesn’t notice. The result was that every time my app allocated a new VLAN on a router for a new customer, the last customer’s VLAN got blatted. Nice.
steve
Testing? Are you still there? Come back!
I run a vocabulary-learning e-site.
Thanks so much for this post as I definitely wasted 2.5hr before I started searching. This is definitely a bug that they should fix ASAP
Big thanks for writing this up. After googling “rails save! fails” and other variations, finally stumbled across this. I’ve been doing data migration and massage and couldn’t figure out why my calls to save, save!, update_attribute and the like wouldn’t get reflected in the database. Thanks again!