58

This is my hash:

tempData = {"a" => 100, "here" => 200, "c" => "hello"}

I need to access the hash keys as a method like:

tempData.a #100
tempData.here # 200
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Sreeraj
  • 2,690
  • 6
  • 26
  • 37

5 Answers5

104

You could just wrap up your hash in an OpenStruct:

require 'ostruct'
tempData = {"a" => 100, "here" => 200, "c" => "hello"}
os = OpenStruct.new tempData
os.a #=> 100
os.here #=> 200

If you really really wanted to, you could also monkey-patch the Hash class, but I'd advise against that:

class Hash
  def method_missing(m, *args, &blk)
    fetch(m) { fetch(m.to_s) { super } }
  end
end

tempData = {"a" => 100, "here" => 200, "c" => "hello"}
tempData.a #=> 100

Update: In my personal extensions library I added a Hash#to_ostruct method. This will recursively convert a hash into an OpenStruct including all nested hashes.

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • I would add that the second solution is fine if you define a new class instead of overwriting the core Hash class. – Adrien Coquio Jun 21 '11 at 10:18
  • 1
    @AdrienCoquio that is basically what OpenStruct is doing – T0xicCode Jan 20 '12 at 03:27
  • An [interesting post](http://pullmonkey.com/2008/01/06/convert-a-ruby-hash-into-a-class-object/), which does the same thing, but needs to write a small class.. – rubyprince Mar 16 '12 at 21:40
  • 1
    Thanks for the link. If you read the comments you'll see that the posts' author said that `OpenStruct` handles all of his initial implementation, but not nested hashes as the version he posted in the comments. – Michael Kohl Mar 17 '12 at 13:27
  • @MichaelKohl..yes..u r right..I used his version of nested hash, in a project where there was a serialized column (which converts to a nested hash in Ruby)and needed it for my Rails form elements having (object, method) syntax.. – rubyprince Mar 27 '12 at 09:17
21

There is another way to do this.

JSON.parse(tempData.to_json, object_class: OpenStruct)

that will give object #<OpenStruct a=100, here=200, c="hello">

In this way nested hash also will be converted to OpenStruct Object

tempData = {a: { b: { c: 3}}, foo: 200, msg: 'test msg'}
obj = JSON.parse(tempData.to_json, object_class: OpenStruct)

Now we are able to call

obj.a.b.c # 3
obj.foo # 200
obj.msg # 'test msg'

Hope this will help someone.

Engr. Hasanuzzaman Sumon
  • 2,103
  • 3
  • 29
  • 38
  • This is quite useful to load test data stored as json into object stubs - instead of tempData.to_json the JSON is loaded from a file. – Marvin Jan 26 '23 at 22:36
8

Alternatively, if it’s just a small script it might be more convenient to just extend Hash itself

class Hash
  def method_missing sym,*
    fetch(sym){fetch(sym.to_s){super}}
  end
end

method_missing is a magic method that is called whenever your code tries to call a method that does not exist. Ruby will intercept the failing call at run time and let you handle it so your program can recover gracefully. The implementation above tries to access the hash using the method name as a symbol, the using the method name as a string, and eventually fails with Ruby's built-in method missing error.

NB for a more complex script, where adding this behavior might break other third-party gems, you might alternatively use a module and extend each instance

module H
  def method_missing sym,*
    fetch(sym){fetch(sym.to_s){super}}
  end
end

the = { answer: 42 }
the.extend(H)
the.answer # => 42

and for greater convenience you can even propagate the module down to nested hashes

module H
  def method_missing sym,*
    r = fetch(sym){fetch(sym.to_s){super}}
    Hash === r ? r.extend(H) : r
  end
end 

the = { answer: { is: 42 } }
the.extend(H)
the.answer.is # => 42
akuhn
  • 27,477
  • 2
  • 76
  • 91
  • This is fine as long as your keys aren't messages to which the native Hash already responds. For instance, try: `the = { answer: { display: 42 } }` – Mark Schneider May 03 '22 at 20:42
3

If the hash is inside a module, you can define methods on that module (or class) dynamically using define_method. For example:

module Version
  module_function

  HASH = { 
    major: 1,
    minor: 2,
    patch: 3,
  }

  HASH.each do |name, value|
    define_method(name) do
      return value
    end
  end
end

This will define a Version module with major, minor, and patch methods that return 1, 2, and 3, respectively.

sferik
  • 1,795
  • 2
  • 15
  • 22
1

you can extend the Hash class in the following way.

class Hash
      # return nil whenever the key doesn't exist
      def method_missing(m, *opts)
        if self.has_key?(m.to_s)
          return self[m.to_s]
        elsif self.has_key?(m.to_sym)
          return self[m.to_sym]
        end
        return nil

        # comment out above line and replace with line below if you want to return an error
        # super
      end
end
lsu_guy
  • 1,525
  • 15
  • 12