views:

115

answers:

3

I want to intercept method calls on a ruby-class and being able to do something before and after the actual execution of the method. I tried the following code, but get the error:

MethodInterception.rb:16:in before_filter': (eval):2:inalias_method': undefined method say_hello' for classHomeWork' (NameError) from (eval):2:in `before_filter'

Can anybody help me to do it right?

class MethodInterception

  def self.before_filter(method)
    puts "before filter called"
    method = method.to_s
    eval_string = "
      alias_method :old_#{method}, :#{method}

      def #{method}(*args)
        puts 'going to call former method'
        old_#{method}(*args)
        puts 'former method called'
      end
    "
    puts "going to call #{eval_string}"
    eval(eval_string)
    puts "return"
  end
end

class HomeWork < MethodInterception
  before_filter(:say_hello)

  def say_hello
    puts "say hello"
  end

end
+8  A: 
Jörg W Mittag
This is simple and nifty.
Swanand
A: 

Jörg W Mittag's solution is pretty nice. If you want something more robust (read well tested) the best resource would be the rails callbacks module.

Swanand
did he say he was using rails??!
banister
I count less than 50 lines of code in Jörg's example (Homework class included). Surely we can come up with a strategy to test it until we consider it robust and well-tested.
Corbin March
@banister: Don't know where Swanand got that crazy notion from. Only 98% of ruby folk use Rails.
Andrew Grimm
Which part of Rails does the callbacks module reside in? ActiveSupport?
Andrew Grimm
@banister: He never did! I directed him to Rails because it has a similar functionality which he can learn /copy from.@Andrew: :-), that too.
Swanand
@Corbin: Sure, we definitely can. But it isn't already, and Rails has, thats why the suggestion.
Swanand
+1  A: 

Less code was changed from original. I modified only 2 line.

class MethodInterception

  def self.before_filter(method)
    puts "before filter called"
    method = method.to_s
    eval_string = "
      alias_method :old_#{method}, :#{method}

      def #{method}(*args)
        puts 'going to call former method'
        old_#{method}(*args)
        puts 'former method called'
      end
    "
    puts "going to call #{eval_string}"
    class_eval(eval_string) # <= modified
    puts "return"
  end
end

class HomeWork < MethodInterception

  def say_hello
    puts "say hello"
  end

  before_filter(:say_hello) # <= change the called order
end

This works well.

HomeWork.new.say_hello
#=> going to call former method
#=> say hello
#=> former method called
Shinya Miyazaki