読者です 読者をやめる 読者になる 読者になる

Module#prepend で継承チェーンを入れ替える

Ruby

昨日は時間を捻出できなくてブログを書けなかった!継続日数は13日でストップ!まぁ継続日数を目標にしてるわけではないし,引き続き学びがあったら少しでもアウトプットできるように頑張ろうと思う.

Module#prepend

引き続き Ruby Gold の対策をしていて,前回同様に Ruby 2.1 で追加された機能がテーマ.今回は Module#prepend を調べた.

Ruby技術者認定試験改訂のお知らせ

Ruby Association の資料に載ってるコードをベースに少しずつ紐解いていく.

module M
  def foo
    puts "M#foo"
  end
end
class C
  prepend M
  def foo
    puts "C#foo"
  end
end
C.new.foo
# >> M#foo

include の場合

include でモジュールを Min-in する場合で,さらにメソッド名が同じ場合,継承チェーン的にクラスが優先されるので,クラス C のメソッドが呼び出される.

module M
  def foo
    puts "M#foo"
  end
end
class C
  include M
  def foo
    puts "C#foo"
  end
end
C.new.foo
p C.ancestors
# >> C#foo
# >> [C, M, Object, Kernel, BasicObject]

参考として,クラス S を継承してから継承チェーンを見てみる.特に変わらず,クラス C のメソッドが呼び出される.継承したクラスよりモジュールの方が優先度が高いことがわかる.

module M
  def foo
    puts "M#foo"
  end
end
class S
  def foo
    puts "S#foo"
  end
end
class C < S
  include M
  def foo
    puts "C#foo"
  end
end
C.new.foo
p C.ancestors
# >> C#foo
# >> [C, M, S, Object, Kernel, BasicObject]

prepend

そこで今回の prepend を使って Mix-in するとどうなるかと言うと,その言葉通り,クラスより先に読み込まれるようになって,継承チェーンが変わってくる.

結果として,モジュール M のメソッドが呼び出される.

module M
  def foo
    puts "M#foo"
  end
end
class C
  prepend M
  def foo
    puts "C#foo"
  end
end
C.new.foo
p C.ancestors
# >> M#foo
# >> [M, C, Object, Kernel, BasicObject]

クラス S を継承した場合の優先度は変わらずで,あくまでクラス C とモジュール M の順序が変わるのが prepend の影響と言える.

module M
  def foo
    puts "M#foo"
  end
end
class S
  def foo
    puts "S#foo"
  end
end
class C < S
  prepend M
  def foo
    puts "C#foo"
  end
end
C.new.foo
p C.ancestors
# >> M#foo
# >> [M, C, S, Object, Kernel, BasicObject]

alias_method_chain と prepend

prepend の挙動を理解したところで,実際にどういう活用事例があるんだろうと考えてみたときに,まさに今読んでる Metaprogramming Ruby 2 に Rails の例が載っててビビった!タイミング良くて勉強になった.

Chapter 11. The Rise and Fall of alias_method_chain

ActiveRecord の Validations で alias_method_chain が使われていて,その実装は alias_method を機能の有無で付け替えて事前処理を追加するようなパターンで Around Aliases と呼ばれている.

抜粋しただけだと意味ないけど,例えばこんな感じ.この実装をよりスッキリさせるために prepend が提案されて,実際に追加された感じ.

alias_method without_method, target
alias_method target, with_method

やっぱり,メタプロ本を買うなら Ruby 2.1 に対応した洋書の方を買うべき!

Metaprogramming Ruby 2: Program Like the Ruby Pros (Facets of Ruby)

Metaprogramming Ruby 2: Program Like the Ruby Pros (Facets of Ruby)

関連エントリー

Ruby Gold 関連