2

I am trying to dynamically define methods on an initialized Ruby object MyObject based on a hash my_hash I pass to its initialize method. In the body of the initialize method, I have the following:

my_hash.each do |key|
  class << self
    define_method(key.underscore.to_sym) do
      my_hash[key]
    end
  end
end

This fails with a undefined local variable or method 'key' for #<Class:#<MyObject:0x007fc7abw0cra0>>. Any ideas why?

The my_hash is made out of a json response with a lot of camelized keys, so it's more convenient to have simple ruby methods to get the values I want.

n_x_l
  • 1,552
  • 3
  • 17
  • 34
  • 1
    Convert your hash to OpenStruct, that will create a method for each of your key. – Raj Jan 25 '15 at 13:54
  • But that won't make the methods available on the MyObject object would it? Unless I somehow redefine method_missing to pass the calls to the OpenStruct object. – n_x_l Jan 25 '15 at 13:56

2 Answers2

4

Local variables are local to the scope they are defined in. The (lexical) scopes in Ruby are script scope, module/class definition scope, method definition scope and block scope. Only block scopes nest in their outer scopes (aka close over their surrounding scope). So, you have to use a block:

my_hash.each_key do |key|
  singleton_class.class_eval do
    define_method(key.to_sym) do
      my_hash[key]
    end
  end
end

Or even better yet:

my_hash.each_key do |key|
  define_singleton_method(key.underscore.to_sym) do
    my_hash[key]
  end
end

Note: I also fixed a bug, you should be iterating the keys with each_key, not each (which is an alias for each_pair).

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
2

You can use define_singleton_method rather than class << self. Also note that Hash#each yields a two element array with the key and value.

my_hash.each do |key, val|
  define_singleton_method(key.underscore.to_sym) do
    val
  end
end
matt
  • 78,533
  • 8
  • 163
  • 197