ActiveRecord serialize using JSON instead of YAML

Viewed 46892

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?

8 Answers

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.

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!

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
Related