JSON like ruby object

07Mar07

Recently when creating a rails backed dsl for interacting with Quickbooks SDK, I found myself desiring the ability to have json like behavior on a ruby object. By this I mean being able to create an object with attributes and subobjects on the fly and then being able to access them easily. My solution and with quite a bit of help from jadams was first to reopen the Hash class and insert a method missing to allow for accessing hash[‘attribute’] with the more rubyesque hash.attribute. The technique and my first ever metaprogramming was inspired by Ola Bini’s excellent blog post, specifically method 4 which uses an example from why’s fantastic camping project.

class Hash
  def method_missing(m,*a)
    if m.to_s =~ /=$/
      self[$`] = a[0]
    elsif a.empty?
      self[m]={} unless self[m]
      self[m]
    else
      raise NoMethodError, "#{m}"
    end
  end
end

The next step was simply to create a generic object with an attr_accessor to create a hash:

class JSONLikeObject
  attr_accessor :type, :attributes
  def initialize(type, attributes = {})
    @type = type
    @attributes = attributes
  end
end

Great! Now I’m able to say foo = JsonLikeObject.new and then say foo.attributes.attribute to access foo.attributes[‘attribute’]. Closer, but I really wanted to be able to say foo.attribute, so I defined method missing for the JSONLikeObject class:

def method_missing(m,*a)
    if m.to_s =~ /=$/
      @attributes.send(m,a[0])
    else
      @attributes[m]={} unless @attributes[m]
      @attributes[m]
    end
  end

Mission accomplished, but I really would like a more complicated/richer tree. For example, customer = JSONLikeObject.new and be able to write customer.contact.address.line1 = ‘Ruby Drive’ and it create customer.attributes[‘contact’][‘address’][‘line1’] = ‘Ruby Drive’ without blowing up. Off to read Ola’s post again and there in example 1 lies the solution:

module Kernel
  def singleton_class
    class << self; self; end
  end
end

Now it is simply a matter of taking the method missing from the class and wrapping it in a module that can be included upon initialization:

class Hash
  def method_missing(m,*a)
    if m.to_s =~ /=$/
      self[$`] = a[0]
    elsif a.empty?
      self[m]={} unless self[m]
      self[m]
    else
      raise NoMethodError, "#{m}"
    end
  end
end
module Kernel
  def singleton_class
    class << self; self; end
  end
end
module Mod
  def method_missing(m,*a)
    if m.to_s =~ /=$/
      @attributes.send(m,a[0])
    else
      @attributes[m]={} unless @attributes[m]
      @attributes[m]
    end
  end
end
class JSONLikeObject
  attr_accessor :type, :attributes
  def initialize(type, attributes = {})
    @type = type
    @attributes = attributes
    singleton_class.class_eval do
      include Mod
    end
  end
end

And bingo, a json like object in ruby.

Advertisements


No Responses Yet to “JSON like ruby object”

  1. Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: