Javascript-style object-value assignment in Ruby

While I was working in Javascript extensively for a project, one of the things I really liked about it was how you could use the dot notation to access members of an object, just as you would call methods. After that, I would often find myself in an IRB shell, absent-mindedly trying to do the same thing with a Ruby Hash, only to be thrown a NoMethodError. The first reaction might be: “Ah, if only Ruby had this feature like Javascript does.”, but of course, it can!

Thanks to the meta-programming capabilities of Ruby, it is very easy to implement such a feature. One of these features is that if a method is called on an object  (or in classic OO parlance, if the object is sent a message) that is not defined, then the interpreter will look for a method called method_missing, first starting on the current class, then traversing the ancestry to the top, until it reaches Kernel, where the implementation of method_missing throws an error.

By implementing method_missing, you can provide a method (or at least a return value) for the user at runtime, even though no method was defined. In fact, this is what ORMs such as ActiveRecord do, because the mappings to the actual field names around a database table are not defined in a wrapper class, but they are inferred at runtime.

So, back to our Hash. When our hash has elements, we want to be able to return these to the caller when they call hash_object.element. Similarly, if the user calls hash_object.element = value, we want to create a key with the given value, just as this syntax would do in Javascript. Ruby is very generous in the way it allows us to modify the standard library, so all we need to do is open up Hash, add our method_missing implementation and go to work.

class Hash
  def method_missing(meth, *args, &block)
    meth_name = meth.to_s
    if meth_name[-1,1] == '='
      self[meth_name[0..-2].to_sym] = args[0]
    else
      self[meth] || self[meth_name]
    end
  end
end

Note: The missing method name is passed into method_missing as a symbol, so we convert it to a String to enable us to check whether it ends with an equals sign in which case we add the value in the args using the method name (minus the trailing ‘=’, converted to a symbol) as the key. When no trailing equals sign is present, we return the value of the key identified by the method name, either as a string or symbol.

You can put the above code into a source file and require it, or just paste it into an IRB shell. Let’s put it to the test:

irb(main):001:0> h = {}
=> {}
irb(main):002:0> h.b
=> nil
irb(main):003:0> h.b = 5
=> 5
irb(main):004:0> h
=> {:b=>5}
irb(main):006:0> h["greeting"] = "Yo!"
=> "Yo!"
irb(main):007:0> h.greeting
=> "Yo!"
irb(main):008:0> h
=> {:b=>5, "greeting"=>"Yo!"}
irb(main):009:0>

Now wasn’t that nice and easy?

 

Tags: