Domanda

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 only one attribute, a field a bit of JSON in it. I take one and try to update it like so.

test = Submission.find(1)
test.update_attribute('json_data',similar_but_different_json(test.json_data))

Let's assume the method similar_but_different_json makes a small update to that JSON. In my case I'm fixing some data errors that were created by a broken form.

When doing this, I don't get any errors, I show a commit in the console but no data submitted and get a return of true.

In order to actually update the record I have to do this.

test = Submission.find(1)
old_json_data = test.json_data
test.json_data = ""
test.json_data = similar_but_different_json(old_json_data)
test.save

What seems to be happening is that ActiveRecord doesn't identify that a change has been made that has to be saved. Could this be why setting the field to an empty string then back to JSON allows the record to save?

È stato utile?

Soluzione

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.

Altri suggerimenti

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.

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top