Ruby

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

こんにちは、食べログでwebサービス開発している@tayu1605です。

去年入社し早くもエンジニア歴(Ruby歴) 2年を迎えます。

そこで今回はRubyで重要な要素である「メソッド探索」を整理していこうと思います。


継承関係のみのクラス

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

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

class Hoge

def method2
"method2"
end
end

class HogeChild < Hoge
def method1
"method1"
end
end

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

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 を実行してみます。

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 で多重継承を実現する機能であるモジュールを見ていきます。

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

module Fuga

def method1
"method1" 
end
end

class Hoge
include Fuga
def method2
"method2"
end
end

method1を実行します

Hoge.new.method1 # "method1"

method1探索

1. Hoge クラスの method1 を探索

2. Hoge クラスにインクルードされた Fuga モジュールの method1 を探索

3. Fuga モジュールの method1 が実行される

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

Hoge.ancestors # [Hoge, Fuga, Object, Kernel, BasicObject]

Hoge.superclass # Object

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


特異メソッド

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

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

class Hoge

end

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

hoge2 = Hoge.new

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

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 には無いことが確認できます。

hoge1.methods # [:method1, ・・・]

hoge2.methods # [・・・](method1なし)


特異クラスに対して Mixin

次は特異クラスでincludeしてみます。

class Hoge

def method1
"method1"
end
end

module Fuga
def method2
"method2"
end
end

hoge1 = Hoge.new
class << hoge1
include Fuga
end

探索順序を追うために hoge1 インスタンスの method1 を実行してみます。

hoge1.method1 # "method1"

hoge1 インスタンスの method1 探索

1. 特異クラスの method1 を探索

2. 特異クラスにインクルードされた Fuga モジュールの method1 を探索

3. hoge1 インスタンスのクラスである Hoge クラスの method1 を探索

4. Hoge クラスの method1 が実行される

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

hoge1 = Hoge.new

hoge1.extend(Fuga)
hoge1.method2 # "method2"


prepend

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

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

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 を実行します。

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 メソッドからも確認できます。

Piyo.ancestors # [Fuga, Piyo ・・・

Hoge.ancestors # [Hoge, Fuga ・・・


refinements

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

まずは定義します。

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 メソッドを呼び出す前後の挙動を見てみます。

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管理について」 です!

よろしくお願いします!