【初心者必見】Rubyのメソッド探索備忘録



こんにちは、食べログでwebサービス開発している@tayu1605です。
去年入社し早くもエンジニア歴(Ruby歴) 2年を迎えます。
そこで今回はRubyで重要な要素である「メソッド探索」を整理していこうと思います。

#継承関係のみのクラス
まずは基本的な 継承関係のみのクラスでのインスタンスメソッドが実行される際のメソッド探索の挙動を見ていきます。

まず2つのクラスを定義します

```ruby
class Hoge
  def method2
    "method2"
  end
end

class HogeChild < Hoge
  def method1
    "method1"
  end
end
```

そして、HogeChild のインスタンスで method1, method2 を実行します。

```ruby
HogeChild.new.method1 # "method1"
HogeChild.new.method2 # "method2"
```

**method1探索**
1. HogeChild クラスから method1 を探索
2. HogeChild クラスの method1 が実行される

**method2探索**
1. HogeChild クラスから method2 を探索
2. HogeChild の親クラスである Hoge クラスから method2 を探索
3. Hoge クラスの method2 が実行される

次にどこにも定義していない method3 を実行してみます。

```ruby
HogeChild.new.method3 # NoMethodError
```

もちろん定義されてないので、NoMethodError が返ります。
ただ、ここでもメソッド探索を見ていくと、HogeChlid クラス、 Hoge クラスだけではなく、その他のクラスへも探索しにいっています。

**method3探索**
1. HogeChild クラスから method3 を探索
2. HogeChild の親クラスである Hoge クラスから method3 を探索 
3. Hoge の親クラスである Object クラスから method3 を探索
4. Object にインクルードされたモジュールである Kernel モジュールから method3 を探索
5. Object の親クラスである BasicObject クラスから method3 を探索
6. BasicObject クラスに定義がないと、method_missing が実行され、 NoMethodError が返される    

＊Ruby ではスーパークラスを省略して記述すると、自動的に Object クラスを継承します。
そして Object クラス以降の継承関係は下記のようになっているので上のようなメソッド探索がされます。
Foo → Object → Kernel（Objectにインクルードされたモジュール） → BasicObject

#Mixin

次は Ruby で多重継承を実現する機能であるモジュールを見ていきます。

まずはモジュールを定義します。

```ruby
module Fuga
  def method1
    "method1"　
  end
end

class Hoge
  include Fuga
  def method2
    "method2"
  end
end
```

method1を実行します

```ruby
Hoge.new.method1 # "method1"
```

**method1探索**
1. Hoge クラスの method1 を探索
2. Hoge クラスにインクルードされた Fuga モジュールの method1 を探索 
3. Fuga モジュールの method1 が実行される 

ここで ancestors メソッドと superclass メソッドからインクルードされたモジュールを確認してみます。

```ruby
Hoge.ancestors # [Hoge, Fuga, Object, Kernel, BasicObject]
Hoge.superclass # Object
```

インクルードされるとインタプリタは指定されたモジュールに対応する無名クラスを作成して、スーパークラスとの間に組み入れますが、無名クラスは ancestors メソッドからは参照できるが superclass メソッドからは参照できないことがここからわかります。

#特異メソッド

次は指定されたインスタンスだけに適用される特異メソッドを見ていきます。

まずは特異メソッドを定義します。

```ruby
class Hoge
end

hoge1 = Hoge.new
def hoge1.method1
  "method1"
end

hoge2 = Hoge.new
```

特異メソッドを定義した hoge1 インスタンスと特異メソッドを定義していない hoge2 インスタンスで method1 を実行します。

```ruby
hoge1.method1 # "method1"
hoge2.method1 # NoMethodError
```

**hoge1 インスタンスの method1 探索**
1. 特異メソッドの method1 を探索
2. 特異メソッドの method1 が実行される 

**hoge2 インスタンスの method1 探索**
1. 特異メソッドがないので、探索すらされない
2. Hoge クラスに method1 を探索
・・・
6. BasicObject クラスに定義がないと、method_missing が実行され、 NoMethodError が返される

methods メソッドからも hoge1 には method1 が存在するが、 hoge2 には無いことが確認できます。

```ruby
hoge1.methods # [:method1, ・・・]
hoge2.methods # [・・・](method1なし)
```


##特異クラスに対して Mixin
次は特異クラスでincludeしてみます。

```ruby
class Hoge
  def method1
    "method1"
  end
end

module Fuga
  def method2
    "method2"
  end
end

hoge1 = Hoge.new
class << hoge1
  include Fuga
end
```
探索順序を追うために hoge1 インスタンスの method1 を実行してみます。

```ruby
hoge1.method1 # "method1"
```

**hoge1 インスタンスの method1 探索**
1. 特異クラスの method1 を探索
2. 特異クラスにインクルードされた Fuga モジュールの method1 を探索
3. hoge1 インスタンスのクラスである Hoge クラスの method1 を探索
4. Hoge クラスの method1 が実行される

また、特異クラスへのインクルードは extend で簡単に書き換えられます。

```ruby
hoge1 = Hoge.new
hoge1.extend(Fuga)
hoge1.method2 # "method2"
```

#prepend
prepend も include と同様にモジュールのメソッドをクラスに取り込むためのメソッドですが、同名のメソッドが存在する場合に振る舞いが異なります。

モジュールを定義して、それぞれ include と prepend するクラスを定義します。

```ruby
module Fuga
  def method1
    "Fugaのmethod1"
  end
end

class Piyo
  prepend Fuga
  def method1
    "Piyoのmethod1"
  end
end

class Hoge
  include Fuga
  def method1
    "Hogeのmethod1"
  end
end

```

Piyo と Hoge クラスのインスタンスで method1 を実行します。

```ruby
Piyo.new.method1 # "Fugaのmethod1"
Hoge.new.method1 # "Hogeのmethod1"
```

**Piyo の method1 探索**
1. Piyo クラスに prepend された Fuga モジュールから method1 を探索
2. Fuga モジュールの method1 が実行される

**Hoge の method1 探索**
1. Hoge クラスから method1 を探索
2. Hoge クラスの method1 が実行される

prepend は呼び出したクラスよりも先にモジュールに対してメソッド探索されることがわかります。
また、ancestors メソッドからも確認できます。

```ruby
Piyo.ancestors # [Fuga, Piyo ・・・
Hoge.ancestors # [Hoge, Fuga ・・・
```


#refinements

refinement は refine メソッドで変更を加えるクラスを宣言して using メソッドを呼び出すことで変更を有効にすることができます。

まずは定義します。

```ruby
module Fuga
  refine Hoge do
    def method1
      "using後のmethod1"
    end
  end
end

class Hoge
  def method1
    "method1"
  end

  using Fuga

  def call_method1_after_using
    method1
  end
end
```

Hoge の method1 を using メソッドを呼び出す前後の挙動を見てみます。

```ruby
hoge1 = Hoge.new
hoge1.method1 # "method1"
hoge1.call_method1_after_using # "using後のmethod1"
```
using メソッドを呼び出す前は method1 は Fuga の定義が有効になっていないので、"method1"になっています。
そして、呼び出し後、変更が有効になり、call_method1_after_using は "using後のmethod1" になっていることがわかります。


#まとめ

いかがでしたでしょうか、こちらさえ把握しておけば、どこのメソッドが呼び出されているか困ることもなくなるでしょう！

明日は @mochikichi321 による 「Bundlerを使ったGem管理について」 です！
よろしくお願いします！