Overriding method by another defined in module Overriding method by another defined in module ruby ruby

Overriding method by another defined in module


In Ruby 2.0 and later you can use Module#prepend:

class Date  prepend DateExtensionend

Original answer for older Ruby versions is below.


The problem with include (as shown in the following diagram) is that methods of a class cannot be overridden by modules included in that class (solutions follow the diagram):Ruby Method Lookup Flow

Solutions

  1. Subclass Date just for this one method:

    irb(main):001:0> require 'date'; module Foo; def next(a=:hi); a; end; end#=> nilirb(main):002:0> class MyDate < Date; include Foo; end#=> MyDateirb(main):003:0> MyDate.today.next(:world)#=> :world
  2. Extend just the instances you need with your own method:

    irb(main):001:0> require 'date'; module Foo; def next(a=:hi); a; end; end#=> nilirb(main):002:0> d = Date.today; d.extend(Foo); d.next(:world)#=> :world
  3. When including your module, perform a gross hack and reach inside the class and destroy the old 'next' so that yours gets called:

    irb(main):001:0> require 'date'#=> trueirb(main):002:0> module Fooirb(main):003:1>   def self.included(klass)irb(main):004:2>     klass.class_eval doirb(main):005:3*       remove_method :nextirb(main):006:3>     endirb(main):007:2>   endirb(main):008:1>   def next(a=:hi); a; endirb(main):009:1> end#=> nilirb(main):010:0> class Date; include Foo; end#=> Dateirb(main):011:0> Date.today.next(:world)#=> :world

    This method is far more invasive than just including a module, but the only way (of the techniques shown so far) to make it so that new Date instances returned by system methods will automatically use methods from your own module.

  4. But if you're going to do that, you might as well skip the module altogether and just go straight to monkeypatch land:

    irb(main):001:0> require 'date'#=> trueirb(main):002:0> class Dateirb(main):003:1>   alias_method :_real_next, :nextirb(main):004:1>   def next(a=:hi); a; endirb(main):005:1> end#=> nilirb(main):006:0> Date.today.next(:world)#=> :world
  5. If you really need this functionality in your own environment, note that the Prepend library by banisterfiend can give you the ability to cause lookup to occur in a module before the class into which it is mixed.


The next method for Date is defined in the Date class and methods defined in a class take precedence over those defined in an included module. So, when you do this:

class Date  include DateExtensionend

You're pulling in your version of next but the next defined in Date still takes precedence. You'll have to put your next right in Date:

class Date  def next(symb=:day)    dt = DateTime.now      {:day   => Date.new(dt.year, dt.month, dt.day + 1),       :week  => Date.new(dt.year, dt.month, dt.day + 7),       :month => Date.new(dt.year, dt.month + 1, dt.day),       :year  => Date.new(dt.year + 1, dt.month, dt.day)}[symb]    endend

From the the Programming Ruby chapter on Classes and Objects:

When a class includes a module, that module's instance methods become available as instance methods of the class. It's almost as if the module becomes a superclass of the class that uses it. Not surprisingly, that's about how it works. When you include a module, Ruby creates an anonymous proxy class that references that module, and inserts that proxy as the direct superclass of the class that did the including.