定义“method_called”. .如何创建一个钩子方法,每次调用类的任何函数时都要调用它?

时间:2022-02-18 16:02:49

I want to make a hook method which gets called everytime any function of a class gets called. I have tried method_added, but it executes only once at the time of class definition,

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

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."

but my requirement is that any function of a class that gets called anywhere in the program triggers the "method_called", hook method.

但我的要求是,程序中任何地方调用的类的任何函数都触发“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."

If there is any defined existing hook method that works just the same, then please tell about it.

如果有任何定义的现有hook方法都是相同的,请说明。

Thanks in advance..

提前谢谢. .

3 个解决方案

#1


17  

Take a look at Kernel#set_trace_func. It lets you specify a proc which is invoked whenever an event (such as a method call) occurs. Here's an example:

看看内核#set_trace_func。它允许您指定一个proc,每当发生事件(如方法调用)时调用该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

Outputs:

输出:

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

#2


18  

method_added is there to run code when a new method has been added to the class; it doesn't report when a method has been called. (As you discovered.)

method_add是在一个新方法被添加到类中时运行代码的;当一个方法被调用时,它不会报告。(如您发现。)

If you don't want to follow mikej's answer, here is a class that implements your specification:

如果您不想遵循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

And output:

和输出:

$ ./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."

#3


1  

I recently wrote something that might be useful, though there are some provisos (see below). Here's the class you want to add your hook to:

我最近写了一些可能有用的东西,尽管有一些附带条件(见下面)。下面是你想要添加的类:

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

And here's the code for adding that hook:

下面是添加钩子的代码:

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

If you call it like this...

如果你这样称呼它……

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

You get the following output:

得到以下输出:

nothing unusual about this bit

这一点没什么特别的

'Regular_old_method' method's been called!

“Regular_old_method”方法被称为!

This approach would be a bad idea if your code checked class names, because even though you've asked for an object of class 'Original', what you got back was an object of class 'OriginalWrapper'.

如果您的代码检查了类名,那么这种方法将是一个坏主意,因为即使您已经请求了一个class 'Original'的对象,但是您得到的是一个class 'OriginalWrapper'的对象。

Plus I imagine there could be other drawbacks to messing with the 'new' method, but my knowledge of Ruby metaprogramming doesn't stretch that far yet.

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

#1


17  

Take a look at Kernel#set_trace_func. It lets you specify a proc which is invoked whenever an event (such as a method call) occurs. Here's an example:

看看内核#set_trace_func。它允许您指定一个proc,每当发生事件(如方法调用)时调用该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

Outputs:

输出:

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

#2


18  

method_added is there to run code when a new method has been added to the class; it doesn't report when a method has been called. (As you discovered.)

method_add是在一个新方法被添加到类中时运行代码的;当一个方法被调用时,它不会报告。(如您发现。)

If you don't want to follow mikej's answer, here is a class that implements your specification:

如果您不想遵循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

And output:

和输出:

$ ./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."

#3


1  

I recently wrote something that might be useful, though there are some provisos (see below). Here's the class you want to add your hook to:

我最近写了一些可能有用的东西,尽管有一些附带条件(见下面)。下面是你想要添加的类:

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

And here's the code for adding that hook:

下面是添加钩子的代码:

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

If you call it like this...

如果你这样称呼它……

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

You get the following output:

得到以下输出:

nothing unusual about this bit

这一点没什么特别的

'Regular_old_method' method's been called!

“Regular_old_method”方法被称为!

This approach would be a bad idea if your code checked class names, because even though you've asked for an object of class 'Original', what you got back was an object of class 'OriginalWrapper'.

如果您的代码检查了类名,那么这种方法将是一个坏主意,因为即使您已经请求了一个class 'Original'的对象,但是您得到的是一个class 'OriginalWrapper'的对象。

Plus I imagine there could be other drawbacks to messing with the 'new' method, but my knowledge of Ruby metaprogramming doesn't stretch that far yet.

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