【Ruby】Moduleをincludeするとはどういうことか

RubyではModuleを読み込むのに複数の方法があります。
include、prepend、extendなどが挙げられます。
何気なく使っていませんか?
とんでもない落とし穴があるかもしれません。
実際に内部でどういった処理が行われているのか、それぞれの違いを確認してみましょう。

include

おそらくModuleを勉強した時に一番最初に見るのがincludeかと思います。
Moduleをクラスにincludeすると、クラスにModuleで定義したメソッドが追加されます。
これはModuleをもとにして作成された無名クラスを、includeしたクラスの親として継承することで実現しています。

module M1
end

class C1
  include M1
end

C1.ancestors
#[C1, M1, Object, Kernel, BasicObject]

C1の親にM1が設定されているのがわかると思います。
そのため、M1で定義されたメソッドはC1で定義されたメソッドでオーバーライドされます。

module M1
  def greet
    puts "hello"
  end
end

class C1
  include M1

  def greet
    puts "hi"
    super
  end
end

C1.new.greet

#hi
#hello

superメソッドはオーバーライドされたメソッドを呼び出します。
後から"hello"が表示されたことでM1のgreetメソッドがオーバーライドされていることがわかります。
ただし、Module内で定義された特異メソッドは継承されないので注意が必要です。

module M1
  def self.lough
    puts "hahaha"
  end
end

class C1
  include M1
end

M1.lough #hahaha
C1.lough #NoMethodError

特異メソッドはM1ではなく、M1の特異クラスに定義されているため、無名クラスに定義が反映されないためです。

prepend

prependはincludeとよく似ていますが、継承の順番が変わります。

module M2
end

class C2
  prepend M2
end

C2.ancestors
#[M2, C2, Object, Kernel, BasicObject]

M2の方が子になっているのがわかると思います。
そのため、includeとは逆で、C2で定義されたメソッドはM2で定義されたメソッドでオーバーライドされます。

module M2
  def greet
    puts "hello"
    super
  end
end

class C2
  prepend M2

  def greet
    puts "hi"
  end
end

C2.new.greet

#hello
#hi

prependはモジュールの処理を優先したい場合に使います。

extend

includeとprependはモジュールを継承関係に組み込んでいたのに対し、extendはクラスとインスタンスの関係に組み込みます。  

module M3
  def greet
    puts "hello"
  end
end

class C3
  extend M3

  def greet
    puts "hi"
  end
end

C3.greet
#hello

C3.new.greet
#hi

C3.ancestors
#[C3,Object,Kernel,BasicObject]

継承関係にM3が存在しないことがわかります。
ところで、先ほどのgreetをクラスメソッドにしたらどうなるでしょうか。

module M4
  def greet
    puts "hello"
  end
end

class C4
  extend M4

  def self.greet
    "hi"
  end
end

C4.greet
#hi

クラスで定義したメソッドでOverrideされました。
クラスメソッドは特異クラスに定義されますので、それがOverrideされたということはその特異クラスの親クラスとしてモジュールが組み込まれているということがわかります。
特異クラスの継承関係を見てみましょう。

C4.singleton_class.ancestors
#[#<Class:C4>, M4, #<Class:Object>, Class, Module, Object, Kernel, BasicObject]

※#<Class:インスタンス名>はそのインスタンスの特異クラスを表します。
C4で定義したクラスメソッドは#<Class:C4>のインスタンスメソッドとして定義されるため、M4で定義されているgreetをOverrideしたという形になっています。
include、prepend、extendを理解することで、意図しないOverrideをしないように気をつけましょう。