Определение "method_called" .. Как я могу сделать метод-ловушку, который вызывается каждый раз, когда вызывается любая функция класса? - PullRequest
12 голосов
/ 13 июля 2010

Я хочу создать метод ловушки, который будет вызываться каждый раз, когда вызывается любая функция класса.Я пробовал 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.

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 ]

18 голосов
/ 13 июля 2010

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."
17 голосов
/ 13 июля 2010

Взгляните на 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
1 голос
/ 26 января 2014

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

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 пока еще не настолько велики.

...