views:

471

answers:

5

I have a a hash

foo = {'bar'=>'baz'}

I would like to call foo.bar #=> 'baz'

My motivation is rewriting an activerecord query into a raw sql query (using Model#find_by_sql). This returns a hash with the SELECT clause values as keys. However, my existing code relies on object.method dot notation. I'd like to do minimal code rewrite. Thanks.

Edit: it appears Lua has this feature:

point = { x = 10, y = 20 }   -- Create new table
print(point["x"])            -- Prints 10
print(point.x)               -- Has exactly the same meaning as line above
+1  A: 

I wrote the code for this a long time ago on a different question here. This is the code to convert the hash to an object:

class Hash
  def to_obj
    self.inject(Object.new) do |obj, ary| # ary is [:key, "value"]
      obj.instance_variable_set("@#{ary[0]}", ary[1])
      class << obj; self; end.instance_eval do # do this on obj's metaclass
        attr_reader ary[0].to_sym # add getter method for this ivar
      end
      obj # return obj for next iteration
    end
  end
end

h = {:foo => "bar", :baz => "wibble"}
o = h.to_obj # => #<Object:0x30bf38 @foo="bar", @baz="wibble">
o.foo # => "bar"
o.baz # => "wibble"
jtbandes
+5  A: 

Rather than copy all the stuff out of the hash, you can just add some behaviour to Hash to do lookups.

If you add this defintion, you extend Hash to handle all unknown methods as hash lookups:

class Hash
  def method_missing(n)
    self[n.to_s]
  end
end

Bear in mind that this means that you won't ever see errors if you call the wrong method on hash - you'll just get whatever the corresponding hash lookup would return.

You can vastly reduce the debugging problems this can cause by only putting the method onto a specific hash - or as many hashes as you need:

a={'foo'=>5, 'goo'=>6}
def a.method_missing(n)
   self[n.to_s]
end

The other observation is that when method_missing gets called by the system, it gives you a Symbol argument. My code converted it into a String. If your hash keys aren't strings this code will never return those values - if you key by symbols instead of strings, simply substitute n for n.to_s above.

cartoonfox
Someone did this early on in a large project I worked on. It caused all kinds of subtle bugs because incorrect method calls on any hash in the system would just return nil instead of alerting us with a NoMethodError. It took forever to remove from the system because code all over the system had come to depend on it. It's one of the most disastrous code-class extensions I've ever seen.
Avdi
Er, that should have read "coRe-class extensions".
Avdi
To (mostly) fix that problem, you could do `if has_key? n.to_s then self[n.to_s] else raise NoMethodError`
jtbandes
Avdi - I'd certainly agree that abuse/overuse of metaprogramming is a problem. There's probably a better way to solve the overall problem (OpenStruct probably) but I just got it working as an exercise.
cartoonfox
+8  A: 

What you're looking for is called OpenStruct. It's part of the standard library.

Avdi
+6  A: 
>> require 'ostruct'
=> []
>> foo = {'bar'=>'baz'}
=> {"bar"=>"baz"}
>> foo_obj = OpenStruct.new foo
=> #<OpenStruct bar="baz">
>> foo_obj.bar
=> "baz"
>>
Hooopo
+1  A: 

A good solution:

class Hash
  def method_missing(method, *opts)
    m = method.to_s
    if self.has_key?(m)
      return self[m]
    elsif self.has_key?(m.to_sym)
      return self[m.to_sym]
    end
    super
  end
end

Note: this implementation has only one known bug:

x = { 'test' => 'aValue', :test => 'bar'}
x.test # => 'aValue'

If you prefer symbol lookups rather than string lookups, then swap the two 'if' condition

Gabor Garami