web-dev-qa-db-ja.com

モジュールで定義された別のメソッドによるメソッドのオーバーライド

翌日を返すインスタンスメソッド_Date#next_を定義したい。そこで、次のようにDateExtensionモジュールを作成しました。

_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
_

それを使う:

_class Date
  include DateExtension
end
_

メソッドd.next(:week)を呼び出すと、RubyエラーがスローされますArgumentError: wrong number of arguments (1 for 0)。デフォルトのnextメソッドをDateDateExtensionモジュールで宣言されたクラスを持つクラス?

37
resilva87

Ruby 2.0以降では Module#prepend

class Date
  prepend DateExtension
end

以前の元の答えRubyバージョンは以下のとおりです。


includeの問題( 次の図 に示すように)は、クラスのメソッドをそのクラスに含まれるモジュールでオーバーライドできないことです(解決策は図に従います): Ruby Method Lookup Flow

ソリューション

  1. この1つのメソッドだけのサブクラスの日付:

    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. 独自のメソッドで必要なインスタンスのみを拡張します。

    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. モジュールを含める場合は、全体的なハックを実行してクラスの内部に到達し、古い「次の」を破棄して、あなたのモジュールが呼び出されるようにします。

    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
    

    このメソッドは、単にモジュールを含めるよりもはるかに侵襲的ですが、システムメソッドによって返される新しいDateインスタンスが自動的に独自のモジュールのメソッドを使用できるようにするための(これまでに示した手法の)唯一の方法です。

  4. しかし、これを行う場合は、モジュールを完全にスキップして、モンキーパッチランドに直接移動することもできます。

    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. 独自の環境でこの機能が本当に必要な場合は、banisterfiendの Prepend ライブラリを使用すると、モジュールが混在するクラスの前にモジュールでルックアップを実行できるようになることに注意してください。

    • Module#prependRuby 2.に入る)のように見えます
107
Phrogz

nextDateメソッドはDateクラスで定義され、クラスで定義されたメソッドは、インクルードされたモジュールで定義されたメソッドよりも優先されます。したがって、これを行うと:

class Date
  include DateExtension
end

nextのバージョンを取得していますが、nextで定義されているDateが引き続き優先されます。 nextDateに入れる必要があります。

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

プログラミングからRuby クラスとオブジェクト)の章

クラスにモジュールが含まれている場合、そのモジュールのインスタンスメソッドは、クラスのインスタンスメソッドとして使用可能になります。これは、モジュールがそれを使用するクラスのスーパークラスになるかのようです。当然のことながら、それはそれがどのように機能するかについてです。モジュールをインクルードすると、Rubyはそのモジュールを参照する匿名プロキシクラスを作成し、インクルードを行ったクラスの直接のスーパークラスとしてそのプロキシを挿入します。

7
mu is too short