Определение "method_called". Как создать метод hook, который вызывается каждый раз при вызове любой функции класса?

Я хочу создать метод hook, который вызывается каждый раз, когда вызывается любая функция класса. Я попытался method_added, но он выполняется только один раз во время определения класса,

class Base

  def self.method_added(name)
    p "#{name.to_s.capitalize} Method 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 been called!!"
"B Method been called!!"
"a called."
"b called."
"a called."
"b called."

но мое требование состоит в том, что любая функция класса, вызываемого в любом месте программы, запускает метод method_called, hook.

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

Если существует какой-либо определенный существующий метод hook, который работает одинаково, тогда, пожалуйста, сообщите об этом.

Спасибо заранее.

Ответ 1

Посмотрите 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

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

Ответ 3

Недавно я написал что-то полезное, хотя есть некоторые оговорки (см. ниже). Здесь класс, к которому вы хотите добавить свой крючок,:

class Original  
  def regular_old_method msg
    puts msg
  end

private

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

И вот код для добавления этого 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

Если вы называете это так...

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

Вы получаете следующий результат:

ничего необычного в этом бите

Вызывается метод

'Regular_old_method'!

Этот подход был бы плохой идеей, если бы ваш код проверял имена классов, потому что даже если вы попросили объект класса "Оригинал", то, что вы получили, было объектом класса "OriginalWrapper".

Плюс я полагаю, что могут быть другие недостатки, связанные с "новым" методом, но мои знания метапрограммирования Ruby еще не растягиваются.