Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
295 views
in Technique[技术] by (71.8m points)

ruby - Overriding method by another defined in module

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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): 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
    #=> nil
    irb(main):002:0> class MyDate < Date; include Foo; end
    #=> MyDate
    irb(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
    #=> nil
    irb(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'
    #=> 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.

  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'
    #=> 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
    
  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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...