Rubyのモジュールまとめ

Rubyのモジュールについて調べたのでまとめます。
動作確認はRuby 2.4.2でやってます。

# Rubyにおけるモジュールについて
#### モジュールの特徴
- インスタンスを生成ができない
- 継承ができない

#### モジュールの使われ方
- 名前空間として利用する
- Mixin
    - include
        -> モジュールのメソッドをクラスのインスタンスメソッドとして継承ツリーの上位に取り込む
    - prepend
        -> モジュールのメソッドをクラスのインスタンスメソッドとして継承ツリーの下位に取り込む
    - extend
        -> モジュールのメソッドをオブジェクトのクラスメソッドとして取り込む

- モジュール関数として利用する

#### 定義
```ruby
module Foo

end
```

## 名前空間として利用する
モジュールの定義の中にネストした別のモジュールやクラスを定義することで名前空間として利用できます。こうすることで同じ名前のクラス・メソッドなどを名前空間で分別することができます。名前空間のモジュールやクラスへのアクセスは`::`を使用します。

```ruby
module MotorCycle
  module Onroad
    class MotoGP
      def foo(bar)
        p bar
      end
    end
  end

  module Offroad
    class MotocrossGP
      def foo(bar)
        p "#{bar} MotocrossGP_class!!!"
      end
    end
  end
end

MotorCycle::Onroad::MotoGP # => MotorCycle::Onroad::MotoGP
mx_gp = MotorCycle::Offroad::MotocrossGP.new
mx_gp.foo('hoge') # => "hoge MotocrossGP_class!!!"
```

## Mixin
ざっくりとクラスとモジュールの違いとしては、以下の通りで、同じ意図を持ったメソッドをモジュールにまとめて見通しを良くしたりするのに使われます。

**クラス**：インスタンス化できる。メソッドを定義できる。
**モジュール**：メソッドを定義できるが、インスタンス化できない。
 
そして、そのモジュールをクラスに取り込むことをMixinと言います。
Rubyの継承は1つのスーパークラスから受け継ぐ、単一継承のみ許可されており、多重継承が許可されておりません。しかし、クラスにモジュールを取り込む(Mixin)ことで多重継承のようなものを実現可能とします。ここではクラスにモジュールを取り込む方法として以下の3つの方法を説明します。

- include
- prepend
- extend

### **include**
モジュールに定義されたメソッドをクラスにインスタンスメソッドとして取り込みます。モジュールをクラスに取り込むには`include` を使用します。
インスタンスメソッドとして取り込むので、メソッドのレシーバはインスタンスです。

```ruby
module Foo
  def foo_method
    p 'foo!!!'
    bar_method(self.class) # ここでのselfはこのモジュールを取り込んだクラスのインスタンスを返す
  end
end

class Bar
  include Foo

  def bar_method(val)
    p val
  end
end

bar = Bar.new
bar.foo_method # => "foo!!!"
               # => Bar
```

#### 継承ツリー

`include` を使用すると、そのクラスとスーパークラスの間に`include`されたモジュールが内部的に定義されます。
以下の通り、継承ツリーは`Bar -> Foo(をラップしたクラス) -> SuperBar` となるため、メソッドの探索もBarから順に探索し、最初に見つかったメソッドが実行されます。

```ruby
module Foo
end

class SuperBar
end

class Bar < SuperBar
  include Foo
end

p Bar.ancestors # => [Bar, Foo, SuperBar, Object, Kernel, BasicObject]
```

####モジュールから取り込んだメソッドのオーバーライド
モジュールから取り込んだメソッドをオーバーライドするには以下のように行います。

```ruby
module Foo
  def foo_method
    p 'foo_method!!!'
  end
end

class Bar
  include Foo

  def foo_method
    p 'bar_class!!!'
    super  # モジュール側に定義されたメソッドが呼び出される
  end
end

bar = Bar.new
bar.foo_method # => "bar_class!!!"
               # => "foo_method!!!"
```

### **prepend** (ruby2.0～)
モジュールのメソッドをクラスのインスタンスメソッドとして継承ツリーの下位に取り込みます。前述の`include`との違いは継承ツリーの下位に取り込まれるため、こちらの方がメソッドの実行順として優先されることです。

```ruby
module Foo
  def foo_method
    p 'foo_module!!!'
    super
  end
end

class Bar
  prepend Foo

  def foo_method
    p 'bar_class!!!'
  end
end

bar = Bar.new
bar.foo_method # => "foo_module!!!"
               # => "bar_class!!!"
```

よって、継承ツリーは`include`と異なり、`Foo(をラップしたクラス) => Bar => SuperBar`となります。

```ruby
module Foo
end

class SuperBar
end

class Bar < SuperBar
  prepend Foo
end

p Bar.ancestors # => [Foo, Bar, SuperBar, Object, Kernel, BasicObject]
```

### **extend**
モジュールをオブジェクトの特異クラスとして取り込み、モジュールのメソッドを特異メソッドとして使用することができます。

###### **オブジェクト(クラス)のクラスメソッドとして取り込む**

以下のようにクラス内で`extend`するとモジュールのメソッドをオブジェクトのクラスメソッドとして取り取り込むことができます。クラスメソッドなのでインスタンスからは呼び出せず、クラスをレシーバとしてクラスから直接呼び出します。

```ruby
module Foo
  def foo_method
    p 'foo_method!!!'
  end
end

class Bar
  extend Foo
end

Bar.foo_method # => "foo_method!!!"
```

###### **オブジェクト(インスタンス)の特異メソッドとして取り込む**

以下のように`Object`のインスタンスに`extend`すると、そのインスタンスにモジュールのメソッドを取り込むことができます。

```ruby
module Foo
  def foo_method
    p 'foo_method!!!'
  end
end

obj = Object.new
obj.extend Foo
obj.foo_method # => "foo_method!!!"

# 特異クラスの継承ツリーを確認するとFooが追加されている
p obj.singleton_class.ancestors # => [#<Class:#<Object:0x00007f7e55aed190>>, Foo, Object, Kernel, BasicObject]
```

## モジュール関数として利用する
`module_function`を使用するとモジュール関数として利用することができます。
> モジュール関数とは、プライベートメソッドであると同時に モジュールの特異メソッドでもあるようなメソッドです。

https://docs.ruby-lang.org/ja/2.4.0/method/Module/i/module_function.html

```ruby
module Foo
  def foo_method
    p 'foo_method!!!'
  end
  module_function :foo_method
end

# モジュールから直接呼び出す方法(モジュールの特異メソッドとして呼んでいる)
Foo.foo_method # => "foo_method!!!"

# includeして直接呼び出す方法
include Foo
foo_method # => "foo_method!!!"
```

モジュール関数を複数定義する場合は以下のように`module_function`以下に続けてメソッドを定義します。

```ruby
module Foo
  module_function

  def foo_method_a
  end
  def foo_method_b
  end
end

```
## 参考
『パーフェクトRuby』