【Ruby】メソッドの探索方法を理解する

#概要
オブジェクトに対してメソッドが呼び出された際にRubyがどの様にメソッドを呼び出しているのかサンプルコードと共にまとめました。

#自身のメソッドを呼び出した時の探索方法

```ruby
class Class1
  def hello
    puts 'Hello from Class1'
  end
end

obj1 = Class1.new
obj1.hello #=> Hello from Class1

p Class1.ancestors #=> [Class1, Object, Kernel, BasicObject]
```
Rubyのインタプリタは`obj1`の直接のクラスである`Class1`を参照し、そこで定義されている`Class1#hello`を実行します。

__参照順序は以下の図の通りです。__
<img width="694" alt="Screen Shot 2018-07-26 at 11.24.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/258219/2911be6c-f339-b8e1-bb10-11197bfeae36.png">

#スーパークラスのメソッドを呼び出す際の探索方法

```ruby
class Class1
  def hello
    puts 'Hello from Class1'
  end
end

class Class2 < Class1; end

obj2 = Class2.new
obj2.hello #=> Hello from Class1

p Class2.ancestors #=> [Class2, Class1, Object, Kernel, BasicObject]
```
Rubyインタプリタはまず`obj2`の直接のクラスである`Class2`を参照します。
しかし、`Class2`には`hello`メソッドが存在しないので親クラスである`Class1`を参照し、クラス内に定義されている`Class1#hello`を実行します。

__参照順序は以下の図の通りです。__
<img width="761" alt="Screen Shot 2018-07-26 at 11.22.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/258219/ca69b2b4-7306-c83e-1752-8073a6ce6e93.png">

#特異メソッドを呼び出した際の探索方法

```ruby
class Class1
  def hello
    puts 'Hello from Class1'
  end
end

obj1 = Class1.new

def obj1.hello
  puts 'Hello From Singleton'
end

obj1.hello #=> Hello From Singleton
```
Rubyのインタプリタはオブジェクトのクラスより先に特異クラスを参照し、そのクラス内のメソッドを先に実行します。

__参照順序は以下の図の通りです。__
<img width="753" alt="Screen Shot 2018-07-26 at 11.34.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/258219/bd0b9c36-a42a-89f1-4cd0-a47d474c0afe.png">

#クラスにモジュールをミックスインした際のメソッドの探索方法
```ruby
module Module1
  def hello
    puts 'Hello from Module1'
  end
end

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

class Class2 < Class1
  include Module1
end

obj2 = Class2.new
obj2.hello #=> Hello from Module1

p Class2.ancestors #=> [Class2, Module1, Class1, Object, Kernel, BasicObject]
```
`include`されたモジュールは`include`したクラスの親クラスとの間にあらわれるようになります。
Rubyのインタプリタはまず直接のクラスである`Class2`を参照し`hello`メソッドを探します。しかし、`Class2`に`hello`メソッドは定義されていないので、次に`Module1`の機能を取り込んだクラスを参照し、そのクラス内の`hello`メソッドを実行します。

__参照順序は以下の図の通りです。__
<img width="808" alt="Screen Shot 2018-07-26 at 13.13.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/258219/51cfd6e0-300a-728c-c19a-5d1cfc39191d.png">

#複数のモジュールがミックスインされている際のメソッドの探索方法

```ruby
module Module1
  def hello
    puts 'Hello from Module1'
  end
end

module Module2
  def hello
    puts 'Hello from Module2'
  end
end

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

class Class2 < Class1
  include Module1
  include Module2
end

obj2 = Class2.new
obj2.hello #=> Hello from Module2
```
２つ以上のモジュールが１つのクラスに`include`されている場合には`include`した順序が後のモジュール内のメソッドが優先的に呼ばれるようになります。

__参照順序は以下の図の通りです。__
<img width="829" alt="Screen Shot 2018-07-26 at 13.39.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/258219/485eabc2-a2a9-2bf8-198d-1315d763ef94.png">

#モジュールがプリペンドされている際のメソッドの探索方法
`Module#prepend`は`Module#include`のように、クラスやモジュールに別のモジュールの機能を取り込むためのメソッドです。

__以下が`Module#prepend`の特徴です。__

- `Module#include`で取り込んだメソッドと同名のメソッドが`Module#prepend`で入れたモジュールに定義されていた場合`Module#prepend`で入れたメソッドが優先して呼ばれます。
- `Module#prepend`で組み込んだモジュールは組み込んだクラスの前におかれます。

```ruby
module Module1
  def hello
    puts 'Hello from Module1'
  end
end

module Module2
  def hello
    puts 'Hello from Module2'
  end
end

module Module3
  def hello
    puts 'Hello from Module3'
  end
end

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

class Class2 < Class1
  prepend Module3
  include Module1
  include Module2
 
  def hello
    puts 'Hello from Class2' 
  end
end

obj2 = Class2.new
obj2.hello #=> Hello from Module3

p Class2.ancestors #=> [Module3, Class2, Module2, Module1, Class1, Object, Kernel, BasicObject]
```
上記でも記しましたように、`Module#prepend`で取り入れられたモジュールは直接のクラス`Class2`や`include`された`Module1`や`Module2`よりも優先して参照され、その中で定義されている`hello`メソッドが呼び出されます。

__参照順序は以下の図の通りです。__
<img width="799" alt="Screen Shot 2018-07-26 at 13.41.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/258219/c165b1bd-6a37-61a4-f01d-274221333634.png">

#オブジェクトに存在しないメソッドを呼び出した場合

```ruby
module Module1
  def hello
    puts 'Hello from Module1'
  end
end

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

class Class2 < Class1
  include Module1
end

obj2 = Class2.new
obj2.goodmorning #=>  undefined method `goodmorning'(NoMethodError)

p Class2.ancestors #=> [Class2, Module1, Class1, Object, Kernel, BasicObject]
```
Rubyのインタプリタはメソッドが見つかるまでクラスの継承ツリーを辿っていきます。
そして継承ツリーを一番上（`BasicObject`)まで辿ってもメソッドが見つからない場合には`BasicObject#method_missing`を呼び出します。

#メソッド探索方法まとめ

- (1) Rubyのインタプリンタはレシーバの特異クラスを参照しメソッドを探す。
- (2) 特異クラスが存在しない場合はレシーバの直接のクラスを参照しメソッドを探す。
- (3) モジュールが`include`されている場合その中からメソッドを探す。
- (4) 直接のクラスにメソッドがなくモジュールも`include`されていない場合、継承した親クラスを参照しメソッドを探す。
- (5) 継承ツリーの一番上のクラスまで辿ってもメソッドが見つからない場合、`BasicObject#method_missing`を呼び出す。