Ramano Ramano - 4 months ago 12
Ruby Question

Insert code into the beginning of each method of a class

How can I dynamically and easily insert code into the beginning of each method of a class and subclasses without actually inserting it manually? I want something like a macros.

class C1
def m1
@i_am = __method__
end

def m2
@i_am = __method__
end
end


This is one of the examples where I want to avoid repetition.

Answer

I initially misinterpreted the question (but have left my original answer after the horizontal line below). I believe the following may be what you are looking for.

class C1
  [:m1, :m2].each do |m|
    define_method(m) do |name|
      @i_am = __method__
      puts "I'm #{name} from method #{@i_am}"
    end
  end
end

C1.instance_methods(false)
  #=> [:m1, :m2] 
c1 = C1.new
  #=> #<C1:0x007f94a10c0b60> 
c1.m1 "Bob"
  # I'm Bob from method m1
c1.m2 "Lucy"
  # I'm Lucy from method m2

My original solution follows.

class C1
  def add_code_to_beginning(meth)
    meth = meth.to_sym
    self.class.send(:alias_method, "old_#{meth}".to_sym, meth)
    self.class.send(:define_method, meth) do
      yield
      send("old_#{meth}".to_sym)
    end
  end
end

Module#alias_method and Module#define_method are private; hence the need to use send.

c = C1.new
  #=> #<C1:0x007ff5e3023650> 
C1.instance_methods(false)
  #=> [:m1, :m2, :add_code_to_beginning] 

c.add_code_to_beginning(:m1) do
  puts "hiya"
end

C1.instance_methods(false)
  #=> [:m1, :m2, :add_code_to_beginning, :old_m1] 
c.m1
  # hiya
  #=> :m1