Reputation: 8825
I'm doing some (too) fancy meta programming, and I have a hard time understanding why the scope is different in the following two cases:
Case 1:
class TesterA
def the_method
puts "I'm an instance_method!"
end
def self.the_method
puts "I'm a class_method!"
end
def self.define_my_methods *method_names
method_names.each do |name|
define_method("method_#{name}") do
the_method
end
end
end
define_my_methods :a, :b, :c
end
t = TesterA.new
t.method_a #=> I'm an instance_method!
t.method_b #=> I'm an instance_method!
t.method_c #=> I'm an instance_method!
Case 2
class TesterB
def the_method
puts "I'm an instance_method!"
end
def self.the_method
puts "I'm a class_method!"
end
def self.define_the_method attr
define_method("method_#{attr}") do
begin
yield
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do
the_method
end
end
end
define_my_methods :a, :b, :c
end
t = TesterB.new
t.method_a #=> I'm a class_method!
t.method_b #=> I'm a class_method!
t.method_c #=> I'm a class_method!
I the second example I introduce a kind of "helper-mothod" define_the_method
which I use for defining the methods rather than define_method
it self. Reason for that is, That I want to append the name of the dynamic method to any exception messages that might occur inside those methods. The problem however is, that the content (when using the latter case) seems to be evaluated in the class-scope.
Why is this, and how can I make it get evaluated in the instance-scope?
Upvotes: 3
Views: 201
Reputation: 44581
It happens because you are providing the block in the scope of the class method self.define_my_methods
, where self
is a class, not an instance. So what you can do is yield
the scope of the define_method
itself:
def self.define_the_method attr
define_method("method_#{attr}") do
begin
yield self
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do |scope|
scope.send(:the_method)
end
end
end
Upvotes: 4
Reputation: 30453
That's because proc
defined with define_method
will be called by using instance_eval
.
In the first sample it is:
do
the_method
end
In second:
do
begin
yield
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
But yield
will have parent scope from the place it was defined.
Here is my suggestion:
def self.define_the_method attr, &block
define_method("method_#{attr}") do
begin
instance_eval(&block)
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end
Upvotes: 2
Reputation: 121000
This block:
do
the_method
end
it defined and scoped in class-scope. There is no reason to expect it’ll be somehow injected into instance-scope. To fix the issue simply pass the receiver explicitly:
yield(self)
and:
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do |receiver|
receiver.the_method
end
end
end
Upvotes: 4