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

时间:2017-08-30 10:42:22

标签: ruby metaprogramming

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

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

为什么会这样,以及如何在实例范围内对其进行评估?

3 个答案:

答案 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