0

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

1
  • I think there's some mixup with your example. In the subject line you mention instance variables, but there are no instance variables anywhere in the code you posted. Commented Nov 16, 2010 at 17:56

2 Answers 2

9

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
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks very much @Jörg, I am gonna accept the answer of @Chubas just because in it are your both answers and I think is important to people with my same problem to read both answers.
I would suggest changing the accepted answer to this one - I doubt many people are pre-1.9 still, so the workaround is not necessary :)
1

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

1 Comment

Please read the @Jörg answer because in it is the Ruby 1.9+ solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.