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,
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.
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.
Thanks in advance..
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:
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
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.)
If you don't want to follow mikej's answer, here is a class that implements your specification:
#!/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."
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!
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'.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With