
はじめに
初めまして、スパイダープラスでWebエンジニアをしているizkです。
普段は、S+Reportというプロダクトでバックエンドを中心にRubyやPHPなどを書いています。
さて、Rubyでコーディングしていると、モジュールをクラスにincludeしてメソッドを呼び出す場面はよくあると思います。
ある日、同じようにモジュールをincludeしてメソッドを呼び出そうとしたところ、undefined methodエラーが発生しました。
原因を調べてみると、メソッドを呼び出そうとした場所がクラスメソッド内だったためでした。includeをextendに書き換えると、無事にメソッドを呼び出せるようになりました。
この時の私は「インスタンスメソッドにはinclude、クラスメソッドにはextend」くらいの認識でコードを書いていたため、なぜそうなるのか仕組みを調べてみることにしました。
本記事の対象読者
本記事のゴール
Rubyのオブジェクトモデル(クラス、モジュール、特異クラス)を整理しつつ、クラスメソッドの呼び出しの仕組みを理解すること
0. Rubyはすべてがオブジェクト
前提
Rubyにおけるオブジェクトとは、状態、振る舞い、クラスへの参照をひとまとめにしたものです。
※インスタンスという言葉は、クラスから生成されたオブジェクトという意味で使っています。
はじめに
Rubyは「純粋オブジェクト指向言語」と呼ばれていて、すべてがオブジェクトとして扱われます。
オブジェクトはクラスの参照を持っているため、何らかのクラスのインスタンスになります。
サンプルコード:
# すべてがオブジェクトである証拠 1.class # => Integer "hello".class # => String nil.class # => NilClass true.class # => TrueClass [1, 2, 3].class # => Array # クラス自体もオブジェクト(Classクラスのインスタンス) Integer.class # => Class String.class # => Class Array.class # => Class Class.class # => Class
最後の行を見ると、ClassクラスもClassのインスタンスだということが分かります。
1. クラス
はじめに
Rubyにおけるクラスは、インスタンスの設計図であり、同時にオブジェクトでもあります。
クラスからインスタンスを生成でき、クラス自体もClassクラスのインスタンスとして振る舞います。
また、クラスはスーパークラス(親クラス)を持ち、継承によって振る舞いを引き継ぐことができます。
1.1 クラスとインスタンス
Userクラスを定義して、そのインスタンスを生成してみます。
以下にサンプルコードとオブジェクトモデル図を記載します。
サンプルコード:
class User; end user = User.new
1行目でクラスを定義して、2行目でクラスのnewメソッドを使ってインスタンスを生成しています。
オブジェクトモデル図
オブジェクトモデル図は、四角はオブジェクトを、矢印は参照を表現しています。
オブジェクトを右へ移動するとクラスが見えて、上へ移動するとスーパークラスが見えるようになっています。
このスーパークラスをたどる経路を「継承チェーン」と呼びます。この考え方は、後述するメソッド探索でも重要になります。
1.2 状態と振る舞い
先ほどのUserクラスに状態(インスタンス変数)と、振る舞い(メソッド)を追加してみます。
このメソッドはインスタンスメソッドと呼ばれ、インスタンスをレシーバとして呼び出されます。
サンプルコード:
class User
def initialize(name)
@name = name # 状態(インスタンス変数)
end
def greet # 振る舞い(メソッド)
"Hi, I'm #{@name}!"
end
end
モデル図
インスタンス変数(@name)はインスタンスが持つため、オブジェクト毎に固有の状態を保持できます。
メソッド(greet)はクラスが持つため、すべてのインスタンスで同じ振る舞いを共有できる仕組みになっています。
1.3 メソッド探索
インスタンスからメソッドを呼び出すとき、Rubyは特定の順序でメソッドを探索します。
この探索ルールは「右へ一歩、そして上へ」と表現されます。
サンプルコード:
class Animal
def breathe
"スーハー"
end
end
class Human < Animal; end
human = Human.new
human.breathe # => "スーハー"
Humanクラスにはbreatheメソッドがありませんが、スーパークラスのAnimalにあるため呼び出せます。
モデル図
この「右へ一歩、そして上へ」というルールは、後述するモジュールのincludeや特異クラスを理解する上で非常に重要になります。
1.4 クラスのクラス
最初に見たとおり、クラスも何かしらのクラスのインスタンスです。
Userクラスのモデル図に追加して、確認してみます。
モデル図
自己参照という仕組みを使うため、ClassのクラスはClass自身になります。
2. モジュール
はじめに
本章では、クラスに似たオブジェクトで、継承チェーン上に存在可能な"モジュール"について説明していきます。
2.1 モジュールとクラス
実は、モジュール(Module)とはクラス(Class)のスーパークラスです。
つまり、全てのクラスはモジュールを継承しているため、クラスはモジュールであると言えます。
サンプルコード:
Class.superclass # => Module
さらに、ModuleのスーパークラスはObjectになります。
サンプルコード:
Module.superclass # => Object
モデル図
モデル図にすると、参照が循環しているように見えますがそんなことは無く、UserもそのクラスであるClassもObjectのサブクラスというだけです。
classとsuperclassの参照を区別しながら考える必要があります。
2.2 Kernelモジュール
Userクラスの継承チェーンを確認すると、Kernelというものが見えてきます。
これは、Objectクラスにincludeされたモジュールになります。
Kernelモジュールには、puts、require、loop、raiseなど基本的なメソッドが定義されています。
つまり、クラス内でputsなどのメソッドを呼び出せるのは、Objectを継承しており、ObjectがKernelモジュールをincludeしているためです。
サンプルコード:
class User; end
User.ancestors # => [User, Object, Kernel, BasicObject]
2.3 モジュールのinclude
モジュールを作成すると、includeメソッドでクラスに追加できます。
includeしたモジュールは、継承チェーンのinclude先のクラスとそのスーパークラスの間に挿入されます。
サンプルコード:
module Greetable
def greet
"Hello!"
end
end
class User
include Greetable
end
User.ancestors # => [User, Greetable, Object, Kernel, BasicObject]
user = User.new
user.greet # => "Hello!"
モデル図
3. 特異クラス(シングルトンクラス)
はじめに
本章では、特定のオブジェクトが1つだけ持てる隠れたクラス「特異クラス」について説明していきます。
特異クラスを理解することで、本記事のゴールであるクラスメソッドの呼び出しについて深く理解することができます。
3.1 クラスメソッド
本記事のテーマであるクラスメソッドについて見ていきます。
クラスメソッドとは、クラスをレシーバとして呼び出せるメソッドのことで、メソッドの先頭にself.を付けることで定義できます。
サンプルコード:
class User
def self.count
100
end
end
User.count # => 100
alice = User.new("Alice")
alice.count # => NoMethodError(インスタンスからは呼び出せない)
では、クラスメソッドはどこに存在するのでしょうか?
それを確認するために、クラスメソッドに関わりの深い特異メソッドを見ていきます。
3.2 特異メソッド
特異メソッドとは、特定のインスタンスだけに定義されたメソッドで、同じクラスの他のインスタンスにも影響しないメソッドです。
サンプルコード:
alice = User.new("Alice")
# aliceオブジェクトだけに特異メソッドを定義
def alice.admin?
true
end
alice.admin? # => true
bob = User.new("Bob")
bob.admin? # => NoMethodError(bobには定義されていない)
では、特異メソッドはどこに存在しているのでしょうか?
答えは、特異クラスです。
3.3 特異クラス(シングルトンクラス)
特異クラスは、各オブジェクトが1つだけ持てる隠れたクラスで、1つだけなので、シングルトンクラスとも呼ばれます。
特異メソッドを定義すると、Rubyは必要に応じて特異クラスを生成し、その中に特異メソッドを格納します。
aliceに特異クラスと特異メソッドが存在していることをサンプルコードで確認してみましょう。
サンプルコード:
# 特異クラスの存在を確認 alice.singleton_class # => #<Class:#<User:0x00007f...>> alice.singleton_class.superclass # => User # 特異メソッドが特異クラスに定義されていることを確認 alice.singleton_class.instance_methods(false) # => [:admin?]
特異クラスを含めたモデル図:

aliceのクラスが特異クラスとなっています。
これは、メソッド探索が「右へ一歩、そして上へ」の順で行われるため、特異メソッドを最初に探索するにはこの位置に入る必要があるためです。
つまり、aliceの概念的なクラスはUserですが、実際のクラスは特異クラス(#<Class:alice>)になります。
3.4 クラスメソッドと特異クラスの関係
もうお気づきかもしれません。クラスメソッドはクラスの特異メソッドになります。
そのため、クラスメソッドが定義されると、クラスに特異クラスが生成されて、その中にクラスメソッドが格納されます。
サンプルコード:
class User
def self.count
100
end
end
特異クラスを含めたモデル図:

インスタンスの特異クラス同様にスーパークラスのクラスメソッドを探索する必要があるため、上記のような継承関係になります。
つまり、Userクラスの概念的なクラスはClassで、実際のクラスは特異クラス(#<Class:User>)になります。
3.5 モジュールのextend
ここまでの内容を踏まえて、extendの仕組みを見ていきましょう。
extendは、モジュールのメソッドをクラスメソッドとして追加します。
includeがインスタンスメソッドを追加するのに対し、extendはクラス自体にメソッドを追加します。
サンプルコード:
module Greetable
def greet
"Hello!"
end
end
class User
extend Greetable
end
User.greet # => "Hello!"(クラスメソッドとして呼び出せる)
user = User.new
user.greet # => NoMethodError(インスタンスメソッドとしては呼び出せない)
extendを使うと、モジュールはクラスの特異クラスの継承チェーンに挿入されます。
3.4で見たように、クラスメソッドは特異クラスに住んでいるため、extendしたモジュールのメソッドもクラスメソッドとして呼び出せるようになります。
最後にモジュールをincludeとextend両方行った場合をモデル図で確認していきます。
サンプルコード:
class User include Greetable # インスタンスメソッドとして追加 extend Greetable # クラスメソッドとして追加 end
モデル図:

このように、includeは左側の継承チェーン(インスタンスメソッド用)に、extendは右側の継承チェーン(クラスメソッド用)にモジュールを挿入します。
まとめ
本記事のゴールである「クラスメソッドの呼び出しの仕組み」をまとめると、以下のようになります。
- クラスメソッドは、クラスの特異クラスに存在している
- クラスメソッドを呼び出すと、クラスの特異クラスの継承チェーンを「右へ一歩、そして上へ」と探索する
includeでモジュールを追加すると、左側の継承チェーン(インスタンスメソッド用)に挿入されるため、インスタンスメソッドとして呼び出せるextendでモジュールを追加すると、右側の継承チェーン(クラスメソッド用)に挿入されるため、クラスメソッドとして呼び出せる
つまり、インスタンスをレシーバとしてメソッドを呼び出したい場合はinclude、クラスをレシーバとして呼び出したい場合はextendを使います。
感想
インスタンスとクラスの間に特異クラスが作られるのは、実物と設計図の間に他の要素が入る感じがして違和感がありましたが、これによりインスタンスメソッドとクラスメソッド両方にメソッド探索のルールが適用できる仕組みになっていると思いました。
実際、特異クラスは隠れたクラスという扱いなので、普段は意識せずコーディングできるようになっています。
冒頭で紹介したundefined methodエラーは、includeしたモジュールが左側の継承チェーン(インスタンスメソッド用)に入ったため、クラスメソッドとして呼び出せなかったと理解できました。
おわりに
ここまでお読みいただき、ありがとうございました!
本記事は、社内で開催された技術LT大会での発表内容をもとに執筆しました。スパイダープラスでは、こうした技術共有の場を定期的に設けており、エンジニア同士が学び合う文化を大切にしています。
スパイダープラスでは、建設DXを推進する仲間を募集中です。Rubyをはじめ様々な技術を使って開発しています。興味のある方は、ぜひお気軽にご連絡ください!