Overriding method by another defined in module
In Ruby 2.0 and later you can use Module#prepend
:
class Date
prepend DateExtension
end
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):
Solutions
-
Subclass Date just for this one method:
irb(main):001:0> require 'date'; module Foo; def next(a=:hi); a; end; end #=> nil irb(main):002:0> class MyDate < Date; include Foo; end #=> MyDate irb(main):003:0> MyDate.today.next(:world) #=> :world
-
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 #=> nil irb(main):002:0> d = Date.today; d.extend(Foo); d.next(:world) #=> :world
-
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' #=> true irb(main):002:0> module Foo irb(main):003:1> def self.included(klass) irb(main):004:2> klass.class_eval do irb(main):005:3* remove_method :next irb(main):006:3> end irb(main):007:2> end irb(main):008:1> def next(a=:hi); a; end irb(main):009:1> end #=> nil irb(main):010:0> class Date; include Foo; end #=> Date irb(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.
-
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' #=> true irb(main):002:0> class Date irb(main):003:1> alias_method :_real_next, :next irb(main):004:1> def next(a=:hi); a; end irb(main):005:1> end #=> nil irb(main):006:0> Date.today.next(:world) #=> :world
-
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.
- Note that
Module#prepend
looks to be coming in Ruby 2.0.
- Note that
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 DateExtension
end
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]
end
end
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.