来自外部作用域的Ruby变量在一个块内未定义-为什么?

时间:2018-08-13 14:19:01

标签: ruby

这部分代码动态创建了几个类:

(1..MAX_ACT).each do |act_id|
  klass = Class.new(ActB) do
    def initialize(trg)
      super(trg, act_id)
    end
  end
  Object.const_set("Act#{act_id}", klass)
end

在这种情况下,公共基类(ActB)的构造函数带有两个参数,而子类的构造函数带有一个参数。

运行此代码效果很好,但是例如,当我稍后尝试实例化这些类之一时,

Act3.new(4)

我收到错误消息

NameError: undefined local variable or method `act_id' for #<Act3:0x00000006008b7990>

错误消息必须引用该行

super(trg, act_id)

因为这是程序中我使用的唯一位置。但是,此变量在上面说了几行时就定义了

(1..MAX_ACT).each do |act_id|

我曾期望, do ... end 块为绑定 act_id 的构造函数创建一个闭合。但是,情况似乎并非如此。

为什么我的示例不起作用?我该如何正确做?

3 个答案:

答案 0 :(得分:4)

def(以及classmodule)创建一个新的本地范围,该范围不会从外部继承任何本地变量。

因此,您对Class.new do .. end创建了一个闭包是正确的……但是内部def不共享它。

如果您需要标准块行为,则可以改用define_method

(1..MAX_ACT).each do |act_id|
  klass = Class.new(ActB) do
    define_method :initialize do |trg|
      super(trg, act_id)
    end
  end
  Object.const_set("Act#{act_id}", klass)
end

答案 1 :(得分:1)

出于好奇,这里有一个黑客,可以愚弄作用域并仍然使用def initialize:)

class ActB
  def initialize(trg, act_id)
    puts "ActID: #{act_id}"
  end 
end
(1..MAX_ACT).each do |act_id|
  klass = Class.new(ActB) do
    @act_id = act_id
    def initialize(trg)
      super(trg, self.class.instance_variable_get(:@act_id))
    end 
  end 
  Object.const_set("Act#{act_id}", klass)
end

Act1.new :foo
#⇒ ActID: 1
Act2.new :foo
#⇒ ActID: 2

答案 2 :(得分:0)

这里的问题是传递给Class.new的块是在该类的上下文中执行的。在该类的上下文中,未定义act_id。因此,要解决此问题,可以将方法定义移到类初始化之外,如下所示:

(1..MAX_ACT).each do |act_id|
  klass = Class.new(ActB)
  klass.define_method(:initialize) do |trg|
    super(trg, act_id)
  end
  Object.const_set("Act#{act_id}", klass)
end