ActiveRecord serialize using JSON instead of YAML

Ruby on-RailsJsonActiverecordYamlSerialization

Ruby on-Rails Problem Overview


I have a model that uses a serialized column:

class Form < ActiveRecord::Base
  serialize :options, Hash
end

Is there a way to make this serialization use JSON instead of YAML?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

In Rails 3.1 you can just

class Form < ActiveRecord::Base
  serialize :column, JSON
end

Hope that helps

Solution 2 - Ruby on-Rails

In Rails 3.1 you can use custom coders with serialize.

class ColorCoder
  # Called to deserialize data to ruby object.
  def load(data)
  end

  # Called to convert from ruby object to serialized data.
  def dump(obj)
  end
end

class Fruits < ActiveRecord::Base
  serialize :color, ColorCoder.new
end

Hope this helps.

References:

Definition of serialize: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/base.rb#L556

The default YAML coder that ships with rails: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/coders/yaml_column.rb

And this is where the call to the load happens: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/read.rb#L132

Solution 3 - Ruby on-Rails

Update

See mid's high rated answer below for a much more appropriate Rails >= 3.1 answer. This is a great answer for Rails < 3.1.

Probably this is what you're looking for.

Form.find(:first).to_json

Update

  1. Install 'json' gem:

    gem install json

  2. Create JsonWrapper class

    lib/json_wrapper.rb

    require 'json' class JsonWrapper def initialize(attribute) @attribute = attribute.to_s end

    def before_save(record) record.send("#{@attribute}=", JsonWrapper.encrypt(record.send("#{@attribute}"))) end

    def after_save(record) record.send("#{@attribute}=", JsonWrapper.decrypt(record.send("#{@attribute}"))) end

    def self.encrypt(value) value.to_json end

    def self.decrypt(value) JSON.parse(value) rescue value end end

  3. Add model callbacks:

    #app/models/user.rb

    class User < ActiveRecord::Base before_save JsonWrapper.new( :name ) after_save JsonWrapper.new( :name )

     def after_find
       self.name = JsonWrapper.decrypt self.name
     end
    

    end

  4. Test it!

    User.create :name => {"a"=>"b", "c"=>["d", "e"]}

#PS: It's not quite DRY, but I did my best. If anyone can fix after_find in User model, it'll be great.

Solution 4 - Ruby on-Rails

My requirements didn't need a lot of code re-use at this stage, so my distilled code is a variation on the above answer:

  require "json/ext"
  
  before_save :json_serialize  
  after_save  :json_deserialize
 

  def json_serialize    
    self.options = self.options.to_json
  end

  def json_deserialize    
    self.options = JSON.parse(options)
  end
    
  def after_find 
    json_deserialize        
  end  

Cheers, quite easy in the end!

Solution 5 - Ruby on-Rails

The serialize :attr, JSON using composed_of method works like this:

  composed_of :auth,
              :class_name => 'ActiveSupport::JSON',
              :mapping => %w(url to_json),
              :constructor => Proc.new { |url| ActiveSupport::JSON.decode(url) }

where url is the attribute to be serialized using json and auth is the new method available on your model that saves its value in json format to the url attribute. (not fully tested yet but seems to be working)

Solution 6 - Ruby on-Rails

I wrote my own YAML coder, that takes a default. Here is the class:

class JSONColumn
  def initialize(default={})
    @default = default
  end
 
  # this might be the database default and we should plan for empty strings or nils
  def load(s)
    s.present? ? JSON.load(s) : @default.clone
  end
 
  # this should only be nil or an object that serializes to JSON (like a hash or array)
  def dump(o)
    JSON.dump(o || @default)
  end
end

Since load and dump are instance methods it requires an instance to be passed as the second argument to serialize in the model definition. Here's an example of it:

class Person < ActiveRecord::Base
  validate :name, :pets, :presence => true
  serialize :pets, JSONColumn.new([])
end

I tried creating a new instance, loading an instance, and dumping an instance in IRB, and it all seemed to work properly. I wrote a blog post about it, too.

Solution 7 - Ruby on-Rails

A simpler solution is to use composed_of as described in this blog post by Michael Rykov. I like this solution because it requires the use of fewer callbacks.

Here is the gist of it:

composed_of :settings, :class_name => 'Settings', :mapping => %w(settings to_json),
                       :constructor => Settings.method(:from_json),
                       :converter   => Settings.method(:from_json)

after_validation do |u|
  u.settings = u.settings if u.settings.dirty? # Force to serialize
end

Solution 8 - Ruby on-Rails

Aleran, have you used this method with Rails 3? I've somewhat got the same issue and I was heading towards serialized when I ran into this post by Michael Rykov, but commenting on his blog is not possible, or at least on that post. To my understanding he is saying that you do not need to define Settings class, however when I try this it keeps telling me that Setting is not defined. So I was just wondering if you have used it and what more should have been described? Thanks.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionToby HedeView Question on Stackoverflow
Solution 1 - Ruby on-RailsJustas L.View Answer on Stackoverflow
Solution 2 - Ruby on-RailsbaluView Answer on Stackoverflow
Solution 3 - Ruby on-RailsSt.WolandView Answer on Stackoverflow
Solution 4 - Ruby on-RailsToby HedeView Answer on Stackoverflow
Solution 5 - Ruby on-RailscaribuView Answer on Stackoverflow
Solution 6 - Ruby on-RailsBenjamin AtkinView Answer on Stackoverflow
Solution 7 - Ruby on-RailsAleranView Answer on Stackoverflow
Solution 8 - Ruby on-RailsAndrew LankView Answer on Stackoverflow