4

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?

rb-
  • 2,315
  • 29
  • 41
  • I should also mention that this isn't a full implementation of Rails. I'm using ActiveRecord with Sinatra. – rb- May 10 '14 at 16:34
  • I thought `update_attribute` was deprecated, in favour of `update_attributes`? – nathanvda May 10 '14 at 22:07
  • It is deprecated, but the issue also happens with `update_attributes`. – rb- May 17 '14 at 13:37
  • I opened an issue on the Rails repo to see if it's a bit in arecord. https://github.com/rails/rails/issues/15146 – rb- May 17 '14 at 13:38
  • Related: http://stackoverflow.com/questions/29958538/rails-is-not-saving-an-attribute-that-is-changed/29958539#29958539 – Joshua Pinter Apr 30 '15 at 03:42

3 Answers3

11

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.

Community
  • 1
  • 1
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
2

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.

Baldrick
  • 23,882
  • 6
  • 74
  • 79
  • That solution does indeed work. It's one line instead of the solution I tried first where I change the field to an empty string then back to JSON. I wasn't familiar with the 'dirty check' but it makes sense. I wonder if I should open an issue on the Rails Github repo. – rb- May 10 '14 at 17:12
2

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

  • 1
    Looks like this is being fixed in 4.2. http://edgeguides.rubyonrails.org/4_2_release_notes.html – Mark G. Sep 30 '14 at 22:47