Определение "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 еще не растягиваются.