Rubyのclass入門(前編): getter / setter・self・クラスメソッドの正体を突き止める

はじめに

システム開発第一事業部の奥田です。普段はフルスタックエンジニアとしてWebアプリの開発を担当しています。

直近でやっていた案件ではTypeScriptをゴリゴリに書いていたのですが、 案件が切り替わってRuby(Ruby on Rails)を使う案件に入ることになりました!

TypeScriptをやってみて思ったところは、やっぱりRubyって癖のある独自の世界観がありますね。

私の中のTypeScript脳をRuby脳に切り替えるついでに、せっかくならJavaScriptの非同期について記事にしたように、 Rubyに関しても何か記事を書けたらいいなと思い、今回は第1回として、Rubyのclassを整理していきます!

先ほども言いましたが、Rubyって一癖あるので初学者向けの言語として紹介されがちですが、 これまでの経験から言うとあんまり初学者向けじゃないようにも感じます。

なので、この独特な世界観を初学者が理解できる助け舟みたいなものを作りたいなと思ったのがきっかけです!

将来的にはclassだけではなくて、並列・並行処理についても書けたらいいなと思っています... (過去にJavaScriptの非同期についても書いたので)


RubyのclassはTypeScriptなどのclassと同じ感覚で読むと途中で引っかかります。

最初は「インスタンスを作るための設計図」と理解して進みがちです。 ただ、実務のコードを読み始めると、その説明だけでは足りない場面が出てきます。

特に引っかかりやすいのが、クラス定義の中で普通にメソッド呼び出しが出てくるところです。

Railsではモデルでよく見るhas_manyとかだけでなく、ActiveSupport::Concernを使ってインスタンスメソッドやクラスメソッド、 関連定義をあとから差し込む書き方も出てきます。

ここでclassの見え方が一気に難しくなります。

たとえば、Railsを見ているとこんなコードが出てきます。

class Author < ApplicationRecord
  has_many :books
end

Rubyではメソッド呼び出しの括弧を省略できます。

この前提を知らないと、次の1行がそもそもメソッド呼び出しに見えません。

「なぜクラス定義の中にいきなり:booksが出てくるのか」

「これは何をしている1行なのか」

と戸惑いやすくなります。

さらに「なぜクラス定義の中でメソッドを呼んでいるのか」という疑問も出ててくるかと思います。

ですが安心してください!

この疑問を言葉にできるようになると、Rubyらしい書き方がかなり読みやすくなります。

そのためにこの記事を書いたのですから!

というわけで、本記事のシリーズはは以下の3部構成で進めようかと思っています!

  • 第1回(今回):classの基本、クラス本体のselfinitialize、クラスメソッドの見方までを整理。
  • 第2回(予定): 継承、include/extend/prepend、メソッド探索順、定数スコープ、可視性を扱う。
  • 第3回(予定): その知識を使いながらRailsのhas_manyscope、コールバック、delegateActiveSupport::Concernと追い方を整理する。

途中で変わったりもするかもですのでご了承ください。

まずはこの記事のゴールを確認

ゴールがないと行き先を失うので迷える子羊状態です。

しっかりとゴールを意識していきましょう。

  • class定義本体が「宣言」ではなく「実行されるコード」であると説明できる
  • selfが文脈ごとに誰を指すかを説明できる
  • attr_accessorなどのアクセサーとself.name = ...の関係を説明できる
  • def self.xxxclass << selfが何をしているかを説明できる

検証環境について

  • 実行確認: Ruby 3.3.11
  • 仕様確認: Ruby公式ドキュメント 3.3系
  • Rails関連の説明確認: Ruby on Rails公式ドキュメント 8.1系

結論

忙しい人向けに記事の内容をまとめたものを置いておきます!

  • Rubyのclassは「設計図を書く場所」であると同時に、「その場で実行されるコードブロック」でもある
  • クラス定義の中で書いたdefや通常のメソッド呼び出しはその場で評価される
  • Xxx.newは内部でallocateinitializeを順に呼び、生成されたオブジェクトへ初期値を入れる
  • Rubyではクラス自体もオブジェクトなので、クラスメソッドは「クラスオブジェクト専用のメソッド」として見ると整理しやすい

1. クラスとインスタンス、getter/setterとアクセサーの基礎

まずは次の3つを理解していきましょう。

  • classはオブジェクトの種類を定義する
  • newはそのクラスから実際のオブジェクトを作る
  • initializeは作られた直後のオブジェクトへ初期値を入れる

そのうえで、クラスを読んでいると早い段階でgettersetterが出てきます。

  • getter: 値を読むためのメソッド
  • setter: 値を書き込むためのメソッド

たとえば@nameというインスタンス変数の値を外から読み書きしたいとしても、 Rubyでは通常の書き方ではそのまま直接アクセスできません。

そのため、外から扱いたいならgetter/setterというメソッドを用意する必要があります。

まずgetter/setterがないとどうなるかを見てみます。

class User
  def initialize(name)
    @name = name
  end
end

user = User.new("Tom")

begin
  p user.name
rescue NoMethodError => e
  p e.class
end

begin
  user.name = "Bob"
rescue NoMethodError => e
  p e.class
end

# NoMethodError
# NoMethodError

ここで使っているrescueも初学者だと急に出てきて戸惑いやすいので軽く説明しておきます。

  • begin,end: まずこの中のコードを実行する
  • rescue: 実行中にエラーが起きたら、そこで止めずにこちらの処理へ移る
  • NoMethodError: 「そのメソッドはありません」という意味のエラー

ここは1行ずつ分解すると読みやすくなります。

  • user.name: userをレシーバにしてnameメソッドを呼んでいる
  • user.name = "Bob": userをレシーバにしてname=("Bob")を呼んでいる


Rubyでは.でつないだ時点で「左側オブジェクトに右側メソッドを送る」という意味になります。

今回はUserクラスにnamename=も定義していないので、どちらの呼び出しでもNoMethodErrorになります。

つまりこのrescueは、失敗することを確認するためにエラー内容を受け取って表示している、ということです。

@nameに値は入っていますがnamename=も定義していないので、外からは読めませんし、書き込めません。

ここで必要になるのがgetter/setterです!

  • getter: インスタンス変数の値を外へ渡すための「読み込み窓口」
  • setter: インスタンス変数へ値を入れるための「書き込み窓口」

この2つを用意すると、外部コードは@nameへ直接触らず、クラスが公開した操作だけを通して読み書きできます。 たとえばsetterの中に「空文字は受け付けない」のようなチェックを入れることもできたりします。

class User
  def initialize(name)
    @name = name
  end

  def name
    @name
  end

  def name=(value)
    @name = value
  end
end

このコードでは、

  • nameが getter
  • name=が setter

です。

つまりRubyでは、

  • @name: インスタンス変数そのもの
  • name: @nameを読み込むgetter
  • name=: @nameへ値を入れるsetter

という3つを分けて考える必要があります。

この区別を先に持っておくと、あとで出てくるself.name = "Tom"などを「変数代入ではなくsetter呼び出しだ」と読みやすくなります。


でも、インスタンス変数毎にわざわざgetter/setterを定義するのは冗長すぎやしませんか?

それに毎回書くと同じようなコードを量産するわけですし、面倒ですよね?


安心してください!

Rubyはそんなめんどくさい作業を自動でやってくれる便利なメソッドを用意してくれています。

それがクラスなどでよく見かけるattr_reader,attr_writer,attr_accessorで、
これらはgetter/setterを自動生成するためのメソッドというわけです。

  • attr_reader :name:nameというreaderメソッドを作る
  • attr_writer :name:name=というwriterメソッドを作る
  • attr_accessor :name: 上の2つを両方作る

ここで押さえたいのは、これらも特別な文法ではなく、クラス定義の中で呼ばれているメソッドだという点です。

なのでattr_reader :nameはその場で

「nameというインスタンスメソッド(= getter)を1つ定義している」

というわけです。

class User
  attr_accessor :name

  # 以下を定義しているのと同じ
  # def name
  #   @name
  # end
end

ここまできたら以下のコードの流れも完璧に説明できるのではないでしょうか?

class User
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

user = User.new("Tom")
puts user.name

このコードで起きていることは次の4つです。

  1. class UserUserというクラスを定義する
  2. User.new("Tom")Userクラスのインスタンスを作る
  3. initializeが呼ばれて@name"Tom"が入る
  4. user.nameを呼ぶと、attr_reader :nameが作ったnameメソッドで値を読む

ここまでをまとめると、アクセサー系の理解は次の形で整理できます。

  • @nameはインスタンス変数そのもの
  • name@nameを読む getter
  • name=@nameへ値を入れる setter
  • attr_reader/attr_writer/attr_accessorは、その getter / setter を自動生成する

この区別が曖昧なままだと、あとでself.name = "Tom"を見たときに「これは変数代入なのか、メソッド呼び出しなのか」が分かりにくくなります。

賢くなった気になれる補足: レシーバとは何か

レシーバは「そのメソッド呼び出しを受け取るオブジェクト」のことです。

user.name

この1行では、userがレシーバ、nameが呼び出しているメソッドです。

レシーバが省略されているときは、暗黙的にselfがレシーバになります。 (selfに関しては後続の章で説明します!)

name          # 実質 self.name
self.name = "Tom"

この見方を先に持っておくと、後で出てくる 「なぜself.を付けないとsetterが呼ばれないのか」が読みやすくなります。

サンプルで確認:dammy_attr_accessorを自作する

attr_accessorが内部でやっていることを理解するために、簡易版のdammy_attr_accessorで再現してみます。

module DammyAccessor
  def dammy_attr_accessor(*names)
    names.each do |name|
      ivar = "@#{name}"

      define_method(name) do
        instance_variable_get(ivar)
      end

      define_method("#{name}=") do |value|
        instance_variable_set(ivar, value)
      end
    end
  end
end

class User
  extend DammyAccessor
  dammy_attr_accessor :name, :age
end

user = User.new
user.name = "Tom"
user.age = 20

p user.name
p user.age
p User.instance_methods(false).grep(/\A(name|name=|age|age=)\z/).sort
# => "Tom"
# => 20
# => [:age, :age=, :name, :name=]

色々と難しいものが出てきますが(後続の補足で説明してます)、このサンプルで見てほしい点は次の2つです。

  • dammy_attr_accessor :name, :ageを呼んだ時点で、name/name=/age/age=がクラスへ追加される
  • 追加されたgetter/setterは、instance_variable_get/instance_variable_set@name@age にアクセスしている

つまりattr_accessorは魔法ではなく、クラス定義中にメソッドを定義している処理だと読めます。

賢くなった気になれる補足:define_methodinstance_variable_get/instance_variable_setの役割

dammy_attr_accessorの中で使っている3つは次の役割を持ちます。

  • define_method(:name) { ... }: メソッドを文字どおり「定義する」。この例ではnamename=をその場でクラスへ追加する
  • instance_variable_get("@name"): 文字列やシンボルで指定したインスタンス変数の値を読む
  • instance_variable_set("@name", value): 文字列やシンボルで指定したインスタンス変数へ値を書く

つまりattr_accessorの内部処理としては、 「define_methodでgetter/setterを生やし、その中でinstance_variable_get/instance_variable_setを使って管理している」 と理解できます。

賢くなった気になれる補足: Rubyのclassは設計図だけではない

TypeScriptやJavaの感覚だと、クラス定義は「メソッドやプロパティを書いておく箱」に見えやすいです。

ですがRubyでは、classは「設計図を書く宣言」だけではなく、クラスオブジェクトを作る(または再オープンする)実行コンテキストでもあります。

ここでは概念だけ先に説明します。(後の章で深掘りしてます)

  • class ... endに入った時点で、その文脈のselfはクラスになる
  • そのクラスにインスタンスメソッドや定数を書く場所でもある
  • そのクラス自身へメソッド呼び出しを行う場所でもある

詳細な実行順は次の2. クラス定義の中身は上から順番に実行されるで具体例を使って確認してます。

この前提を持っておくと、クラス定義中のincludeattr_accessorを読みやすくなるかと思います。

賢くなった気になれる補足:name=みたいなメソッド名はRubyでは普通

ほかの言語から来ると、name=のように記号が入ったメソッド名はかなり違和感がありかと思います。

include?のように?ならなんとなくわかりますが、=がメソッド名に来ることは中々想定できないのではないでしょうか?

Rubyではこれは特別扱いではなく、普通のメソッド名です。

なので次は正しいRubyコードです。

class User
  def name=(value)
    @name = value
  end
end

user = User.new
user.name = "Bob"

user.name = "Bob"は、文法的にはuser.name=("Bob")というメソッド呼び出しとして解釈されます。

つまり=は「代入専用の特殊構文」ではなく、「name=というメソッド名の一部」です。

同じ発想で、Rubyではvalid?save!のようなメソッド名も普通に使われます。

  • ?で終わる名前: 真偽値を返す意図を表すことが多い
  • !で終わる名前: 破壊的変更や強い副作用がある版を表すことが多い

一次情報: - Ruby公式:methods(Method Names / Assignment methods)

2. クラス定義の中身は上から順番に実行される

前の補足で触れた概念をここからは実コードで確認していきます!

の前に簡単に補足を入れておきます。

賢くなった気になれる補足: スコープとは何か

スコープは「今どこでコードを実行しているか」という文脈のことです。

Rubyではこの文脈によって、次の2つが変わります。

  • selfが誰になるか
  • 変数や定数をどう解決するか

今回の記事でまず押さえるべきスコープは次の3つです。

  • トップレベル: selfmain
  • クラス定義の中: selfはそのクラス
  • インスタンスメソッドの中: selfはそのインスタンス

クラス定義の中のselfは2つ目のスコープの話です。

selfは書く場所で指す相手が変わる

selfは固定の何かを指すキーワードではありません。

「今そのコードを受け取っているオブジェクト」を指します。

次のコードを見てください。

class User
  puts "class body self=#{self}"

  # インスタンスメソッド 
  def hello
    puts "instance method self=#{self}"
  end

  # クラスメソッド
  def self.describe
    puts "class method self=#{self}"
  end
end

User.describe
User.new.hello

# 出力:
# class body self=User
# class method self=User
# instance method self=#<User:0x...>

このコードで押さえておきたいことは3つです。

  • クラス本体のselfは、そのクラス自身
  • クラスメソッドの中のselfも、そのクラスオブジェクト自身
  • インスタンスメソッドの中のselfは、そのメソッドを受け取ったインスタンス

つまりselfは「場所によって変わる」のではなく、「今の受け手に合わせて決まる」と理解した方がズレません!

いきなりインスタンスメソッドクラスメソッドというのが出てきて混乱するかもしれませんが、 今は以下のように覚えておいてください!

  • インスタンスメソッド: def helloのように定義し、User.new.helloのようにインスタンスへ送るメソッド
  • クラスメソッド: def self.describeのように定義し、User.describeのようにクラスへ送るメソッド

コードで確認しよう!

Ruby公式ではクラス定義の中のselfは、その時点のスコープを表すオブジェクトだと説明されています。

class Demo ... endの中ではそのselfDemoです。

以下のコードで確認してみましょう。

class Demo
  puts "inside class body, self=#{self}"

  TRACE = []
  VALUE = 1 + 2
  TRACE << "1) VALUE defined"

  def self.answer
    TRACE << "3) Demo.answer called"
    VALUE
  end

  TRACE << "2) class body evaluated"
end

p Demo::TRACE
p Demo.answer
p Demo::TRACE

# inside class body, self=Demo
# ["1) VALUE defined", "2) class body evaluated"]
# 3
# ["1) VALUE defined", "2) class body evaluated", "3) Demo.answer called"]

class Demo ... endの中では、

  • selfDemo
  • VALUE = 1 + 2Demo の定数定義
  • TRACE << ... のような通常コードも、その場で実行される
  • def self.answer はクラスメソッドを定義するだけで、メソッド本体は呼ばれるまで実行されない

が行われています。

ここで押さえたいのは、「クラス定義を読んでいる」のではなく「クラス定義を実行している」という感覚です。

クラス定義は「実行して組み立てる -> 待機 ->new」で読む

クラスまわりの時間軸を3段で分けて考えると理解しやすいです。

  1. ロード(実行)タイム: ファイル読み込み時にclass ... endの中身が上から順番に実行される
  2. 待機状態: クラスオブジェクトができあがり、いつでもnewできる状態になる
  3. インスタンス化タイム: MyClass.newが呼ばれた瞬間に個別オブジェクトが生成される

ここで言う「待機状態」は仕様用語ではありません。

理解しやすくするための説明用ラベルです。

実際にはロード時点でクラスオブジェクトはすでに存在していて、newを呼ばれるのを待っている状態です。

もう少し具体化すると、

  • class MyClass ... end が評価された時点で、MyClassというClassオブジェクトがプロセスのメモリ上に存在する
  • そのクラスオブジェクトには定義したインスタンスメソッドやinclude情報、定数などが保持される
  • MyClass.new は毎回そのクラスオブジェクトに対してnewを呼び、そこから個別のインスタンスを作る

つまり「待機」は、「クラス定義の結果がメモリ上に保持されていて、あとから何度でもnewで使い回せる状態」という意味です。

次のコードを見るとnewで作ったインスタンスが同じクラスオブジェクトを参照していることが確認できます。

class MyClass
end

p MyClass.class
p MyClass.object_id

a = MyClass.new
b = MyClass.new

p a.class.object_id
p b.class.object_id
p a.class.object_id == MyClass.object_id
p b.class.object_id == MyClass.object_id


# Class
# 123456
# 123456
# 123456
# true
# true

object_id の具体的な数値は実行ごとに変わりますが、比較結果が true になる点が重要です。

次のコードを動かすと、この流れが確認できます。

module MyConcern
  def concern_loaded?
    true
  end
end

class MyClass
  puts "1) class body start self=#{self}"

  include MyConcern
  puts "2) include done"

  def self.say_hello
    puts "3) class method defined"
  end

  say_hello
end

puts "4) class definition finished"
obj = MyClass.new
puts "5) instance created class=#{obj.class} concern_loaded?=#{obj.concern_loaded?}"


# 1) class body start self=MyClass
# 2) include done
# 3) class method defined
# 4) class definition finished
# 5) instance created class=MyClass concern_loaded?=true

この結果から分かることは次の3つです。

  • クラス本体はファイル読み込み時に実行される
  • includedef self.xxxは、その場でクラスへ反映される
  • newはクラス定義完了後に呼ばれ、そこで初めてインスタンスが作られる

なぜself.を付けないといけない場面があるのか

初学者が一番詰まりやすいのはsetterの部分かと思います。

class User
  attr_accessor :name

  def rename_without_self
    name = "tom"
  end

  def rename_with_self
    self.name = "tom"
  end
end

user = User.new
user.rename_without_self
p user.name

user.rename_with_self
p user.name

# nil
# "tom"

name = "tom"の部分は、nameというsetterメソッド呼び出しではありません。 Rubyはこれを「ローカル変数nameへの代入」と解釈します。

一方でself.name = "tom"と書くと、name=というメソッド呼び出しとして解釈されます。

「え、レシーバが省略されているときは、暗黙的にselfがレシーバになるならname = "tom"は 暗黙的にself.name = "tom"になるのでは?」 と思ったのならあなたは成長しています!!

実はここ、仕様としてかなり重要な部分でして、
name = "tom" はRubyの文法上、常にローカル変数代入として解釈されるんです!

暗黙的にselfが効くのは「通常のメソッド呼び出し」のときで、代入式にはそのまま適用されないというわけです。

これらを踏まえると

  • xxx = 1は、先にローカル変数代入として解釈される
  • self.xxx = 1は、xxx=メソッド呼び出しになる

となるため、「今のインスタンスに対してsetterを呼びたい」ときはself.が必要になるというわけです。

self.を付けると「レシーバを明示する」ことになる

self.を付ける意味は、単に見やすくすることではありません。

「このメソッドの受け手は今のselfです」と明示することです。

たとえば次のコードを見てください。

class User
  def label
    private_name
  end

  def label_with_self
    self.private_name
  end

  def label_with_copy
    copy = self
    copy.private_name
  end

  private

  def private_name
    "tom"
  end
end

user = User.new
p user.label
p user.label_with_self
begin
  p user.label_with_copy
rescue NoMethodError => e
  p e.class
end

# "tom"
# "tom"
# NoMethodError

この例で分かることは次の3つです。

  • private_nameは、レシーバを書かずに呼べる
  • self.private_nameのように、selfをそのままレシーバにした呼び出しも許可される
  • copy = selfのあとにcopy.private_nameと書くとNoMethodErrorになる

中身が同じオブジェクトでも、copyはローカル変数をレシーバにした呼び出しなので、self.private_nameとは別扱いになります。 なので処理が失敗します。

次のルールを覚えておくと混乱しにくいです。

  • setterを呼びたいときはself.が必要
  • 通常のprivateメソッドは、まずレシーバなしで読む
  • self.を付けたときは「受け手を明示した呼び出し」だと理解する

defもその場でメソッドを追加している

次のコードを見てください。

class User
  puts "before def: #{instance_methods(false).inspect}"

  def hello
    "hi"
  end

  puts "after def: #{instance_methods(false).inspect}"
end

# before def: []
# after def: [:hello]

def helloが評価された時点で、Userクラスにインスタンスメソッドhelloが追加されています。

「あとでまとめて反映される」のではなく、その場でUserに追加されます。

クラスはあとから再オープンできる

Rubyでは同じクラスに対してもう一度class User ... endと書き、あとからメソッドを追加できます。 これをクラスの再オープンといいます。

class User
  def name
    "tom"
  end
end

class User
  def greet
    "hello #{name}"
  end
end

puts User.new.greet

この性質があるため、1つのクラスの機能が複数の場所に分かれて書かれることがあります。

Rubyのコードで「このクラス定義はここで終わり」と決めつけにくい理由の1つです。

またクラスの再オープンのデメリットとして以下の3点があります

  • 挙動の追跡が困難になる(定義が分散する)
  • 意図しない上書き(メソッド衝突)が発生する
  • 外部ライブラリとの干渉リスクが高い(※モンキーパッチ問題)

あんまり進んで使うものではないということですね。

※ モンキーパッチ 既存のソースコードを直接修正することなく、プログラムの実行時(ランタイム)にクラスやメソッド、ライブラリの動作を動的に拡張・変更するテクニック。

一次情報: Ruby公式:modules_and_classes(クラスの再オープン)

3.User.newの内部で何が起きるのか

クラスをインスタンス化するときに.newを使いますが、
この.newの内部では何が起こっているのか気になりませんか?

Ruby公式のClassドキュメントをみてみると

Calls allocate to create a new object of class’s class, then invokes that object’s initialize method, passing it args. This is the method that ends up getting called whenever an object is constructed using .new.

とあります。

.newは内部でallocateを呼び、そのあとinitializeを呼ぶと説明されています。

allocateは自身のインスタンスを生成して返すメソッドです。

生成したオブジェクトは自身のインスタンスであること以外には何も特性を持たないのが特徴です。

つまりは、initializeをスキップしてインスタンスを生成したい場面で使えるメソッドというわけです。

このallocateはRailsのActiveRecordで使われており、DBから取得したデータをオブジェクトに復元する際、 initializeを通すと「新規生成」と同じ処理(バリデーション等)が走ってしまうので、 initializeをスキップさせるためにallocateが使われていたりもします。

また、クラスに定義されたメソッドの呼び出しがinitializeに依存している場合、 allocateでインスタンス化したオブジェクトだとメソッドは呼べるが、初期化されていないのでメソッドが壊れてエラーになります。

「オブジェクト生成」と「初期化」を分離させたい時に使うメソッドですが、 メタプログラミング等で使うことが多いメソッドでもあると思うので、ここでは一旦忘れても問題ありません!

ではコードで確認してみましょう!

class Box
  attr_reader :value

  def initialize(value)
    @value = value
  end
end

# `Box`クラスの初期化前インスタンスを作成
raw = Box.allocate
p raw.class
p raw.class == Box
# オブジェクトのインスタンス変数名をシンボルの配列として返すメソッド
p raw.instance_variables


# `Box` クラスのインスタンスを作成し、`initialize`も実行
box = Box.new(10)
p box.class
p box.instance_variables
p box.value


# Box
# true
# []

# Box
# [:@value]
# 10

この差を見ると、それぞれの役割が分かりやすいです。

  • allocate: 空のオブジェクトだけを用意する
  • initialize: そのオブジェクトへ初期状態を入れる
  • new: 上の2つを順に実行する入口

ということがわかります。

raw.class == Boxtrueになっている点から、allocate直後のrawは「Boxのインスタンスで、まだ初期化前の状態」だと読めます。

newを「魔法の命令」ではなく「生成の流れをまとめたメソッド」と見ると、initializeの役割も追いやすくなりますね。

賢くなった気になれる補足: initializeはコンストラクタっぽいが、完全に同じではない

TypeScriptやJavaの感覚だとinitializeを「コンストラクタ」と呼びたくなりますが、 Rubyに「コンストラクタ」という専用構文はなく、initializeがコンストラクタの役割を担っているという感じです。

また、Rubyでインスタンス生成の入口になっているのはClass#newです。

整理するとこうです。

  • 生成の入口:User.new
  • オブジェクト確保:allocate
  • 初期化:initialize

そのため、「Rubyではinitializeがインスタンスを作る」のではなく、 「newが作った(正確にはallocateが作った)インスタンスをinitializeが初期化する」というのが正しいです。

4. クラスもオブジェクトである

クラスもオブジェクトであるという点を押さえると、クラスメソッドの見え方がかなり変わります。

class User
end

p User.class

# Class

つまりUserそのものも、Classクラスのインスタンスである1つのオブジェクトです。

この理解を入れると、クラスメソッドも追いやすくなります。

class User
  def self.table_name
    "users"
  end
end

p User.table_name

# "users"

これは「Userというクラスオブジェクトにtable_nameというメソッドを定義している」と見ると整理しやすいです。

クラス本体ではselfUserなので、def self.table_nameUserに対するメソッド定義になります。

5. クラスメソッドは「特異クラス」という専用の場所に入る

Rubyではオブジェクト固有のメソッドを保持する仕組みとして、singleton class(特異クラス、メタクラス、eigenclass)があります。

少し難しい言葉ですが、ここでは「そのオブジェクト専用のメソッド置き場」と考えれば十分です。

class User
  class << self
    def table_name
      "users"
    end
  end
end

p User.table_name

# "users"

class << selfは、「今のselfの特異クラスを開く」という意味です。 クラス本体ではselfUserなので、class << selfUserの特異クラスを開いていることになります。

この中でdefを書くと、Userオブジェクト専用メソッドが定義されます。 それが普段呼んでいるクラスメソッドです。

流れで書くと次の3ステップです。

  • クラス本体でのselfUser
  • class << selfUserの特異クラスへ入る
  • その中で定義したdef table_nameUser.table_nameとして呼べる

つまり、普段「クラスメソッド」と呼んでいるものは、 内部的には「クラスオブジェクト専用のメソッド置き場に入っているメソッド」と考えると整理しやすいかと思います!

因みにですが、def self.table_nameclass << selfは、書き方が違うだけで同じ方向を向いています。

比較用に、2つの書き方を並べると次のようになります。

class UserByDefSelf
  def self.table_name
    "users"
  end
end

class UserBySingletonClass
  class << self
    def table_name
      "users"
    end
  end
end

p UserByDefSelf.table_name
p UserBySingletonClass.table_name

# "users"
# "users"

一次情報: Ruby公式:modules_and_classes(singleton class)

賢くなった気になれる補足: Rubyにおけるsingleton classとは

singleton classは、特定の1オブジェクトにだけひもづくメソッド定義領域です。

  • 通常のクラス本体: そのクラスから作るインスタンス全体で共有されるメソッド
  • singleton class: その1オブジェクトだけが持つメソッド

本当に「シングル」なのかを確認してみましょう!

user1 = "tom"
user2 = "bob"

def user1.nickname
  upcase
end

p user1.nickname

begin
  p user2.nickname
rescue NoMethodError => e
  p e.class
end

p user1.singleton_class == user1.singleton_class
p user1.singleton_class == user2.singleton_class
p user1.singleton_class.instance_methods(false).include?(:nickname)

# "TOM"
# NoMethodError
# true
# false
# true

この結果から分かることは次の3つです。

  • nicknameuser1にだけ生えていて、user2には生えない
  • 同じオブジェクトのsingleton_classは毎回同じ(true
  • 別オブジェクトのsingleton_classは別物(false

本当に一つしかないオブジェクトですね。

クラスメソッドとのつながり

クラスメソッドは、「クラスオブジェクト(例: User)のsingleton classに定義されたメソッド」として実装されています。

class User
end

class << User
  def table_name
    "users"
  end
end

p User.table_name
p User.respond_to?(:table_name)
p User.new.respond_to?(:table_name)
p User.singleton_class.instance_methods(false).include?(:table_name)

# "users"
# true
# false
# true

Userにはtable_nameが生えますが、User.new(インスタンス化したオブジェクト)には生えません。

これは、table_nameUserインスタンス用ではなく、Userオブジェクト自身のsingleton classに定義されているためです。

賢くなった気になれる補足: 「クラスのクラス」はどうなっているのか

User.classClassなら、「Class.classは一体何なんだ!」と思うかもしれませんが、これもClassです。 なんかややこしいですね。

この自己参照みたいな構造は最初は不思議に見えますが、Rubyでは

  • インスタンスはクラスから作られる
  • クラス自身もClassのインスタンスである

というオブジェクトモデルを採っています。

このため、「クラスをただの静的な箱」と見ると混乱します。

「クラスもメソッドを受け取れるオブジェクト」として見ると、ここまでの説明がつながって見えるかと思います。

まとめ

前編の要点は次の6つです。

  • Rubyのclass本体は、読むだけの宣言ブロックではなく、その場で実行される
  • クラス定義は「実行して組み立てる -> 待機 ->newでインスタンス化」の流れで読むと追いやすい
  • クラス本体のselfはクラス自身を指すため、def self.xxxはクラスオブジェクトへのメソッド追加になる
  • User.newallocateinitializeを順に呼ぶ入口
  • クラスメソッドはクラスオブジェクトの特異メソッドとして捉えると追いやすい
  • クラスメソッドの探索順はsingleton_class.ancestorsで確認できる

参考資料

テコテックの採用活動について

テコテックでは新卒・中途採用を積極的に行っています。 採用サイトでは会社の雰囲気や福利厚生、募集ポジションをご確認いただけます。 ご興味をお持ちいただけましたら、ぜひチェックしてみてください。 tecotec.co.jp