fguillen
fguillen

Reputation: 38772

Define singleton methods on initialization using instance variables

I'm trying to optimize some code and I want to instead on checking a value on every method call just define the method to respond with the checking already pre-calculate, because this checking doesn't change on the whole live of the instance.

I decided to define different versions of the method for every instance created. More or less this way:

class TestingSingletonMethodsWithVariable
  METHODS = %w(a b c d)

  def initialize(favorite_method)
    class << self
      METHODS.each do |method_name|
        if( favorite_method == method_name )
          define_method method_name do
            puts "#{method_name} its my favorite method"
          end
        else
          define_method method_name do
            puts "#{method_name} its not my favorite method"
          end
        end
      end
    end
  end
end

t = TestingSingletonMethodsWithVariable.new('b')
t.a
t.b
t.c
t.d

# $ ruby test/testing_singleton_methods_with_variable.rb 
# test/testing_singleton_methods_with_variable.rb:7:in `initialize': undefined local variable or method `favorite_method' for #<Class:#<TestingSingletonMethodsWithVariable:0x1001a77b8>> (NameError)
#   from test/testing_singleton_methods_with_variable.rb:6:in `each'
#   from test/testing_singleton_methods_with_variable.rb:6:in `initialize'
#   from test/testing_singleton_methods_with_variable.rb:21:in `new'
#   from test/testing_singleton_methods_with_variable.rb:21

What is happening is that something weird is happening with the variables: the variables declares out-side the class << self block are not visible for the variables inside.

Any one can explain me how can I do the behavior I'm looking for?

Thanks

Upvotes: 0

Views: 1780

Answers (2)

J&#246;rg W Mittag
J&#246;rg W Mittag

Reputation: 369438

In Ruby, only blocks can be closures, class bodies (as well as module and method bodies) cannot be closures. Or to put it another way: only blocks create a new nested lexical scope, all others (module bodies, class bodies, method bodies and script bodies) create new top-level scopes.

So, you will need a block. Normally, this would mean using some form of eval, but here you can just use define_singleton_method instead:

class TestingSingletonMethodsWithVariable
  METHODS = %w(a b c d)

  def initialize(favorite_method)
    METHODS.each do |method_name|
      if favorite_method == method_name
        define_singleton_method method_name do
          puts "#{method_name} its my favorite method"
        end
      else
        define_singleton_method method_name do
          puts "#{method_name} its not my favorite method"
        end
      end
    end
  end
end

t = TestingSingletonMethodsWithVariable.new('b')
t.a
t.b
t.c
t.d

Upvotes: 9

Chubas
Chubas

Reputation: 18043

Adding to Jörg's answer: define_singleton_method is Ruby 1.9+. If you want to run it in pre 1.9, the following works:

class Object
  def metaclass
    class << self; self; end
  end
end
class TestingSingletonMethodsWithVariable
  METHODS = %w(a b c d)

  def initialize(favorite_method)
    METHODS.each do |method_name|
      if( favorite_method == method_name )
        metaclass.send(:define_method, method_name, Proc.new do
          puts "#{method_name} its my favorite method"
        end)
      else
        metaclass.send(:define_method, method_name, Proc.new do
          puts "#{method_name} its not my favorite method"
        end)
      end
    end
  end
end

t = TestingSingletonMethodsWithVariable.new('b')
t.a
t.b
t.c
t.d

Upvotes: 1

Related Questions