2

I'm still new to Ruby in many ways so am a bit stuck trying to do this (via a Module? or base class?).

I want to do a "puts" for each method call executed on a class. Similar to a very simple form of a cucumber formatter, ie:

class MyClass
    def method_a
        puts 'doing some stuff'
    end 
end

So that the output looks like:

MyClass.new.method_a => 'methods_a', 'doing some stuff'

More importantly I want it to apply to any method on any class (dynamically, without littering my code). And I'd like to apply some formatting, ie so 'method_a' => 'Method A'. What's the best way to do this? AOP framework?

Bealer
  • 864
  • 9
  • 16
  • Please refer to: http://stackoverflow.com/questions/3779456/ruby-method-interception – MurifoX Sep 06 '12 at 11:47
  • Hmm, it's in the right direction, but you still have to specify the before filter call in your class for every method. – Bealer Sep 06 '12 at 12:48

2 Answers2

3
class MyClass
  def method_a
    puts "Doing A..."
  end

  def method_b
    puts "Doing B..."
  end

  def self.call_before_all_methods
    all_instance_methods = instance_methods - Class.instance_methods
    all_instance_methods.each do |x|
      class_eval <<-END
        alias old_#{x} #{x}
        def #{x}
          print "\'#{x.to_s.split('_').each{|x| x.capitalize!}.join(' ')}\', "
          old_#{x}
        end
        # remove_method old_#{x}.to_sym
      END
    end
  end

  private_class_method :call_before_all_methods
  call_before_all_methods
end

a = MyClass.new
a.method_a
a.method_b

So the trick here is make an alias for each method first, and then re-define the method by "print formatted method name" + "original method which is the alias". It's also dynamically processed by Here document (<<-END).

But since re-definition of each method will call its original method, eventually it's not possible to remove_method or undef the alias. I think it's not a big deal to leave all those alias (old_method_a, old_method_b) there.

Jing Li
  • 14,547
  • 7
  • 57
  • 69
0

Here you are (apart from before_filter), even though I don't know why you want to do this:

class MyClass
  def puts_all(&blk)
    # get all instance_methods, also including default ones, so remove them by - Class.instance_methods
    all_other_methods = self.class.instance_methods - Class.instance_methods
    # remove the method name itself dynamically by saying __callee__
    all_other_methods.delete(__callee__)
    # formatting as per your need
    all_other_methods.each do |x|
      print "#{x.to_s.split('_').each{|x| x.capitalize!}.join(' ')}, "
      send(x)
    end
  end

  def method_a
    puts "Doing A..."
  end

  def method_b
    puts "Doing B..."
  end

  def another_fancy_method
    puts "Doing fancy..."
  end
end

MyClass.new.puts_all
#=> Method A, Doing A...
#=> Method B, Doing B...
#=> Another Fancy Method, Doing fancy...

So, to achieve this dynamically, you can simply use instance_methods and _callee_.

Jing Li
  • 14,547
  • 7
  • 57
  • 69
  • @Bealerless, I think this dynamical approach did pretty much what you want. Can you tell if it works for you? Thanks. – Jing Li Sep 13 '12 at 05:46
  • Not quite. I don't want to execute methods dynamically. I just want when a method executes, that I can call a piece of code. In my case to output the name of the method being executed. – Bealer Sep 13 '12 at 16:46