Store accessor for nested JSON

Michael Victor picture Michael Victor · Jun 7, 2015 · Viewed 13.2k times · Source

I have the model "Organization" that stores all information related to an organization. There is a field of type JSONB named "integrations" that stores information pertaining to all external service integrations that an organization has.

How do I use store accessors to access information stored in nested JSON, for example :

{
 "mailchimp": {"api_key":"vsvsvef", "list_id":"12345"},
 "sendgrid" : {"username":"msdvsv", "password":"123456"}
}

I know I can access mailchimp with store accessors like this :

store_accessor :integrations, :mailchimp

How can I easily access the api_key of mailchimp?

Answer

twonegatives picture twonegatives · Jun 7, 2015

You're right, unfortunately store_accessor does not allow you to access nested keys. The reason is that store_accessor is basically just a shortcut which defines getter and setter methods:

# here is a part of store_accessor method code, you can take a look at
# full implementation at
# http://apidock.com/rails/ActiveRecord/Store/ClassMethods/store_accessor
_store_accessors_module.module_eval do
  keys.each do |key|
    # here we define a setter for each passed key
    define_method("#{key}=") do |value|
      write_store_attribute(store_attribute, key, value)
    end

    # and here goes the getter
    define_method(key) do
      read_store_attribute(store_attribute, key)
    end
  end
end

So, your options here are:

  1. To implement your own set of getter and setter methods manually:

    # somewhere in your model
    def mailchimp_api_key
      self.mailchimp["api_key"]
    end
    
    def mailchimp_api_key= value
      self.mailchimp["api_key"] = value
    end
    

    This solves a problem, but you'd have to write lots of this repeatedly for each of nested attributes.

  2. To write your own helper method inside of ActiveRecord::Store::ClassMethods module which would define the same setter and getter methods dynamically for the set of attributes you pass in. You'd have to take the basic implementation of Rails store_accessor and add an additional hash keys iteration to it. Not sure if this is going to be an easy one, but would definitely be interesting to see shared as a gem.

  3. Leave Rails itself and use the power of postgres json type support with some pure SQL code. For example, you can access api_key attribute with something like that:

    SELECT integrations->'mailchimp'->>'api_key' as mailchimp_api_key FROM your_table_name;
    

    More on postgres json queries can be found here.