views:

44

answers:

2

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

+2  A: 

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.

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
mikej
+1  A: 

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."
sarnold
I like this. It seems less invasive than a global `set_trace_func` since this hackery is constrained to just this class.
Squeegy