为什么我的 define_method 内容在 class 范围内被评估?

Why is my define_method content being evaluated in the class scope?

我正在做一些(太)花哨的元编程,我很难理解为什么在以下两种情况下范围不同:

案例一:

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!

案例二

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 它自己。这样做的原因是,我想将动态方法的名称附加到这些方法中可能出现的任何异常消息。然而,问题是,内容(当使用后一种情况时)似乎在 class 范围内进行评估。

为什么会这样,我怎样才能让它在实例范围内得到评估?

这个区块:

do
  the_method
end

它在 class 范围内定义和限定范围。没有理由期望它会以某种方式注入实例范围。要解决此问题,只需显式传递接收器即可:

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

这是因为您在 class 方法 self.define_my_methods 的范围内提供块,其中 self 是 class,而不是实例。所以你可以做的是 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

那是因为用define_method定义的proc会被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