Ruby中的换行和解包方法

时间:2014-11-19 02:19:10

标签: ruby

我正在研究一个会窥探方法调用的库(SinonJS风格)。我想要做的是包装和解包方法。要包装方法,我可以将原始方法包装在一个块中:

module Spy
  def on(receiver, msg)
    @original = receiver.method(msg)
    wrapped = Proc.new {|*args| @original}
    receiver.define_singleton_method(msg, wrapped)
  end

  extend self
end

instance = Object.new
Spy.on(instance, :to_s)

这很好用,但解开这个方法是有问题的:

module Spy
  # Add this to the above

  def restore(receiver, msg)
    receiver.define_singleton_method(msg, @original)
  end
end

instance = Object.new
original = instance.method(:to_s)
Spy.on(instance, :to_s)
Spy.restore(instance, :to_s)
restored = instance.method(:to_s)

original == restored
=> false
original.object_id
=> 70317288647120
restored.object_id
=> 70317302643500

实际上,看起来Object#method总是会返回一个新的object_id。有没有办法让我将完全相同的方法重新连接到对象?在JS中,我只是存储函数并将其交换回原位。我误解了Ruby的一些东西吗?我可以使用另一种方法吗?我真的对用于测试的==比较器感兴趣

提前致谢!

编辑:

问题的简要版本:

irb(main):001:0> receiver = Object.new
=> #<Object:0x007fc4a1939320>
irb(main):002:0> original = receiver.method(:to_s)
=> #<Method: Object(Kernel)#to_s>
irb(main):003:0> original == receiver.method(:to_s)
=> true
irb(main):004:0> receiver.define_singleton_method(:to_s, original)
=> :to_s
irb(main):005:0> original == receiver.method(:to_s)
=> false
irb(main):006:0>

是否有另一种方法可以重新附加一个方法,使上述情况成立?

2 个答案:

答案 0 :(得分:1)

第一个问题是,您传递给define_singleton_method的广告在receiver的上下文中进行了评估,因此@original将为nil

您可以将方法复制到局部变量,以便该块将成为闭包

@original = receiver.method(msg)
org = @original
wrapped = Proc.new { |*args| org }

其次,该方法的对象ID无关紧要,您实际上期望它是不同的。看看你实际得到的方法:

instance.method(:to_s)
#<Method: Object(Kernel)#to_s>

Spy.on(instance, :to_s)
Spy.restore(instance, :to_s)

instance.method(:to_s)
#<Method: #<Object:0x00000001>.to_s>

该方法从Kernel更改为单例类!为什么ID会一样?但这并不重要,因为您所做的一切都是将方法从Kernel附加到单例类,因此无论如何都要运行相同的代码。

答案 1 :(得分:0)

看起来Mocha通过定义一个包装原始方法的新方法来实现这一点:https://github.com/freerange/mocha/blob/master/lib/mocha/class_method.rb#L75

我对此解决方案并非百分之百满意,但在大多数情况下它应该可以胜任。如果我可以取消附加并将方法重新附加到实例

,我仍然感兴趣

编辑:

事实证明,我可以使用Method#source_location代替我的测试。当这是间谍时,它将指向我的间谍类,当它没有被发现时,它指向原始实现

编辑2:

查看Max关于方法定义位置和挖掘Ruby Method对象的评论。我最终确定original.owner.instance_eval { define_method msg, wrapped }以包裹和original.owner.instance_eval { define_method msg, original }进行恢复。这适用于我使用Method#==测试

的原始测试