我正在做一些(太)花哨的元编程,我很难理解为什么范围在以下两种情况下有所不同:
案例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!
案例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!
我在第二个例子中介绍了一种“helper-mothod”define_the_method
,我用它来定义方法,而不是define_method
它自己。原因是,我想将动态方法的名称附加到可能在这些方法中发生的任何异常消息。然而问题是,内容(当使用后一种情况时)似乎在类范围内进行评估。
为什么会这样,以及如何在实例范围内对其进行评估?
答案 0 :(得分:4)
这个区块:
do
the_method
end
它在类范围内定义和作用域。没有理由期望它会以某种方式注入实例范围。要解决这个问题,只需明确传递接收器:
yield(self)
和
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do |receiver|
receiver.the_method
end
end
end
答案 1 :(得分:4)
之所以发生这种情况,是因为您在类方法self.define_my_methods
的范围内提供了块,其中self
是一个类,而不是一个实例。所以你可以做的是yield
define_method
本身的范围:
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
答案 2 :(得分:2)
这是因为proc
定义的define_method
将使用instance_eval
来调用。
在第一个样本中:
do
the_method
end
第二名:
do
begin
yield
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
但是yield
将在其定义的位置具有父范围。
这是我的建议:
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