【问题标题】:Ruby: context switches when calling dynamically defined methodsRuby:调用动态定义的方法时的上下文切换
【发布时间】:2010-02-13 16:19:52
【问题描述】:

这里有一些测试代码可以解释我遇到的问题。 Child 类调用 Parent 类的方法。 Parent 的方法之一在 Parent 上定义了一个名为 foo 的新方法。在定义foo 之后,尝试从 Child 类调用它是可行的,但上下文完全不同(我只能访问 Child 的实例变量,而不是 Parent 的)。

我猜测这与 Ruby 的闭包有关。当我调用 define_method 时,我是否应该使用块以外的东西? 编辑:我尝试使用 lambda 和 proc,但它没有任何改变。

class Parent
  @foo = 'foo'

  def self.existing_method
    puts "Calling existing_method, @foo is #{@foo}"
  end

  def self.define_new_method
    self.class.send :define_method, :foo do
      context = methods.include?('bar') ? 'child' : 'parent'

      puts "Context is #{context}, @foo is #{@foo.inspect}"
    end
  end
end

class Child
  @foo = 'childfoo'

  def self.method_missing(*args, &block)
    Parent.send args.shift, *args, &block
  end

  def self.bar
  end
end

Child.existing_method    # Calling existing_method, @foo is foo
Child.define_new_method
Child.foo                # Context is child, @foo is "childfoo"
                         # (in Ruby 1.9, the context is parent, but
                         # @foo is still "childfoo")

Parent.foo               # Context is parent, @foo is "foo"

这个结果不是我想要的。 Child.foo的回复应该和Parent.foo的一样。

提前致谢!

【问题讨论】:

  • 我从你上面的例子中得到了不同的结果。我的 Child.foo 给出'上下文是父级,@foo 是“childfoo”'
  • 这真的很奇怪。我正在运行 Ruby 1.8.7(不是 1.9),所以这可能与差异有关。
  • 啊,是的。我目前正在运行 1.9.1。
  • 我可以确认差异出现在 1.8.7p174 和 1.9.1 之间。
  • ... 并且您在 1.8.7 头部中遇到了问题行为,所以我想说您将不得不转到 1.9 才能使其按预期工作。

标签: ruby metaprogramming


【解决方案1】:

经过大量挖掘,这是我想出的。

  1. 调用self.class.define_method实际上定义了Object的一个instance方法。这意味着 everything 收到了这个新方法,这就是明显的上下文转换的原因(Child.foo 实际上是在调用Child.foo 而不是Parent.foo)。哎呀。
  2. 为了定义一个类方法,你必须获取实际的类对象,由于某种原因,它不是self,而是class << self; self; end(是的,我也没有得到那个)。代码在这里简单提一下:http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html

Ruby 有时会让我的大脑受伤。此代码应返回预期结果。

class Parent
  @foo = 'foo'

  def self.existing_method
    puts "Calling existing_method, @foo is #{@foo}"
  end

  def self.define_new_method
    inst = class << self; self; end # Do not understand this...
    inst.send :define_method, :foo do
      context = methods.include?(:bar) ? 'child' : 'parent'
      puts "Context is #{context}, @foo is #{@foo.inspect}"
    end
  end
end

class Child
  @foo = 'childfoo'

  def self.method_missing(*args, &block)
    return unless args.length > 0
    Parent.send args.shift, *args, &block
  end

  def self.bar
  end
end

Child.existing_method     # Calling existing_method, @foo is foo
Child.define_new_method
Child.foo                 # Context is parent, @foo is "foo"
Parent.foo                # Context is parent, @foo is "foo"

【讨论】:

  • 这是一篇详细解释元类的为什么 Lucky Stiff 文章:dannytatom.github.com/metaid
  • 类 那是一个单例类,不需要添加类级别的方法。这取决于你的目标是什么。
【解决方案2】:

这很有趣;我认为 Yehuda Katz 今天的帖子正是解释了你想要什么:http://yehudakatz.com/2010/02/15/abstractqueryfactoryfactories-and-alias_method_chain-the-ruby-way/


编辑:好的 - 这不是“你想要的”,再想一想,因为它不是元编程,但它很可能更接近你想要做的事情。至于你的具体回答,我看一下代码。

【讨论】:

  • 感谢您的链接,Trevoke。有时间我会看一下帖子的。
【解决方案3】:

在 Ruby 1.9x 中,

'methods'方法返回Symbol(method name) Array

因此使用respond_to方法:

context = respond_to?(:bar) ? 'child' : 'parent'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-01
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多