将define_method与method_added一起使用

时间:2013-12-22 14:13:56

标签: ruby

我正在尝试重新定义类中定义的每个新方法并为其添加功能:

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)

我该如何正确地做到这一点?

4 个答案:

答案 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
  • 重新绑定方法(我想它是不受限制的,因为它具有相同的名称?)