I previously had:
serialize :params, JSON
But this would return the JSON and convert hash key symbols to strings. I want to reference the hash using symbols, as is most common when working with hashes. I feed it symbols, Rails returns strings. To avoid this, I created my own getter/setter. The setter is simple enough (JSON encode), the getter is:
  def params
    read_attribute(:params) || JSON.parse(read_attribute(:params).to_json).with_indifferent_access
  end
I couldn't reference params directly because that would cause a loop, so I'm using read_attribute, and now my hash keys can be referenced with symbols or strings. However, this does not update the hash:
model.params.merge!(test: 'test')
puts model.params # => returns default params without merge
Which makes me think the hash is being referenced by copy.
My question is twofold. Can I extend active record JSON serialization to return indifferent access hash (or not convert symbols to strings), and still have hash work as above with merge? If not, what can I do to improve my getter so that model.params.merge! works? 
I was hoping for something along the lines of (which works):
  def params_merge!(hash)
    write_attribute(:params, read_attribute(:params).merge(hash))
  end
  # usage: model.params_merge!(test: 'test')
Better yet, just get Rails to return a hash with indifferent access or not convert my symbols into strings! Appreciate any help.
use the built-in serialize method :
class Whatever < ActiveRecord::Base
 serialize :params, HashWithIndifferentAccess
end
see ActiveRecord::Base docs on serialization for more info.
Posting comment as answer, per @fguillen's request... Caveat: I am not typically a Rubyist… so this may not be idiomatic or efficient. Functionally, it got me what I wanted. Seems to work in Rails 3.2 and 4.0...
In application_helper.rb:
module ApplicationHelper
  class JSONWithIndifferentAccess
    def self.load(str)
      obj = HashWithIndifferentAccess.new(JSON.load(str))
      #...or simply: obj = JSON.load(str, nil, symbolize_names:true)
      obj.freeze #i also want it set all or nothing, not piecemeal; ymmv
      obj
    end
    def self.dump(obj)
      JSON.dump(obj)
    end
  end
end
In my model, I have a field called rule_spec, serialized into a text field:
serialize :rule_spec, ApplicationHelper::JSONWithIndifferentAccess
Ultimately, I realized I just wanted symbols, not indifferent access, but by tweaking the load method you can get either behavior.
I ended up using a variation on bimsapi's solution that you can use not only with simple un-nested JSON but any JSON.
Once this is loaded...
module JsonHelper
  class JsonWithIndifferentAccess
    def self.load(str)
      self.indifferent_access JSON.load(str)
    end
    def self.dump(obj)
      JSON.dump(obj)
    end
    private
      def self.indifferent_access(obj)
        if obj.is_a? Array
          obj.map!{|o| self.indifferent_access(o)}
        elsif obj.is_a? Hash
          obj.with_indifferent_access
        else
          obj
        end
      end
  end
end
then instead of calling
JSON.load(http_response)
you just call
JsonHelper::JsonWithIndifferentAccess.load(http_response)
Does the same thing but all the nested hashes are indifferent access.
Should serve you well but think a little before making it your default approach for all parsing as massive JSON payloads will add significant ruby operations on top of the native JSON parser which is optimised in C and more fully designed for performance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With