【Ruby/Rails】毎回忘れる #include #extend の意味

今日、長期インターン中の開発で通知処理を Railsの`concern`で実装することになり、その時に「`＃include`,`#extend`ってどっちがなんだったっけ？」となったので改めて調べてアウトプットすることにしました。


## `#include`と`#extend`の違いは？
ズバリ、
1. **あるモジュール内で定義されたメソッドを、別のクラスのインスタンスメソッドとして追加したい場合は`#include`を使う**

2. **あるモジュール内で定義されたメソッドを、（クラスなどの）オブジェクト自身のメソッドとして追加したい場合は`#extend`を使う**


### そもそも`#include` `#extend`があると何が嬉しいのか？

Rubyでは、一つのクラスが複数のクラスを継承する「多重継承」が許されておらず、一つのクラスしか継承できません。その代わりに用いられるのが、特に回数制限なくメソッドや定数を追加できる`#include`, `#extend`です。ちなみに、`#include` `#extend`できる対象はモジュールのみです。

### `＃include`
[公式リファレンス(#include)](https://docs.ruby-lang.org/ja/latest/method/Module/i/include.html)
`include`は、別のモジュールで定義したメソッドを**あたかも自身のクラスの中で定義したかのように取り込む**ことができます。

※以下、誤解させてしまいそうn表記があったため`include`の説明に一部修正を加えました（@scivola ご指摘ありがとうございます🙇‍♀️）。

```ruby
module TestModule
  def test_method
    puts "マヨネーズ"
  end
end

class TestClass
  include TestModule

  def test_method2
    # このモジュール内で定義したかのように呼べる
    test_method
  end
end
```

ということは、クラス内から`include`すると、あるモジュールで定義したメソッドをそのクラスの**インスタンスメソッド**として追加できるということになります。

```ruby
module TestModule
  def test_method
    puts "マヨネーズ"
  end
end

class TestClass
  include TestModule
end

# インスタンスメソッドとして呼べる
test_instance = TestClass.new
test_instance.test_method # -> "マヨネーズ"
```
また第二に、`include`はクラス内だけでなくモジュール内で呼ぶこともできます。

しかし、モジュールはインスタンス化できないので、クラス内で`include`した時のようにインスタンスメソッドとして外部から呼ぶことはできません。

```ruby
module TestModule
  def test_method
    puts "ようかん"
  end
end

module TestModule2
  include TestModule

  def test_method2
    # あたかも自身が定義したように呼べるのは同じ
    test_method
  end
end

# インスタンス化できないのでインスタンスメソッドとしては呼べない
TestModule.new.test_method # =>NoMethodError: undefined method `new' for TestModule2:Module

# モジュール自身からも当然呼べない
TestModule2.test_method # =>NoMethodError: undefined method `test_method' for TestModule2:Module
```

第三に、`include`をすると、別のモジュール内で定義した**定数**も`include`したクラスやモジュール内で使えるようになります。

実際、Railsアプリケーションでは、`config/initializers/constants.rb`にてアプリケーションで使用する定数をモジュールで定義しておく実装が見られます。([Ruby on Railsで定数の指定](https://qiita.com/srockstyle/items/daed31a78c343e607822))


----結論、`include`は
1. あるモジュールで定義したメソッドを、あたかも自身で定義したかのように使えるようにする
1. クラス内で呼んだ場合は、インスタンスメソッドとして使えるようになる
1. あるモジュールで定義した定数を、別のクラスやモジュールで使えるようにする


## `#extend`
[公式リファレンス(#extend)](https://docs.ruby-lang.org/ja/latest/method/Object/i/extend.html)
`extend`は、モジュールで定義したメソッドを、`extend`を呼んだ**オブジェクト自身**のメソッドとして追加します。`include`は一律でインスタンスメソッドなので、そこが違う点です。

```ruby
module TestModule
  def test_method
    puts "ドンキーコング"
  end
end

class TestClass
  extend TestModule
end

# インスタンスから呼ぶと NoMethodError になる
test_instance = TestClass.new
test_instance.test_method # => NoMethodError: undefined method `test_method' for #<TestClass:...>

# クラス自身から呼ぶと呼べる
TestClass.test_method # => "ドンキーコング"
```

`extend`したモジュールはそれを`extend`した**オブジェクト自身**だけが呼ぶことができます。なので、上記の場合は`extend`を呼んだそのクラス自身だけが`#test_method`を呼べるということです。

実質のところ、`extend`はモジュールのメソッドを**クラスメソッド**として取り入れる際に頻繁に使われます。

しかし、厳密には`extend`= クラスメソッド ではありません。`extend`は**オブジェクト**であればクラス以外の何からでも呼べるのです。

```ruby
module TestModule
  def test_method
    puts "ドンキーコング"
  end
end

# オブジェクトなのでハッシュからも#extendが呼べる
test_hash = { partner: :dd_kong }
test_hash.extend(TestModule)

# そして使える
test_hash.test_method # => "ドンキーコング"
```

なので、「`#include`はインスタンスメソッドで、`#extend`はクラスメソッド！」と覚えるのも悪くはないのですが、厳密にいうと、

- **`#include`**
    1. モジュール内で定義したメソッドを**クラスのインスタンスメソッド**として使えるようにする
    1. モジュール内で定義したメソッドを**別のモジュール内**に追加する
    1. モジュール内で定義した**定数**を別のクラスやモジュールで使えるようにする
- **`#extend`**
    1. それを呼んだ**オブジェクト自身**に対してメソッドを追加できる
    1. クラスメソッドとして追加する場合によく使われる

という認識がより正確なようです！




[^1]:`class`はインスタンス化ができる、`module`はできない。`class`の方が高機能で、インスタンス化しないなら`module`で良いということですね。[【Ruby】クラスとモジュールの違いと使い分け](https://qiita.com/kat0/items/c17850e002cbfc9c157d)より