I want to define an instance method Date#next
which returns the next day. So I made a DateExtension
module, like this:
module DateExtension 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
Using it:
class Date include DateExtension end
Calling the method d.next(:week)
makes Ruby throw an error ArgumentError: wrong number of arguments (1 for 0)
. How can I override the default next
method from Date
class with the one declared in DateExtension
module?
Method overriding, in object-oriented programming, is a language feature that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its superclasses or parent classes.
Overloading occurs when two or more methods in one class have the same method name but different parameters. Must have at least two methods by the same name in the class. Must have a different number of parameters.
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):
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.
Module#prepend
looks to be coming in Ruby 2.0.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With