はじめに
システム開発第一事業部の奥田です。普段はフルスタックエンジニアとして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の基本、クラス本体のself、initialize、クラスメソッドの見方までを整理。 - 第2回(予定): 継承、
include/extend/prepend、メソッド探索順、定数スコープ、可視性を扱う。 - 第3回(予定): その知識を使いながらRailsの
has_many、scope、コールバック、delegate、ActiveSupport::Concernと追い方を整理する。
途中で変わったりもするかもですのでご了承ください。
- はじめに
- まずはこの記事のゴールを確認
- 検証環境について
- 結論
- 1. クラスとインスタンス、getter/setterとアクセサーの基礎
- 賢くなった気になれる補足: Rubyのclassは設計図だけではない
- 賢くなった気になれる補足:name=みたいなメソッド名はRubyでは普通
- 2. クラス定義の中身は上から順番に実行される
- 3.User.newの内部で何が起きるのか
- 賢くなった気になれる補足: initializeはコンストラクタっぽいが、完全に同じではない
- 4. クラスもオブジェクトである
- 5. クラスメソッドは「特異クラス」という専用の場所に入る
- 賢くなった気になれる補足: 「クラスのクラス」はどうなっているのか
- まとめ
- 参考資料
- テコテックの採用活動について
まずはこの記事のゴールを確認
ゴールがないと行き先を失うので迷える子羊状態です。
しっかりとゴールを意識していきましょう。
class定義本体が「宣言」ではなく「実行されるコード」であると説明できるselfが文脈ごとに誰を指すかを説明できるattr_accessorなどのアクセサーとself.name = ...の関係を説明できるdef self.xxxとclass << selfが何をしているかを説明できる
検証環境について
- 実行確認: Ruby 3.3.11
- 仕様確認: Ruby公式ドキュメント 3.3系
- Rails関連の説明確認: Ruby on Rails公式ドキュメント 8.1系
結論
忙しい人向けに記事の内容をまとめたものを置いておきます!
- Rubyの
classは「設計図を書く場所」であると同時に、「その場で実行されるコードブロック」でもある - クラス定義の中で書いた
defや通常のメソッド呼び出しはその場で評価される Xxx.newは内部でallocateとinitializeを順に呼び、生成されたオブジェクトへ初期値を入れる- Rubyではクラス自体もオブジェクトなので、クラスメソッドは「クラスオブジェクト専用のメソッド」として見ると整理しやすい
1. クラスとインスタンス、getter/setterとアクセサーの基礎
まずは次の3つを理解していきましょう。
classはオブジェクトの種類を定義するnewはそのクラスから実際のオブジェクトを作るinitializeは作られた直後のオブジェクトへ初期値を入れる
そのうえで、クラスを読んでいると早い段階でgetterとsetterが出てきます。
- 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クラスにnameもname=も定義していないので、どちらの呼び出しでもNoMethodErrorになります。
つまりこのrescueは、失敗することを確認するためにエラー内容を受け取って表示している、ということです。
@nameに値は入っていますがnameもname=も定義していないので、外からは読めませんし、書き込めません。
ここで必要になるのが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が gettername=が setter
です。
つまりRubyでは、
@name: インスタンス変数そのものname:@nameを読み込むgettername=:@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つです。
class UserでUserというクラスを定義するUser.new("Tom")でUserクラスのインスタンスを作るinitializeが呼ばれて@nameに"Tom"が入るuser.nameを呼ぶと、attr_reader :nameが作ったnameメソッドで値を読む
ここまでをまとめると、アクセサー系の理解は次の形で整理できます。
@nameはインスタンス変数そのものnameは@nameを読む gettername=は@nameへ値を入れる setterattr_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_methodとinstance_variable_get/instance_variable_setの役割
dammy_attr_accessorの中で使っている3つは次の役割を持ちます。
define_method(:name) { ... }: メソッドを文字どおり「定義する」。この例ではnameやname=をその場でクラスへ追加する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. クラス定義の中身は上から順番に実行されるで具体例を使って確認してます。
この前提を持っておくと、クラス定義中のincludeやattr_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つです。
- トップレベル:
selfはmain - クラス定義の中:
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の中ではそのselfがDemoです。
以下のコードで確認してみましょう。
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の中では、
selfはDemoVALUE = 1 + 2はDemoの定数定義TRACE << ...のような通常コードも、その場で実行されるdef self.answerはクラスメソッドを定義するだけで、メソッド本体は呼ばれるまで実行されない
が行われています。
ここで押さえたいのは、「クラス定義を読んでいる」のではなく「クラス定義を実行している」という感覚です。
クラス定義は「実行して組み立てる -> 待機 ->new」で読む
クラスまわりの時間軸を3段で分けて考えると理解しやすいです。
- ロード(実行)タイム: ファイル読み込み時に
class ... endの中身が上から順番に実行される - 待機状態: クラスオブジェクトができあがり、いつでも
newできる状態になる - インスタンス化タイム:
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つです。
- クラス本体はファイル読み込み時に実行される
includeやdef 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 == Boxがtrueになっている点から、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というメソッドを定義している」と見ると整理しやすいです。
クラス本体ではselfがUserなので、def self.table_nameはUserに対するメソッド定義になります。
5. クラスメソッドは「特異クラス」という専用の場所に入る
Rubyではオブジェクト固有のメソッドを保持する仕組みとして、singleton class(特異クラス、メタクラス、eigenclass)があります。
少し難しい言葉ですが、ここでは「そのオブジェクト専用のメソッド置き場」と考えれば十分です。
class User class << self def table_name "users" end end end p User.table_name # "users"
class << selfは、「今のselfの特異クラスを開く」という意味です。
クラス本体ではselfがUserなので、class << selfはUserの特異クラスを開いていることになります。
この中でdefを書くと、Userオブジェクト専用メソッドが定義されます。
それが普段呼んでいるクラスメソッドです。
流れで書くと次の3ステップです。
- クラス本体での
selfはUser class << selfでUserの特異クラスへ入る- その中で定義した
def table_nameがUser.table_nameとして呼べる
つまり、普段「クラスメソッド」と呼んでいるものは、 内部的には「クラスオブジェクト専用のメソッド置き場に入っているメソッド」と考えると整理しやすいかと思います!
因みにですが、def self.table_nameとclass << 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つです。
nicknameはuser1にだけ生えていて、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_nameがUserインスタンス用ではなく、Userオブジェクト自身のsingleton classに定義されているためです。
賢くなった気になれる補足: 「クラスのクラス」はどうなっているのか
User.classがClassなら、「Class.classは一体何なんだ!」と思うかもしれませんが、これもClassです。
なんかややこしいですね。
この自己参照みたいな構造は最初は不思議に見えますが、Rubyでは
- インスタンスはクラスから作られる
- クラス自身も
Classのインスタンスである
というオブジェクトモデルを採っています。
このため、「クラスをただの静的な箱」と見ると混乱します。
「クラスもメソッドを受け取れるオブジェクト」として見ると、ここまでの説明がつながって見えるかと思います。
まとめ
前編の要点は次の6つです。
- Rubyの
class本体は、読むだけの宣言ブロックではなく、その場で実行される - クラス定義は「実行して組み立てる -> 待機 ->
newでインスタンス化」の流れで読むと追いやすい - クラス本体の
selfはクラス自身を指すため、def self.xxxはクラスオブジェクトへのメソッド追加になる User.newはallocateとinitializeを順に呼ぶ入口- クラスメソッドはクラスオブジェクトの特異メソッドとして捉えると追いやすい
- クラスメソッドの探索順は
singleton_class.ancestorsで確認できる
参考資料
テコテックの採用活動について
テコテックでは新卒・中途採用を積極的に行っています。 採用サイトでは会社の雰囲気や福利厚生、募集ポジションをご確認いただけます。 ご興味をお持ちいただけましたら、ぜひチェックしてみてください。 tecotec.co.jp