Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically extend existing method or override send method in ruby

Let say we have classes A,B,C.

A
 def self.inherited(sub)
   # meta programming goes here
   # take class that has just inherited class A
   # and for foo classes inject prepare_foo() as 
   # first line of method then run rest of the code
 end

 def prepare_foo
   # => prepare_foo() needed here
   # some code
 end

end

B < A
  def foo
    # some code
  end
end

C < A
  def foo
    # => prepare_foo() needed here
    # some code
  end
end

As you can see I am trying to inject foo_prepare() call to each one of foo() methods.

How can that be done?

Also I have been thinking about overriding send class in class A that way I would run foo_prepare and than just let send (super) to do rest of the method.

What do you guys think, what is the best way to approach this problem?

like image 310
Haris Krajina Avatar asked Sep 20 '12 10:09

Haris Krajina


3 Answers

Here's a solution for you. Although it's based on module inclusion and not inheriting from a class, I hope you will still find it useful.

module Parent
  def self.included(child)
    child.class_eval do
      def prepare_for_work
        puts "preparing to do some work"
      end
  
      # back up method's name
      alias_method :old_work, :work
  
      # replace the old method with a new version, which has 'prepare' injected
      def work
        prepare_for_work
        old_work
      end
    end
  end
end

class FirstChild
  def work
    puts "doing some work"
  end

  include Parent # include in the end of class, so that work method is already defined.
end

fc = FirstChild.new
fc.work
# >> preparing to do some work
# >> doing some work
like image 189
Sergio Tulentsev Avatar answered Nov 06 '22 11:11

Sergio Tulentsev


I recommend Sergio's solution (as accepted). Here is what I did which fit my needs.

class A
  def send(symbol,*args)
    # use array in case you want to extend method covrage
    prepare_foo() if [:foo].include? symbol
    __send__(symbol,*args)
  end
end

or

class A
  alias_method :super_send, :send           

  def send(symbol,*args)
    prepare_foo() if [:foo].include? symbol
    super_send(symbol,*args)
  end
end
like image 34
Haris Krajina Avatar answered Nov 06 '22 10:11

Haris Krajina


As of Ruby 2.0 you can use 'prepend' to simplify Sergio's solution:

module Parent
  def work
    puts "preparing to do some work"
    super
  end
end

class FirstChild
  prepend Parent

  def work
    puts "doing some work"
  end
end

fc = FirstChild.new
fc.work

This allows a module to override a class's method without the need for alias_method.

like image 2
griswoldbar Avatar answered Nov 06 '22 09:11

griswoldbar