24

I have a rather large json document that I have to store in a field for each Evaluation instance in my app. The over time, certain actions on the app will require me to change various key/value pairs in the document. The Rails 4 and PostgreSQL json data type seems ideal for this problem, but I cannot get changes to commit to the database.

Here's an abbreviated workflow:

I have a json file that I import into a field for each Evaluation record:

// example.json
{ "question_1":"no" }

I create a record and import the json:

>> evaluation = Evaluation.create assessments: File.read("example.json")
>> evaluation.assessments = File.read("#{Rails.root}/example.json")
=> "{ \"question_1\":\"no\" }"

Calling on the assessments field seems to work fine:

>> evaluation.assessments
=> {"question_1"=>"no"}

OR

>> evaluation.assessments["question_1"]
=> "no"

Example 1

Changing the json isn't working too well. This doesn't actually commit anything:

>> evaluation.assessments["question_1"] = "yes"
=> "yes"

>> evaluation.assessments["question_1"]
=> "yes"

>> evaluation.save
   (0.3ms)  BEGIN
   (0.2ms)  COMMIT
=> true

Example #2

Replacing with a completely new object does commit:

>> evaluation.assessments = {"question_1"=>"yes"}
=> {"question_1"=>"yes"}

>> evaluation.save
(0.3ms)  BEGIN
SQL (0.7ms)  UPDATE "evaluations" SET "assessments" = $1, "updated_at" = $2 WHERE "evaluations"."id" = 1  [["assessments", "{\"question_1\":\"yes\"}"], ["updated_at", "2014-08-21 00:52:03.581817"]]
(3.8ms)  COMMIT
=> true
  • What am I doing wrong in Example #1?
  • Why won't the database commit the change I made to the evaluation.assessment column when I call it, change the value, and call save on the instance?
Randy Burgess
  • 4,835
  • 6
  • 41
  • 59
  • 2
    This has been [fixed as of Rails 4.2.0](https://github.com/rails/rails/issues/6127#issuecomment-46791673). – vergenzt Jan 26 '17 at 21:48

1 Answers1

39

This ended up being an easy fix due to a known bug related to ActiveRecord not knowing the changed attribute is dirty:

https://github.com/rails/rails/issues/6127

Here's the solution:

>> evaluation.assessments["question_1"] = "yes"
=> "yes"

>> evaluation.assessments_will_change!
>> evaluation.save
(1.1ms)  BEGIN
SQL (1.6ms)  UPDATE "evaluations" SET "assessments" = $1, "updated_at" = $2 WHERE "evaluations"."id" = 4  [["assessments", "{\"question_1\":\"yes\"}"], ["updated_at", "2014-08-21 01:59:47.331216"]]
(2.5ms)  COMMIT
=> true
Randy Burgess
  • 4,835
  • 6
  • 41
  • 59
  • 2
    Thanks, I was struggling with this. Didn't even thought to consider ActiveRecord was at fault ... – Stan Mar 11 '15 at 16:53
  • 1
    I *think* that this problem/solution was rendered irrelevant with the Rails 4.2 update, but I'm not completely sure. – Randy Burgess Mar 12 '15 at 21:22
  • This was mind numbing. Couldn't figure out for the life of me why this wasn't working especially when it would persist on the model but not if I did a .reload – justinxreese Apr 01 '15 at 20:19
  • 2
    In Rails 5, a simple `merge!` followed by a `save` will do the trick - whether you are updating an existing key or adding a new one. – sameers Oct 21 '16 at 18:33