I\'m wondering if there is something at work here that I don\'t understand or if I\'ve run into a bug in ActiveRecord (4.1.1).
I have a database full of records with
ActiveModel (by 4.1.1) doesn't have a way to track "inline" modifications on attributes.
Your 'similar_but_different_json' method is probably making inline modifications on the string.
Just duplicate the string before modifying it.
test = Submission.find(1)
test_json_data_duplicate = test.json_data.dup
test.update_attribute('json_data',similar_but_different_json(test_json_data_duplicate))
When you did ...
test.json_data = ""
... ActiveModel could catch the change because you are setting it to a new String object that happens to be empty. So when you call update_attribute the model has already known that the attribute has changed.
If you try to empty the string in an inline manner your trick will not work.
test = Submission.find(1)
old_json_data = test.json_data
test.json_data.clear # Instead of test.json_data = ""
test.json_data = similar_but_different_json(old_json_data)
test.save
ActiveModel::Dirty
I don't understand why exactly the object is not marked dirty. A workaround is to use update_columns
:
test.update_columns(json_data: similar_but_different_json(test.json_data))
It will execute an UPDATE query directly in the DB, without any validation, dirty check, etc... The json_data
field must not be read-only though.
will_change!
You can also use:
test.json_data_will_change! # Goes before the save.
This will tell ActiveModel that the attribute, json_data
, has changed (i.e. it's dirty ← there's a joke there somewhere) and will update the value properly on save.
See Rails is not saving an attribute that is changed for some more details as well.