定义“method_called”..如何在每次调用类的任何函数时调用一个钩子方法?

时间:2010-07-13 10:26:39

标签: ruby metaprogramming hook

我想创建一个钩子方法,每次调用类的任何函数时都会调用它。 我已经尝试过method_added,但它只在类定义时执行一次,

class Base

  def self.method_added(name)
    p "#{name.to_s.capitalize} Method's been called!!"
  end
  def a
    p "a called."
  end
  def b
    p "b called."
  end
end
t1 = Base.new
t1.a
t1.b
t1.a
t1.b

Output:

"A Method's been called!!"
"B Method's been called!!"
"a called."
"b called."
"a called."
"b called."

但我的要求是在程序中的任何地方调用的类的任何函数都会触发“method_called”,hook方法。

Expected Output:
"A Method's been called!!"
"a called."
"B Method's been called!!"
"b called."
"A Method's been called!!"
"a called."
"B Method's been called!!"
"b called."

如果有任何已定义的现有钩子方法同样有效,那么请告诉它。

提前致谢..

3 个答案:

答案 0 :(得分:18)

在将新方法添加到类中时,

method_added可以运行代码;它不会报告何时调用方法。 (正如你发现的那样。)

如果您不想关注mikej的回答,这里有一个实现您的规范的类:

#!/usr/bin/ruby

class Base
  def self.method_added(name)
    if /hook/.match(name.to_s) or method_defined?("#{name}_without_hook")
      return
    end
    hook = "def #{name}_hook\n p 'Method #{name} has been called'\n #{name}_without_hook\nend"
    self.class_eval(hook)

    a1 = "alias #{name}_without_hook #{name}"
    self.class_eval(a1)

    a2 = "alias #{name} #{name}_hook"
    self.class_eval(a2)
  end
  def a
    p "a called."
  end
  def b
    p "b called."
  end
end
t1 = Base.new
t1.a
t1.b
t1.a
t1.b

输出:

$ ./meta.rb
"Method a has been called"
"a called."
"Method b has been called"
"b called."
"Method a has been called"
"a called."
"Method b has been called"
"b called."

答案 1 :(得分:17)

看看Kernel#set_trace_func。它允许您指定在发生事件(例如方法调用)时调用的proc。这是一个例子:

class Base
  def a
    puts "in method a"
  end

  def b
    puts "in method b"
  end
end

set_trace_func proc { |event, file, line, id, binding, classname|
  # only interested in events of type 'call' (Ruby method calls)
  # see the docs for set_trace_func for other supported event types
  puts "#{classname} #{id} called" if event == 'call'
}

b = Base.new
b.a
b.b

输出:

Base a called
in method a
Base b called
in method b

答案 2 :(得分:1)

我最近写了一些可能有用的东西,虽然有一些附带条件(见下文)。 这是您想要添加钩子的类:

class Original  
  def regular_old_method msg
    puts msg
  end

private

  def always_called method_called
    puts "'#{method_called.to_s.capitalize}' method's been called!"
  end
end

以下是添加该钩子的代码:

class << Original
  def new(*args)
    inner = self.allocate
    outer_name = self.name + 'Wrapper'
    outer_class = Class.new do
      def initialize inner_object
        @inner = inner_object
      end
      def method_missing method_called, *args
        @inner.send method_called, *args
        @inner.send :always_called, method_called
      end
    end
    outer_class_constant = Object.const_set(outer_name, outer_class)
    inner.send :initialize, *args
    outer_class_constant.new inner
  end
end

如果你这样称呼它......

o = Original.new
o.regular_old_method "nothing unusual about this bit"

您将获得以下输出:

  

这个位没什么不寻常的

     

'Regular_old_method'方法已被调用!

如果您的代码检查了类名,这种方法将是一个坏主意,因为即使您要求提供类'Original'的对象,您得到的是'OriginalWrapper'类的对象。

另外我认为搞乱“新”方法可能还有其他缺点,但我对Ruby元编程的了解还远远不够。