Trinopoty
Trinopoty

Reputation: 423

How to get rid of surrounding quotes in Rails?

I'm having problems with weird behaviour in RoR. I'm having a Hash that i'm converting to json using to_json() like so:

data = Hash.new
# ...
data = data.to_json()

This code appears inside a model class. Basically, I'm converting the hash to JSON when saving to database. The problem is, the string gets saved to database with its surrounding quotes. For example, saving an empty hash results in: "{}". This quoted string fails to parse when loading from the database.

How do I get rid of the quotes?

The code is:

def do_before_save
  @_data = self.data
  self.data = self.data.to_json()
end

EDIT: Due to confusions, I'm showing my entire model class

require 'json'
class User::User < ActiveRecord::Base
    after_find { |user|
        user.data = JSON.parse(user.data)
    }
    after_initialize { |user|
        self.data = Hash.new unless self.data
    }
    before_save :do_before_save
    after_save :do_after_save
    private
        def do_before_save
            @_data = self.data
            self.data = self.data.to_json()
        end
        def do_after_save
            self.data = @_data
        end
end

The data field is TEXT in mysql.

Upvotes: 3

Views: 1611

Answers (4)

userkhan
userkhan

Reputation: 66

use {}.as_json instead of {}.to_json

ex:

 a = {}
 a.as_json # => {}
 a.to_json # => "{}"

http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

Upvotes: 0

max
max

Reputation: 102249

If you are saving hash as a JSON string in a varchar column you can use serialize to handle marshalling/unmarshaling the data:

class Thing < ActiveRecord::Base
  serialize :foo, JSON
end

Knowing exactly when to convert the data in the lifecycle of a record is actually quite a bit harder than your naive implementation. So don't reinvent the wheel.

However a huge drawback is that the data cannot be queried in the DB*. If you are using Postgres or MySQL you can instead use a JSON or JSONB (postgres only) column type which allows querying. This example is from the Rails guide docs:

# db/migrate/20131220144913_create_events.rb
create_table :events do |t|
  t.json 'payload'
end

# app/models/event.rb
class Event < ApplicationRecord
end

# Usage
Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]})

event = Event.first
event.payload # => {"kind"=>"user_renamed", "change"=>["jack", "john"]}

## Query based on JSON document
# The -> operator returns the original JSON type (which might be an object), whereas ->> returns text
Event.where("payload->>'kind' = ?", "user_renamed")

Upvotes: 0

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230521

I'm willing to bet money that this is the result of you calling .to_json on the same data twice (without parsing it in between). I've had a fair share of these problems before I devised a rule: "don't mutate data in a lossy way like this".

If your original data was {}, then first .to_json would produce "{}". But if you were to jsonify it again, you'd get "\"{}\"" because a string is a valid json data type.

I suggest that you put a breakpoint in your before_save filter and see who's calling it the second time and why.

Update

"call .to_json twice" is a general description and can also mean that there are two subsequent saves on the same object, and since self.data is reassigned, this leads to data corruption. (thanks, @mudasobwa)

Upvotes: 4

maicher
maicher

Reputation: 2745

It depends on your model's database field type.

If the field is string type (like VARCHAR or TEXT) it should be stored as string (no need to get rid of the quotes - they are fine). Make sure calling to_json once.

If the field is Postgres JSON type, then you can just assign a hash to the model's field, no need to call to_json at all.

Upvotes: 0

Related Questions