1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rubyオブジェクトモデルの全体像 — class, module, オブジェクトの関係を理解する

1
Last updated at Posted at 2026-03-07

Rubyオブジェクトモデルの全体像 — class, module, オブジェクトの関係を理解する

はじめに — この図が Ruby オブジェクトモデルの全て

Rubyのオブジェクトモデルは、たった1枚の図で表現できます。

                         .class(俺を作ったのは?)
                         ─────────────────────────→
                                                          BasicObject
                                                              ↑ .superclass
                                                            Object
                                                              ↑ .superclass
               BasicObject ────────────.class───→ ┐         Module
                    ↑ .superclass                 │           ↑ .superclass
                    Object ────────────.class───→ ├──→ Class ─┘
                    ↑ .superclass                 │    ↑ │
"hello" ──.class──→ String ────────────.class───→ ┘    └─┘ .class(自分自身!)

  ↑ .superclass(俺の継承元は?)
  • 縦(上向き) = .superclass で継承元を辿る → クラスごとに違う
  • 横(右向き) = .class で「俺を作ったクラス」を辿る → 全員 Class に行き着く

この記事では、この図の各パーツを順番に解説していきます。

この記事で理解できること:

  • 横軸(.class)— なぜ全員 Class に行き着くのか
  • 縦軸(.superclass)— 継承ツリーの仕組み
  • Class の正体 — なぜ Module の子どもなのか
  • class と module の使い分け
  • ancestors — メソッド探索の仕組み
  • 特異クラス(singleton class)の役割

対象読者: Ruby初心者〜中級者
参照文献: 「メタプログラミング Ruby 第2版」Paolo Perrotta 著(オライリー・ジャパン)


1. 横軸を理解する — .class(俺を作ったクラスは?)

全てがオブジェクト

Rubyでは、数値も文字列も配列も、全てがオブジェクトです。
.class を呼ぶと、そのオブジェクトを作ったクラスがわかります。

# 全部オブジェクト。全部 .class で「作り主」がわかる
1.class          # => Integer
"hello".class    # => String
[1, 2].class     # => Array
true.class       # => TrueClass
nil.class        # => NilClass

他の言語では「ただの数値」である 1 も、Rubyではメソッドを持つオブジェクトです。

1.odd?       # => true
1.zero?      # => false
-5.abs       # => 5

オブジェクトの中身

オブジェクトの中には何が入っているのでしょうか?
答えはシンプルで、2つだけです。

┌─────────────────────────────────┐
│          オブジェクト              │
├─────────────────────────────────┤
│ ① インスタンス変数(データ)       │
│    @name = "太郎"                │
│    @age  = 25                   │
│                                 │
│ ② クラスへの参照(振る舞いの設計図)│
│    → Userクラスを見に行く         │
└─────────────────────────────────┘

※ 概念モデルとしての説明です。実際には文字列の値やオブジェクトIDなど、内部的なデータも保持しています。

メソッド(振る舞い)はオブジェクト自身ではなく、クラス側に定義されています
オブジェクトは「自分がどのクラスから生まれたか」を覚えていて、メソッドが呼ばれるとクラスを見に行きます。

class User
  def initialize(name, age)
    @name = name  # ← オブジェクトが持つ(インスタンス変数)
    @age = age    # ← オブジェクトが持つ
  end

  def greet        # ← クラスが持つ(メソッド)
    "こんにちは、#{@name}です"
  end
end

user = User.new("太郎", 25)
user.greet  # => "こんにちは、太郎です"

これが冒頭の図の**横矢印(.class)**の意味です。user.classUser を返し、"hello".classString を返します。

クラスもオブジェクト

ここがRubyの面白いところです。クラス自体もオブジェクトです。
ということは、クラスにも .class が呼べます。

String.class  # => Class
Array.class   # => Class
User.class    # => Class

つまり StringUser は、Class というクラスから生まれたオブジェクトです。

冒頭の図をもう一度見てください。左側の全てのクラスから右に向かって .class の矢印が伸び、全て Class に行き着いています

全てのクラスの .class は Class

これは継承の頂点である BasicObject ですら例外ではありません

String.class      # => Class
Object.class      # => Class
BasicObject.class # => Class  ← 継承の頂点ですら!

では Class.class は?

Class.class  # => Class (自分自身!)

ここでループが閉じます。図の右下で Class.class が自分自身を指しているのがこれです。
これがRubyのオブジェクトモデルの横軸の全てです。


2. 縦軸を理解する — .superclass(俺の継承元は?)

.class と .superclass は全く別の質問

横軸(.class)は「俺を作ったのは誰?」という質問でした。
縦軸(.superclass)は「俺の継承元は誰?」という全く別の質問です。

# 同じ String でも、答えが全く違う
String.class       # => Class  ← 俺を作ったのは?
String.superclass  # => Object ← 俺の継承元は?

.class は全てのクラスで答えが同じ(Class)でしたが、.superclassクラスごとに答えが違います

superclass で辿る継承ツリー

全てのクラスには親クラス(superclass)があります。

String.superclass   # => Object
Object.superclass   # => BasicObject
BasicObject.superclass  # => nil(ここが頂点)
継承ツリー:

BasicObject        ← 全ての頂点(ほぼ何も持たない)
  ↑
Object             ← ほとんどのクラスの親(to_s, == 等)
  ↑
String / Array / User / ...   ← 普段使うクラス

自分のクラスも同じです。

class Animal; end
class Dog < Animal; end

Dog.superclass     # => Animal
Animal.superclass  # => Object
Object.superclass  # => BasicObject
BasicObject
  ↑
Object
  ↑
Animal
  ↑
Dog

冒頭の図の左側、縦に積まれた BasicObject → Object → String がこれにあたります。


3. Class の正体 — Module を継承している

Rubyには class の他に module という仕組みがあります。module はメソッドをまとめて include でクラスに取り込んだり、名前空間を作ったりするための仕組みです。

module Greetable
  def greet
    "こんにちは"
  end
end

class User
  include Greetable
end

User.new.greet  # => "こんにちは"

ここで面白い事実があります。Class の親クラスを辿ってみましょう。

Class.superclass   # => Module
Module.superclass  # => Object
Class ──superclass──→ Module ──superclass──→ Object ──superclass──→ BasicObject
  │
  │  つまり class は module の全機能を持っている
  │  + .new でインスタンスを生成できる
  └──→ class = module + new

冒頭の図の右側を見てください。Class の上に Module があり、さらに ObjectBasicObject と続いています。
class は module を継承して .new の能力を追加したものです。

class と module は赤の他人ではなく、親子関係にあります。

厳密にはクラスは module の全機能をそのまま受け継いでいるわけではありません。
例えばクラスは他のクラスに include できず、refine も使えません。
「module の主要機能 + .new」くらいが正確な表現です。


4. class と module の使い分け

違いを比較

┌──────────────────┬──────────────────┬──────────────────┐
│                  │ class            │ module           │
├──────────────────┼──────────────────┼──────────────────┤
│ インスタンス生成  │ ○ .new できる    │ ✕ できない        │
│ 継承             │ ○ < で単一継承   │ ✕ 継承できない     │
│ Mix-in           │ ✕               │ ○ include/prepend │
│ 用途             │ 「何であるか」    │ 「何ができるか」    │
└──────────────────┴──────────────────┴──────────────────┘

is-a なら class、can-do なら module

is-a(〜である)なら class、can-do(〜できる)なら module です。

# 犬は動物「である」→ class の継承
class Animal
  def breathe
    "呼吸する"
  end
end

class Dog < Animal
  def bark
    "ワン!"
  end
end

# 泳ぐ「ことができる」→ module の Mix-in
module Swimmable
  def swim
    "泳ぐ"
  end
end

class Dog < Animal
  include Swimmable  # 泳げる能力を付与
end

dog = Dog.new
dog.breathe  # => "呼吸する"   ← Animal から継承
dog.bark     # => "ワン!"     ← Dog 自身
dog.swim     # => "泳ぐ"      ← Swimmable から Mix-in

Rubyの標準ライブラリでの実例

# Comparable — 比較「できる」能力を付与
class Weight
  include Comparable

  attr_accessor :kg

  def initialize(kg)
    @kg = kg
  end

  def <=>(other)        # これを定義するだけで
    @kg <=> other.kg
  end
end

a = Weight.new(60)
b = Weight.new(80)
a < b    # => true      ← Comparable が <, >, == 等を自動提供
a.between?(Weight.new(50), Weight.new(70))  # => true
Comparable module が提供するメソッド:
  <=> を定義するだけで、以下が全部使える

  ┌─────────────────────┐
  │  <  >  <=  >=  ==   │
  │  between?  clamp    │
  └─────────────────────┘

5. ancestors — メソッド探索の仕組み

ancestors — メソッド探索パスの全体像

superclass は親クラスだけを辿りますが、ancestorsmodule も含めた完全な探索パスを返します。

Dog.ancestors
# => [Dog, Swimmable, Animal, Object, Kernel, BasicObject]
Dog の ancestors(メソッド探索パス):

Dog → Swimmable → Animal → Object → Kernel → BasicObject
 ①      ②          ③        ④       ⑤         ⑥

メソッドが呼ばれると、①から順に探す。
見つかった時点で実行。見つからなければ次へ。

Kernel は Object に include されている module で、putsrequire 等の「どこでも使えるメソッド」を提供しています。

Kernel.instance_method(:puts)     # => #<UnboundMethod: Kernel#puts>
Kernel.instance_method(:require)  # => #<UnboundMethod: Kernel#require>

include と prepend の違い

module の取り込み方で、ancestors の挿入位置が変わります。

module Logging
  def greet
    puts "ログ: greet が呼ばれた"
    super  # ← 元のメソッドを呼ぶ
  end
end

class User
  include Logging  # Logging は User の「後ろ」に入る
end

class Admin
  prepend Logging  # Logging は Admin の「前」に入る
end
include の場合:
  User → Logging → Object → ...
  ↑ User のメソッドが先に見つかる(Logging は呼ばれない)

prepend の場合:
  Logging → Admin → Object → ...
  ↑ Logging のメソッドが先に見つかる(元のメソッドをラップできる)
User.ancestors    # => [User, Logging, Object, ...]
Admin.ancestors   # => [Logging, Admin, Object, ...]

prepend はメソッドのラップに使うのが典型的なパターンです。

module Benchmark
  def heavy_process
    start = Time.now
    result = super           # ← 元のメソッドを実行
    puts "実行時間: #{Time.now - start}秒"
    result
  end
end

class Report
  prepend Benchmark

  def heavy_process
    sleep(1)
    "完了"
  end
end

Report.new.heavy_process
# ログ: 実行時間: 1.001秒
# => "完了"

Ruby がメソッドを見つけるまで

メソッドが呼び出されたとき、Ruby は以下の手順で探します。

dog = Dog.new
dog.swim  # ← これが呼ばれたとき、何が起きるか?
Step 1: レシーバ(dog)のクラスを取得
        dog.class → Dog

Step 2: Dog の ancestors を上から順に探す

        Dog に swim ある? → ない
         ↓
        Swimmable に swim ある? → ある! → 実行
         ↓(もし無ければ続く)
        Animal → Object → Kernel → BasicObject

Step 3: BasicObject まで見つからなければ method_missing を探す
        (同じ経路をもう一度辿る)

Step 4: method_missing も無ければ NoMethodError
dog.swim の探索:

dog ─── class ───→ Dog          swim ある? → No
                    │
                    ↓
                  Swimmable     swim ある? → Yes! ★実行
                    │
                    ↓
                  Animal        (ここには来ない)
                    │
                    ↓
                  Object
                    │
                    ↓
                  BasicObject

method_missing — 最後の砦

全ての ancestors を探しても見つからなかった場合、Ruby は method_missing を呼びます。

class FlexibleUser
  def method_missing(method_name, *args)
    if method_name.to_s.start_with?("say_")
      word = method_name.to_s.sub("say_", "")
      "#{word}!"
    else
      super  # 本当に存在しないメソッドは親に任せる(NoMethodError)
    end
  end
end

user = FlexibleUser.new
user.say_hello   # => "hello!"
user.say_ruby    # => "ruby!"
user.unknown     # => NoMethodError
user.say_hello の探索:

FlexibleUser → Object → Kernel → BasicObject
  全部なし!

method_missing を探す:
FlexibleUser → method_missing ある! → 実行

method_missing は強力ですが、デバッグが困難になるため乱用は避けましょう。
respond_to_missing? も合わせて定義するのがマナーです。


6. 特異クラス(singleton class)

クラスメソッドの謎

Ruby でクラスメソッドを定義する時、こう書きます。

class User
  def self.count
    42
  end
end

User.count  # => 42

この self.count はどこに住んでいるのでしょうか?
User.instance_methods には含まれません。

User.instance_methods(false)  # => [:greet]  ← count はない

答えは**特異クラス(singleton class)**です。

特異クラスとは

全てのオブジェクトは、自分だけの隠れたクラスを持っています。それが特異クラスです。

User.singleton_class  # => #<Class:User>
通常の理解:
  User は Class のインスタンス

本当の構造:
  User → #<Class:User>(特異クラス) → Class
         ↑
         ここに self.count が住んでいる

図解:

user(オブジェクト)
  │
  │ class
  ↓
User(クラス)
  │
  │ class
  ↓
#<Class:User>(User の特異クラス)
  │            ├── count メソッド  ← ここにいた!
  │ superclass
  ↓
#<Class:Object>(Object の特異クラス)
  │
  ↓
  Class

特定のオブジェクトだけにメソッドを追加

特異クラスを使うと、特定のオブジェクト1個だけにメソッドを追加できます。

alice = User.new("Alice", 20)
bob   = User.new("Bob", 25)

# alice だけに特別なメソッドを追加
def alice.admin?
  true
end

alice.admin?  # => true
bob.admin?    # => NoMethodError(bob には無い)
alice の構造:

alice → #<Class:alice>(alice の特異クラス)→ User → Object
         ├── admin? メソッド
         │
         alice だけが持つメソッド

bob の構造:

bob → User → Object
      (admin? は無い)

まとめ

最後に、冒頭の図をもう一度見てみましょう。ここまで読んだあなたなら、全てのパーツが理解できるはずです。

                         .class(俺を作ったのは?)
                         ─────────────────────────→
                                                          BasicObject
                                                              ↑ .superclass
                                                            Object
                                                              ↑ .superclass
               BasicObject ────────────.class───→ ┐         Module
                    ↑ .superclass                 │           ↑ .superclass
                    Object ────────────.class───→ ├──→ Class ─┘
                    ↑ .superclass                 │    ↑ │
"hello" ──.class──→ String ────────────.class───→ ┘    └─┘ .class(自分自身!)

  ↑ .superclass(俺の継承元は?)
概念 一言で
オブジェクト インスタンス変数 + クラスへの参照
クラス メソッドの置き場。クラス自体も Class のインスタンス
module 能力を付与する仕組み。include/prepend で取り込む
.class 「俺を作ったのは?」→ 全員 Class に行き着く
.superclass 「俺の継承元は?」→ クラスごとに違う
ancestors メソッド探索の順序。include/prepend で変わる
特異クラス オブジェクト固有の隠れたクラス。クラスメソッドの住処

これらを理解していると、Rubyのコードリーディングやデバッグの精度が格段に上がります。
ancestors を見ればメソッドがどこから来ているか一目瞭然ですし、method(:xxx).owner で定義元を特定できます。

# このメソッド、どこで定義されてる?
User.new("太郎", 25).method(:greet).owner  # => User
[1,2,3].method(:map).owner                 # => Array
1.method(:zero?).owner                     # => Integer

参照文献: 「メタプログラミング Ruby 第2版」Paolo Perrotta 著(オライリー・ジャパン)

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?