如何修复此NameError?

时间:2010-06-15 13:22:11

标签: ruby scope metaprogramming

我想在特定对象的元类上使用实例方法中的值v

v = ParserMap[kind][:validation]   # We want to use this value later.
s = ParserMap[kind][:specs]
const_set(name, lambda {
  p = Parser.new(&s)

  # This line starts a new scope...
  class << p
    define_method :validate do |opts|
      v.call(self, opts)  # => NameError! The `class` keyword above
                          #    has started a new scope and we lost
                          #    old `v`.
    end
  end
  p
})

不幸的是,class关键字启动了一个新的范围,所以我丢失了旧的范围,我得到了一个N​​ameError。我该如何解决这个问题?

3 个答案:

答案 0 :(得分:1)

class << p替换为class << p; self end.class_eval do,它将有效。

class << p; self end将返回p的元类,因此您可以在其上调用class_eval。然后,给class_eval的块将在元类的上下文中执行(与之前相同),但不启动新的作用域。

答案 1 :(得分:1)

您的第一个倾向可能是在class_eval使用p,如下所示:

p.class_eval {
  ...
}

唉,这不起作用,因为class_evalModule上定义的方法,而不是Object上定义的方法。由于p是对象的实例,而不是ModuleClass的实例,因此它没有class_eval方法。

诀窍是首先获得p的单例类,然后在其上运行class_eval。由于 Class(扩展名为Module),因此它具有class_eval方法。如果您使用的是1.9.2或更高版本,则可以使用singleton_class方法:

p.singleton_class.class_eval {
  ...
}

否则,你可以直接获得单例类:

(class << p; self; end).class_eval {
  ...
}

正如Jorg所指出的,你也可以使用define_singleton_method

p.define_singleton_method :validate { |opts|
  v.call(self, opts)
}

但请注意,如果您这样做,生成的validate方法将为private,这可能不是您想要的。

答案 2 :(得分:0)

只是为了踢,这是Ruby 1.9.2中的样子:

v = ParserMap[kind][:validation]
s = ParserMap[kind][:specs]
const_set(name, ->{
  Parser.new(&s).tap {|p|
    p.define_singleton_method :validate do |opts| v.(self, opts) end
  }
})
  • 使用Ruby 1.8.7和1.9.0中引入的K组合子(p)替换最后Object#tap的显式返回
  • lambda方法调用替换为Ruby 1.9.0中引入的proc文字
  • obj.call(args)替换为Ruby 1.9.0中引入的obj.(args)
  • 最重要的是:在Ruby 1.9.2中引入(或更确切地说:将要介绍)使用Object#define_singleton_method