我正在编写一个用spies包装方法的库(基本上透明地允许你向方法添加回调)。我目前在处理绑定方法时工作正常
class FakeClass
def self.hello_world
'hello world'
end
def age
25
end
end
module Spy
def self.on(receiver, msg)
original = receiver.method(msg)
wrapped = Proc.new {|*args| original.call(*args)}
original.owner.instance_eval do
define_method msg, wrapped
end
end
end
# works
Spy.on(FakeClass, :hello_world)
# also works
Spy.on(FakeClass.new, :age)
# what I want to do
Spy.on_any_instance(FakeClass, :age)
到目前为止,我注意到了一些事情:
FakeClass.methods.include? :age
# => false
FakeClass.instance_method :age
# => #<UnboundMethod FakeClass#age>
这引出了我的问题:
由于instance_method
会返回UnboundMethod,如何定义替换方法以覆盖它(例如,我想要define_instance_method
)
另外,我如何包装UnboundMethod,以便在创建实例时将其绑定到实例?
我可以在不弄乱initialize
方法的情况下执行此操作吗?
编辑:
根据Ajedi32的建议,我能够将我的方法改为这样的(我的库中的API稍微复杂一点,但你会得到要点):
def wrap_original
context = self
@original.owner.instance_eval do
define_method context.msg do |*args|
if context.original.respond_to? :bind
result = context.original.bind(self).call(*args)
else
result = context.original.call(*args)
end
context.after_call(result, *args)
result
end
end
end
def unwrap_original
context = self
@original.owner.instance_eval do
define_method context.msg, context.original
end
end
编辑:
在野外链接到我的宝石。这是与Core交互然后实例化新实例的主要入口点(API)https://github.com/jbodah/spy_rb/blob/a40ed2a67b088bfb7d40d12c5b6ffc882a5097c8/lib/spy/api.rb
答案 0 :(得分:1)
define_method
实际上确实默认定义了一个实例方法。在你的情况下,它定义一个类方法的唯一原因是因为你在instance_eval
内调用它。 (有关instance_eval
的确切行为的详细信息,您可能需要阅读this article)。在第一种情况下执行所需操作的更简单方法是使用define_singleton_method
在接收器对象上定义单例方法:
module Spy
def self.on(receiver, msg)
original = receiver.method(msg)
# Use `define_singleton_method` to define a method directly on the receiver
receiver.define_singleton_method(msg) do |*args, &block|
puts "Calling #{msg} with args #{args}" # To verify it's working
original.call(*args, &block) # Call with &block to preserve block args
end
end
end
Spy.on(FakeClass, :hello_world)
FakeClass.hello_world # Prints: Calling hello_world with args []
f = FakeClass.new
Spy.on(f, :age)
f.age # Prints: Calling age with args []
对于您的on_any_instance
方法,您可以使用常规define_method
方法。您必须使用send
来调用它,因为define_method
是私有的。由于您尝试调用的原始方法是UnboundMethod
,因此在调用它之前,您必须bind将其调用到要调用它的实例:
module Spy
def self.on_any_instance(receiver, msg)
original = receiver.instance_method(msg) # Get `UnboundMethod`
# We must call `define_method` with `send`, since it's private
receiver.send(:define_method, msg) do |*args, &block|
puts "Calling #{msg} with args #{args}"
# Method gets bound to `self`, the current instance
original.bind(self).call(*args, &block)
end
end
end
Spy.on_any_instance(FakeClass, :age)
FakeClass.new.age # Prints: Calling age with args []
答案 1 :(得分:0)
事实证明我可以做到这一点:
original = FakeClass.instance_method(:age)
FakeClass.instance_eval {:define_method, :age, original}
但我需要看看我现在如何包装方法