Haris Krajina
Haris Krajina

Reputation: 15286

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?

Upvotes: 7

Views: 5094

Answers (3)

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230336

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

Upvotes: 9

griswoldbar
griswoldbar

Reputation: 499

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.

Upvotes: 2

Haris Krajina
Haris Krajina

Reputation: 15286

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

Upvotes: 4

Related Questions