特異メソッドのmix-in

mix-inにおける特異メソッドの扱いがよく分かっていなかったのでメモ。

結論は以下。

- モジュールに定義された特異メソッドはmix-inされない
- いろいろmix-inしたい場合はincludeとextendを組み合わせる
- 「extendedフックでinclude」、「includedフックでextend」などの工夫でまとめてmix-inできる
- ActiveSupport::Concernを利用すると良しなにやってくれる


<!-- more -->



例として、以下のメソッドを呼び出せるようにmix-inすることを考えていく。

- `Class_a.new.i_method`
- `Class_a.s_method`

# 継承

- `Class_a.new.i_method` => できる！
- `Class_a.s_method` => できる！

```
class Class_parent
  def i_method
    puts 'this is instance method'
  end

  def self.s_method
    puts 'this is singleton method'
  end
end

class Class_a < Class_parent
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method
```


# includeのみ

- `Class_a.new.i_method` => できる！
- `Class_a.s_method` => できない

```
module Module_1
  def i_method
    puts 'this is instance method'
  end

  def self.s_method
    puts 'this is singleton method'
  end
end

class Class_a
  include Module_1
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method 
# => NoMethodError: undefined method `s_method' for Class_a:Class
```

# extendのみ

- `Class_a.new.i_method` => できない
- `Class_a.s_method` => できる！

```
module Module_2
  def s_method
    puts 'this is singleton method'
  end
end

class Class_a
  extend Module_2
end

Class_a.new.i_method
# => NoMethodError: undefined method `i_method' for #<Class_a:0x007f8a960357c8>
Class_a.s_method
# => this is singleton method
```


# includeとextendをそれぞれ明示的に行う

- `Class_a.new.i_method` => できる！
- `Class_a.s_method` => できる！

```
module Module_1
  def i_method
    puts 'this is instance method'
  end
end

module Module_2
  def s_method
    puts 'this is singleton method'
  end
end

class Class_a
  include Module_1
  extend Module_2
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method
```

# extendするとフックでincludeも行うようにする

- `Class_a.new.i_method` => できる！
- `Class_a.s_method` => できる！

```
module Module_2
  def s_method
    puts 'this is singleton method'
  end

  def self.extended(base)
    base.include Module_1
  end

  module Module_1
    def i_method
      puts 'this is instance method'
    end
  end
end

class Class_a
  extend Module_2
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method
```

# includeするとフックでextendも行うようにする

- `Class_a.new.i_method` => できる！
- `Class_a.s_method` => できる！

```
module Module_1
  def i_method
    puts 'this is instance method'
  end

  def self.included(base)
    base.extend Module_2
  end 
  
  module Module_2
    def s_method
      puts 'this is singleton method'
    end
  end
end

class Class_a
  include Module_1
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method
```
# ActiveSupport::Concernをつかう

### ClassMethodsモジュール

ClassMethodsモジュール内に定義したメソッドを特異メソッドとしてmix-inしてくれる。

- `Class_a.new.i_method` => できる！
- `Class_a.s_method` => できる！

```
require 'active_support'

module Module_1
  extend ActiveSupport::Concern

  def i_method
    puts 'this is instance method'
  end
  
  module ClassMethods
    def s_method
      puts 'this is singleton method'
    end
  end
end

class Class_a
  include Module_1
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method
```

### includedブロック

includedブロック内の処理を、includeした先のクラス内で実行してくれる。
なのでここで特異メソッドを定義したり、（似たようなものだが）scopeを定義したりできる。

```
require 'active_support'

module Module_1
  extend ActiveSupport::Concern

  def i_method
    puts 'this is instance method'
  end

  included do
    def self.s_method
      puts 'this is singleton method'
    end
  end
end

class Class_a
  include Module_1
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method
```