我正在尝试重新定义类中定义的每个新方法并为其添加功能:
class Test
def self.method_added(name)
old_method = instance_method(name)
define_method(name) do |*args, &block|
@@tests += 1
old_method.call(*args, &block)
end
end
end
这导致:
stack level too deep (SystemStackError)
在线:
def self.method_added(name)
我该如何正确地做到这一点?
答案 0 :(得分:7)
错误是由无限递归引起的。一旦你定义了钩子,每当你拨打define_method
时,它也会触发method_added
,然后轮回召回method_added
,依此类推。
无论你有什么干净的解决方案,这种方法本身都是错误的。你说
我想计算每个方法调用。
但使用method_added
不会涉及多个案例。事实上,在创建回调之前存在的所有方法都不会触发method_added
。此外,它不适用于您从父类继承的所有方法,包括从Object
继承的方法,例如to_s
。
如果您想计算方法调用,您还有其他方法。
最简单的一个,在不了解Ruby内部的情况下,是使用代理对象将每个方法调用委托给您的对象,但是跟踪方法调用。这称为Proxy Design Pattern。
class MethodCounter < BasicObject
def initialize(instance)
@counter = 0
@instance = instance
end
def method_missing(*args)
@counter += 1
@instance.send(*args, &block)
end
end
然后直接使用您的实例
t = Test.new
t.method
将其包装在代理
中t = MethodCounter.new(Test.new)
t.method
另一种方法是使用诸如set_trace_funct
之类的Ruby跟踪方法在调用方法时添加回调。
以下是一个实际示例:Logging all method calls in a Rails app
答案 1 :(得分:0)
感谢您的评论,错误是由self.method_added
的无限递归引起的
每次在define_method
内调用self.method_added
时,都会触发对self.method_added
的新调用,因为正在定义新方法。
为了避免这种情况,我必须设置一个实例变量@new_method:
class Test
@new_method = true
def self.method_added(name)
if @new_method
@new_method = false
old_method = instance_method(name)
define_method(name) do |*args, &block|
@@tests += 1
old_method.call(*args, &block)
end
@new_method = true
end
end
end
我找不到更好的解决方案。有什么更优雅的吗?
答案 2 :(得分:0)
您可能希望将要定义的方法附加到方法列表(数组)中,因此,如果您决定要记住这些方法,则可以清除该注释,
if !@memized_methods.include?name.to_sym then
@memoized_methods<<name.to_sym
define_method(name) { ...
.
.
.
}
end
答案 3 :(得分:0)
对于定义设置(在没有钩子的minitest中)的抽象测试用例类,我需要类似的东西。我实际上是用你的主意的。
def self.method_added(method_name)
# only override 'setup'
return unless method_name == :setup
# prevents recursion
if @_rewriting
@_rewriting = false
return
end
@_rewriting = true
setup_method = instance_method(method_name)
define_method(method_name) do |*args, &block|
# do something
setup_method.bind(self).call(*args, &block)
end
end
示例中有两处遗漏的地方:
@_rewriting
)