Ruby
目次
- 概要
- 1. Rubyとは何か
- 2. 実行環境と動作の仕組み
- 3. 変数・データ型
- 4. 演算子と式
- 5. 制御フロー
- 6. メソッド
- 7. ブロック・Proc・Lambda
- 8. オブジェクトとクラス
- 9. モジュールとミックスイン
- 10. メタプログラミング
- 11. コレクション操作
- 12. 文字列・正規表現
- 13. 例外処理
- 14. 並行・並列処理(Thread / Fiber / Ractor)
- 15. 型とRBS / Sorbet / Steep
- 16. パッケージング・依存管理(gem/Bundler)
- 17. テスト戦略
- 18. RailsとActiveSupportの世界
- 19. パフォーマンス
- 20-A. パフォーマンス最適化の実践
- 20-B. Ruby での型安全性と Typecheck ツール
- 20-C. Ruby での並行・並列の詳細
- 20-D. メタプログラミングの実践パターン
- 20. Ruby 3.0〜3.4の新機能
- 21. よくある落とし穴FAQ
- 22. 図解: オブジェクトモデルとメソッド探索
- 23. 学習ロードマップ(30日)
- 24. 用語集
- まとめ
- 参考文献
概要
まず、この章の中心構造を図で確認します。細部に入る前に、どの概念がどこへつながるかをつかむための地図です。
コード例は、そのまま写すためだけのものではありません。直前の本文で「何を確かめる例か」を押さえ、直後の説明で「どの性質が見えるか」を確認してください。実務では、ここに入力の境界、失敗時の挙動、依存する実行環境を足して読むと判断しやすくなります。
Rubyは、オブジェクト指向と柔軟な構文を通じて、書き手の意図を自然に表現することを重視する言語です。
このページでは、オブジェクトモデル、ブロック、メタプログラミング、例外、Gem、Railsとの関係、近年の型・JIT周辺を整理します。
1. Rubyとは何か
このセクションでは「Rubyはなぜ生まれたのか」「なぜ『プログラマの幸福』を最重視するのか」「他の動的言語と何が違うのか」を解説します。Rubyを理解するうえで、そのカルチャーを抑えるとあとが圧倒的に楽になります。
Rubyは、**「動的型付け・純粋オブジェクト指向・マルチパラダイム」**のプログラミング言語です。1995年にまつもとゆきひろ(Matz)によって公開され、現在は次のような領域で広く使われています。
- Webアプリケーション: Ruby on Rails、Sinatra、Hanami
- インフラ自動化: Chef、Puppet、Vagrant、Fastlane
- DSL構築: Rakefile、Bundler、RSpec、自前DSL
- スクリプト・自動化: テキスト処理、業務自動化
- 静的サイト生成: Jekyll、Middleman
1-1. Rubyの歴史(誕生からRuby 3.4まで)
「日本生まれの言語」
1993年2月24日、まつもとゆきひろは新しい言語の構想をはじめました。彼は当時、PerlとPythonに親しんでおり、両者の良いところを取りつつ「完全に純粋なオブジェクト指向」「プログラマが書いて楽しい」言語を作りたいと考えていました。
「私がRubyを作ったのは、自分が楽しくプログラミングしたかったから」(Matz)
名前は同僚の石塚圭樹が提案した「Ruby」(紅玉)が採用されました。Perl(パール、真珠)に対抗する宝石名というユーモアもあったといいます。
Ruby 1.0公開(1996)
1996年12月、Ruby 1.0が日本国内向けに公開されました。当初は日本のローカルコミュニティを中心に使われ、英語のドキュメントが整備されるまで世界的には知られていませんでした。
世界進出(2000〜2004)
2000年、David ThomasとAndrew Huntによる『Programming Ruby』(通称「ピッケル本」)が出版され、英語圏でも一気にRubyの認知度が上がります。これが世界進出の第一歩でした。
1995 Ruby 0.95(日本のニュースグループに公開)
1996 Ruby 1.0
2000 Programming Ruby(ピッケル本)出版
2004 Ruby on Rails公開(DHH)
2007 Ruby 1.9開発開始(YARV採用)
2013 Ruby 2.0
2020 Ruby 3.0(3x3達成、Ractor、パターンマッチング)
2021 Ruby 3.1(YJIT実験的搭載)
2022 Ruby 3.2(YJIT量産レベル、WASM対応)
2023 Ruby 3.3(YJIT大幅高速化、Prismパーサ統合)
2024 Ruby 3.4(Prismがデフォルトパーサ、Hash#exceptなど)
Railsの衝撃(2004)
2004年、デンマークのデイヴィッド・ハイネマイヤー・ハンソン(DHH)が Ruby on Rails を公開します。Railsは「Convention over Configuration(設定より規約)」「Don’t Repeat Yourself(繰り返しを避けよ)」というスローガンで、Webアプリ開発を圧倒的に効率化しました。
Railsの成功によりRuby自体の人気も爆発的に上がり、Ruby = Web開発言語というイメージが広く定着します。Twitter、GitHub、Shopify、Airbnb、Stripe、Cookpadなど、多くの著名サービスがRailsで作られました。
YARVとRuby 1.9(2007)
長らくRubyは「遅い」と言われていました。Ruby 1.8までのインタプリタはASTを木構造のまま辿る方式で、性能に限界があったためです。
2007年、ささだこういちが開発した YARV(Yet Another Ruby VM) がRuby 1.9に統合されます。ASTをバイトコードにコンパイルしてVMで実行する方式で、Rubyは大幅に高速化しました。これが現代Rubyの実行モデルの基礎です。
Ruby 3x3とRuby 3.0(2020)
2015年、Matzは 「Ruby 3ではRuby 2の3倍速くする(Ruby 3x3)」 という目標を掲げます。これは単純な高速化ではなく、JIT・並列処理・型情報 という3本柱の総合的な進化を目指すものでした。
1. JITコンパイル(MJIT → 後のYJIT)
2. Ractor(GVLを超えた並列処理)
3. RBS(型情報フォーマット)
Ruby 3.0(2020年12月25日)は実際にベンチマークによっては3倍を超える速度を達成し、目標を有言実行しました。同時に パターンマッチング(case/in)、型情報フォーマットRBS、Ractor といった大型機能が追加されました。
モダンRuby(2022〜)
3.0 (2020) Ractor、パターンマッチング、RBS、エンドレスメソッド
3.1 (2021) YJIT実験的搭載、Hash短縮記法({ x:, y: })、debug.gem
3.2 (2022) YJIT量産化、WASI対応(ブラウザで動くRuby)、Dataクラス
3.3 (2023) YJIT大幅高速化、Prismパーサ統合、M:Nスレッドスケジューラ(実験的)
3.4 (2024) Prismデフォルト化、itブロック引数、Hash#exceptなど改善
直近では特に YJIT が業界の注目を集めています。Shopifyが中心となって開発するJITコンパイラで、Railsアプリで体感できる速度向上を実現しました。
1-2. 設計哲学(プログラマの幸福、最小驚き原則)
Rubyのすべての設計判断の中心にあるのは、**「プログラマが幸せになるかどうか(programmer happiness)」**です。Matzは何度もこう述べています。
「Rubyは、人間がコンピュータに合わせるのではなく、コンピュータが人間に合わせる言語であるべきだ」
Principle of Least Surprise(最小驚き原則)
「コードを読んだとき、書き手の意図が直感的に読み取れる」という設計原則です。たとえば次のような書き方が許されているのも、この哲学から来ています。
3.times { puts "hello" } # 整数にもtimesメソッドが生える
[1, 2, 3].sum # 配列にsum
"hello".reverse # 文字列にreverse
nil.to_s # nilにもメソッドがある(""を返す)
「3という数値に times メソッドが生えているのは自然か?」と問われれば多くのプログラマは違和感を覚えますが、Rubyは 「3回繰り返したい」という意図がそのままコードになることを優先しました。
設計判断の例
| Rubyの判断 | 哲学 |
|---|---|
| 整数もnilもすべてオブジェクト | 一貫性、特殊ケースを作らない |
制御構文も式(if が値を返す) |
表現力 |
unless, until のような否定形構文 |
自然な英語に近づける |
修飾子(後置 if) |
短く読みやすく書ける |
| ブロック・Procが第一級 | 関数型のエッセンスを楽に書ける |
| オープンクラス | 既存クラスを拡張する自由 |
これらは「プログラマが書きたいように書ける選択肢を増やす」という思想で、選択肢の多さが時に 「Rubyは方言が多い」 と批判される元にもなります。Pythonの「There should be one obvious way」とは対照的な哲学です。
1-3. なぜ動的型付けか/RBSの登場
Rubyは 動的型付けです。変数や引数に型を書く必要がなく、すべての型情報は実行時に決まります。
x = 42
x = "hello" # 型が変わってもOK
x = [1, 2, 3]
なぜ動的型付けか
Matzは「プログラマが楽に書けることが優先」という理由で動的型付けを選びました。Perl・Pythonの系譜にあり、当時のスクリプト言語文化を引き継いでいます。
加えて、Rubyの メタプログラミング(実行時にメソッドを動的に定義したり、method_missing で呼び出しを横取りしたり)は、動的型付けあってこそ自然に書ける機能です。Railsのような魔法のDSLは、動的型付けの自由度なしには成り立ちません。
RBS(Ruby 3.0+)の登場
ただし、コードベースが大きくなると「型情報がないと辛い」という現実が訪れます。これに応えるため、Ruby 3.0で公式の RBS(Ruby Signature) が導入されました。
# user.rb
class User
def initialize(name, age)
@name = name
@age = age
end
def adult?
@age >= 20
end
end
# sig/user.rbs
class User
def initialize: (String name, Integer age) -> void
def adult?: () -> bool
end
特徴は 「ソースコードと型情報を別ファイルに分ける」 こと。Pythonの型ヒントは .py 内に書きますが、Rubyは .rbs という別ファイルに書くアプローチを採用しました。これにより、既存のRubyコードを一切変更せず、外部から型情報を被せられます。
詳細は第15章で扱います。
1-4. Rubyのマルチパラダイム性
Rubyは「純粋オブジェクト指向」を中核としつつ、手続き型・関数型・メタプログラミングの要素も自然に書けるマルチパラダイム言語です。
純粋オブジェクト指向
「すべてはオブジェクト」が徹底されています。
1.class # Integer
nil.class # NilClass
true.class # TrueClass
1.method(:+).call(2) # 3(演算子もメソッド呼び出し)
整数・nil・true・falseを含めて、例外なくすべてがオブジェクト。JavaやPythonの「プリミティブ型vs参照型」のような区別はありません。
関数型のエッセンス
ブロック・Proc・Lambdaを駆使すると、関数型に近い書き方ができます。
[1, 2, 3, 4]
.select { |x| x.even? }
.map { |x| x * x }
.reduce(:+)
# 20
メソッドチェーンと無名関数(ブロック)の組み合わせは、HaskellやScalaのパイプライン的な記述に通じます。
メタプログラミング
Rubyのメタプログラミング能力は最強クラス。第10章で詳述しますが、method_missing、define_method、instance_eval、特異クラスといった仕組みで、実行時にクラスやメソッドを自在に操作できます。
これがRailsの has_many :books のようなDSLを可能にし、テストフレームワークRSpecの it { expect(...).to eq ... } のような英語風の構文を実現しています。
1-5. このセクションのまとめ
Rubyの出自:
1993年からまつもとゆきひろ(Matz)が設計
1996年Ruby 1.0、2000年に英語圏進出
2004年Rails登場で爆発的普及
2020年Ruby 3.0(Ractor、RBS、パターンマッチング)
哲学:
「プログラマの幸福」が最優先
Principle of Least Surprise(最小驚き原則)
Pythonの「one way」と対照的に「複数の表現を許す」
型システム:
動的型付け
RBS(3.0+)でソースとは別ファイルに型情報
Steep / Sorbet / TypeProfで静的解析
パラダイム:
純粋オブジェクト指向(数値もnilもオブジェクト)
関数型のエッセンス(ブロック・Proc・Lambda)
最強クラスのメタプログラミング能力
次のセクションでは、Rubyのコードが内部でどう動くのか――YARV、YJIT、GVL、Ractor、GCを解説します。
2. 実行環境と動作の仕組み
このセクションでは「ruby script.rb を実行したとき、内部で何が起こっているか」を解説します。PythonのGILに当たるRubyの GVL、その制約を超える Ractor、最近話題の YJIT、Prism パーサといった重要トピックを抑えます。
2-1. CRuby(MRI)・JRuby・TruffleRuby
「Ruby」と言ってもいくつか実装があります。
Ruby(言語仕様)
└── CRuby (MRI) 公式実装。C言語で書かれている。世界の99%はこれ
└── JRuby JVM上で動くRuby。Javaライブラリと統合可能、真の並列
└── TruffleRuby Oracle GraalVM上のRuby。極めて高速なJITを持つ
└── mruby組み込み向けの軽量実装(Matz自身が主導)
└── Artichoke Rust製の実験的実装
CRuby(MRI)
普段「Ruby」と呼んでいるのはこれ。MRI(Matz’s Ruby Implementation) とも呼ばれます。LinuxディストリビューションやHomebrewで入る ruby コマンドはCRubyです。
JRuby
Java仮想マシン(JVM)上で動くRuby。CRubyとの違いは、
- 真のマルチスレッド並列処理が可能(GVLがない)
- Javaライブラリを直接呼べる
- 起動が遅い(JVMの起動コストがある)
- C拡張ライブラリの互換性に制約
TruffleRuby
Oracle LabsのGraalVM上で動くRuby。最先端のJIT技術を使い、ベンチマークによってはCRubyの数倍〜数十倍速い。ただし普及度は限られ、エコシステムの互換性に課題があります。
mruby
組み込み機器向けの軽量実装。Matz自身が主導しており、ゲーム機、ルータ、IoTデバイスに搭載されています。
2-2. YARVと命令列
CRubyは内部で YARV(Yet Another Ruby VM) という仮想マシン上でコードを実行します。Ruby 1.9以降の標準実装。
1. ソースコード(.rb)をPrismまたはparse.yでパース
2. AST(抽象構文木)を構築
3. ASTをYARVのバイトコード(命令列)にコンパイル
4. YARVがバイトコードを逐次実行
バイトコードを覗いてみる
RubyVM::InstructionSequence で確認できます。
code = "1 + 2"
puts RubyVM::InstructionSequence.compile(code).disasm
# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)>
# 0000 putobject 1
# 0002 putobject 2
# 0004 opt_plus
# 0006 leave
putobject で値をスタックに積み、opt_plus で足し、leave で関数を抜ける。スタックマシンとして動作しています。
Prismパーサ(Ruby 3.3+)
Ruby 3.3で Prism という新しいパーサが標準ライブラリに統合されました。3.4ではデフォルトになります。
従来の parse.y はBison製で長年動いてきましたが、メンテナンス性に問題がありました。Prismは手書きパーサで、
- エラー回復が強い: 構文エラーがあっても続けて解析できる(IDE向き)
- 複数言語にバインディング: Ruby以外(JavaScript、Rustなど)からも使える
- Ruby内部実装の標準化: CRuby・TruffleRuby・JRubyが同じパーサを共有
を実現しました。地味ながらRuby実装の大きな転換点です。
2-3. YJIT(3.1+)
長らくRubyは「遅い」と言われていました。YARVはASTより速いとはいえ、依然として動的言語のオーバーヘッドが大きかったためです。
YJITとは
YJIT(Yet Another Just-In-Time compiler) は、Shopifyが中心となって開発したCRuby用のJITコンパイラ。Ruby 3.1で実験的搭載、3.2で量産レベル、3.3で大幅高速化されました。
# YJITを有効にして実行
ruby --yjit script.rb
# 環境変数で有効化
RUBY_YJIT_ENABLE=1 ruby script.rb
# Railsでも
RUBY_YJIT_ENABLE=1 rails server
何が速くなるか
YJITは 「実行頻度の高いコードを機械語にコンパイル」 することで高速化を実現。
- Railsアプリ: 平均15〜20% の高速化(Shopify本番環境で実証済み)
- メソッド呼び出し中心のRubyコード: 倍速になる例も
- 数値計算ベンチマーク: 10倍以上速くなることも
仕組み
YJITは Lazy Basic Block Versioning(LBBV) という技術を採用。動的型付け言語の特徴を活かし、「実行時に観測した型情報をもとに、その型に特化した機械語を生成」する手法です。
def add(a, b)
a + b
end
# 整数で呼ばれた → 整数特化版を生成
add(1, 2)
add(3, 4)
# 文字列で呼ばれた → 文字列特化版を生成
add("hello", "world")
# 同じバイトコードに対して、型ごとに別の機械語が生成される
2-4. GVL(Global VM Lock)の正体
Rubyの話で必ず出てくる GVL(Global VM Lock) とは何かを解説します。PythonのGILとほぼ同じ機構です。
GVLの定義
GVLとは、「CRubyインタプリタ全体に1つだけ存在する大きなロックで、同時にバイトコードを実行できるスレッドは1つだけ」 という仕組みです。
スレッドA ─┐
スレッドB ─┼─ GVL(1つだけ)─→ 実行できるのは1スレッドだけ
スレッドC ─┘
これにより、CPUバウンドな純Rubyコードはマルチスレッドにしても並列に動かない。スレッドを4つ立ててもCPUコア数を活かせません(同時に動けるのは1つだから)。
# CPUバウンドな処理(GVLで並列化されない)
threads = 4.times.map do
Thread.new do
sum = 0
10_000_000.times { |i| sum += i }
sum
end
end
threads.each(&:join)
# シングルスレッドとほぼ同じ時間
なぜGVLがあるか
GVLの目的は 「内部状態の整合性を保つため」。CRubyのオブジェクト管理・参照カウント・GCは、複数スレッドが同時に動くと壊れます。GVLですべてのバイトコード実行を直列化するのが最も簡単な解決策でした。
GIL/GVLの利点と欠点もPythonと同じです。
- 利点: C拡張が書きやすい、シングルスレッド性能が高い
- 欠点: マルチコアを活かせない
GVLを解放する場面
GVLは常時保持されているわけではありません。次のような場面で自動的に解放されます。
- I/O待ち(ネットワーク、ファイル、
sleep) - C拡張内の特定操作(明示的に
rb_thread_call_without_gvlを呼んだとき)
つまり、I/Oバウンドな処理はRubyのThreadで並行化できる。「RubyではI/OはThread、CPUはRactorまたは プロセス」という現代的な使い分けの根拠です。
2-5. Ractor(3.0+)と真の並列
Ruby 3.0で導入された Ractor は、GVLの壁を超えた 真の並列実行を可能にする新しい並行プリミティブです。Pony言語やErlangのアクターモデルにインスパイアされています。
Ractorとは
Ractorは 「アクターのように振る舞う、プロセス内独立実行単位」。それぞれが 独自のGVLを持つので、複数のRactorはCPUの別コアで本当に並列に動きます。
r1 = Ractor.new { 10_000_000.times.sum }
r2 = Ractor.new { 10_000_000.times.sum }
r3 = Ractor.new { 10_000_000.times.sum }
r4 = Ractor.new { 10_000_000.times.sum }
# 4並列で実行される
[r1, r2, r3, r4].map(&:take)
Ractorの制約
Ractorは強力ですが、以下の 強い制約 があります。
shared = "hello"
r = Ractor.new(shared) { |s| s.upcase }
# 文字列はコピーされて渡される
Ractorは実験的扱い
Ruby 3.3時点でもRactorは 「実験的(experimental)」 扱いです。標準ライブラリ・Gem・C拡張のRactor対応が進んでおらず、本番投入には慎重さが要ります。
ただし方向性は明確で、Rubyが マルチコア時代に対応する核として、今後数年で実用化が進むと見られています。詳細は第14章。
2-6. ガベージコレクション
CRubyのメモリ管理は マーク・スイープ + 世代別GC + インクリメンタルGC という現代的な実装です。
進化の歴史
Ruby 1.x素朴なマーク・スイープGC
Ruby 2.1世代別GC(RGenGC)導入
Ruby 2.2インクリメンタルGC追加
Ruby 2.7コンパクションGC(GC.compact)追加
Ruby 3.0自動コンパクション
Ruby 3.3 GCアルゴリズムがgemとしてプラガブルに(MMTk実験)
世代別GC
オブジェクトを「新しい世代(new generation)」と「古い世代(old generation)」に分け、古い世代のGCを少なくすることで効率化。多くのオブジェクトは生まれてすぐ死ぬという統計に基づいた最適化です。
コンパクションGC
GC.compact を呼ぶと メモリ上のオブジェクトを詰め直し、断片化を解消します。Ruby 3.0以降は自動で行われます。
GC.compact # 手動コンパクション
GC.stat # GC統計情報
2-7. このセクションのまとめ
処理系:
CRuby(MRI)が事実上の標準
JRuby(JVM、真の並列)/ TruffleRuby(GraalVM、超高速)/ mruby(組み込み)
実行モデル:
ソース → Prism/parse.y → AST → YARVバイトコード → VM実行
RubyVM::InstructionSequenceでバイトコードを覗ける
YJIT(3.1+):
Shopify製のJITコンパイラ
Lazy Basic Block Versioningで型特化コード生成
Railsで15〜20% 高速化、Ruby 3.3で大幅改善
GVL(Global VM Lock):
CRubyのスレッドはバイトコード実行を直列化
CPUバウンド: GVLのためThreadでは並列化されない → Ractorを使う
I/Oバウンド: I/O待ち中はGVLを解放するのでThreadが効く
Ractor(3.0+、実験的):
GVLを持つ独立した実行単位(アクター的)
本物の並列実行が可能
共有オブジェクトに強い制約
GC:
マーク・スイープ + 世代別 + インクリメンタル + コンパクション
GC.compact / GC.stat
次のセクションでは、Rubyの変数・データ型・Symbol vs String・nilとfalse といった基礎を扱います。
3. 変数・データ型
このセクションはRubyを理解する上での基礎パート。Rubyの最大の特徴である 「すべてはオブジェクト」、変数の4種類、SymbolとStringの使い分け、nilとfalseの特別さを抑えます。
3-1. すべてはオブジェクト
Rubyの最も重要な特性は 「すべてがオブジェクト」 であること。
1.class # Integer
1.0.class # Float
"hello".class # String
:sym.class # Symbol
nil.class # NilClass
true.class # TrueClass
false.class # FalseClass
[].class # Array
{}.class # Hash
# クラス自体もオブジェクト
String.class # Class
Class.class # Class(Classもオブジェクト)
Javaの int のような プリミティブ型は存在しません。整数の 1 も Integer クラスのインスタンスで、メソッドを呼べます。
1.even? # false
1.+(2) # 3(演算子もメソッド呼び出し!)
1.times { puts "hi" } # 1回出力
nil.to_s # ""(nilにもメソッドがある)
この一貫性が、Rubyの表現力の源泉です。「3という数値に times メソッドがある」のは、整数がオブジェクトであることの自然な帰結なのです。
3-2. 変数の4種類(ローカル / インスタンス / クラス / グローバル)
Rubyの変数は 接頭辞によって種類が決まります。
| 種類 | 接頭辞 | 例 | スコープ |
|---|---|---|---|
| ローカル変数 | (無し)または _ |
x, name |
ブロック・メソッド・スクリプト |
| インスタンス変数 | @ |
@name |
単一のインスタンス |
| クラス変数 | @@ |
@@count |
クラス階層全体(共有) |
| グローバル変数 | $ |
$debug |
プログラム全体 |
| 定数 | 大文字始まり | PI, Math::PI |
定義スコープ |
ローカル変数
最も普通の変数。メソッド・ブロックの中、またはスクリプトのトップレベルで生きます。
def greet(name)
message = "hello, #{name}" # ローカル変数
puts message
end
[1, 2, 3].each do |x|
doubled = x * 2 # ブロックスコープ
end
# doubledはブロックの外で参照できない
インスタンス変数(@)
オブジェクトごとの状態を保持。未初期化でアクセスしても nil になる(NameErrorにならない)のがRubyの特徴。
class User
def initialize(name)
@name = name # インスタンス変数
end
def hello
"hi, #{@name}"
end
def whatever
@undefined # nil(未初期化でもエラーにならない)
end
end
これは便利な反面、タイポによるバグの温床でもあります。@nmae と書いてもエラーにならず、ただnilになるだけ。Object#instance_variable_defined? で確認するか、警告を有効にして実行することで検出できます。
クラス変数(@@)
クラス階層全体で共有される変数。サブクラスでも値が共有されるため、継承との相性が悪く、現代のRubyでは使用が推奨されません。
class Counter
@@count = 0
def self.increment; @@count += 1; end
def self.count; @@count; end
end
# 代わりに「クラスインスタンス変数」を使うのが推奨
class Counter
@count = 0 # ← これはCounterというオブジェクト自体のインスタンス変数
class << self
attr_accessor :count
end
def self.increment; @count += 1; end
end
グローバル変数($)
プログラム全体で共有。多用は避けるべきだが、$stdout $stderr $DEBUG などのシステム指定の特殊変数は活用される。
定数
大文字で始まる名前は定数として扱われ、再代入すると警告が出ます(エラーにはならない)。
PI = 3.14
PI = 3.14159 # warning: already initialized constant PI
class User
MAX_AGE = 150
end
User::MAX_AGE # 150
3-3. 主要な組み込み型
| 型 | リテラル例 | 用途 |
|---|---|---|
Integer |
42, -7, 0xff, 1_000_000 |
整数(任意精度) |
Float |
3.14, 1e10, Float::INFINITY |
IEEE 754倍精度 |
Rational |
1/3r, Rational(1, 3) |
有理数 |
Complex |
1+2i, Complex(1, 2) |
複素数 |
String |
"hello", 'a' |
文字列(ミュータブル) |
Symbol |
:name, :"with space" |
イミュータブル識別子 |
Array |
[1, 2, 3] |
可変配列 |
Hash |
{ a: 1, b: 2 } |
キー値マッピング |
Range |
1..10, 1...10 |
範囲 |
Regexp |
/pattern/ |
正規表現 |
Proc |
Proc.new { ... }, ->{...} |
無名関数 |
NilClass |
nil |
値の不在 |
TrueClass |
true |
真 |
FalseClass |
false |
偽 |
Pythonと違い、String は ミュータブル です(変更可能)。
s = "hello"
s.upcase! # 元の文字列を破壊的に変更
s # "HELLO"
ただし frozen string literal という機能で、文字列リテラルを暗黙にフリーズできます(推奨)。
# frozen_string_literal: true
s = "hello"
s.upcase! # FrozenError: can't modify frozen String
3-4. nilとfalse(falsyはこの2つだけ)
Rubyの真偽性は シンプルです。
falsy: nilとfalseの2つだけ
それ以外はすべてtruthy(0でも "" でも [] でも、すべてtruthy!)
if 0
puts "0はtruthy" # 出力される(Pythonと違う!)
end
if ""
puts "空文字もtruthy" # 出力される
end
if []
puts "空配列もtruthy" # 出力される
end
if nil
puts "実行されない"
end
Python・JavaScriptと異なる重要な特徴。Rubyでは「値があるか?」と「値がfalsyか?」を区別したいとき、nil? を明示的に使います。
items = []
# Bad(Python・JSの感覚で)
if items.empty? # 空配列はtruthyなので必要
puts "no items"
end
# 値がない場合はnilチェック
result = api_call
if result.nil?
puts "no result"
end
nilの特別な性質
nil は NilClassのシングルトンインスタンスで、メソッドが豊富に生えています。
nil.to_s # ""
nil.to_a # []
nil.to_h # {}
nil.inspect # "nil"
nil.nil? # true
nil.respond_to?(:foo) # false
これにより nil をうっかり扱っても多くの場合エラーにならない(その代わりにバグが見えにくい)。安全ナビゲーション &.(4-5)と組み合わせると安心です。
3-5. SymbolとString
Rubyの特徴の一つが SymbolとStringの使い分け。
:name # Symbol
"name" # String
違い
| 観点 | Symbol | String |
|---|---|---|
| ミュータブル | × | ○(frozenでなければ) |
| 同一インスタンス | ○(同名は同一) | ×(毎回新しい) |
| GC対象 | 動的に作ったものはGCされる(2.2+) | される |
| 用途 | 識別子・キー・メタ | テキストデータ |
:foo.object_id == :foo.object_id # true(同じインスタンス)
"foo".object_id == "foo".object_id # false(別インスタンス)
使い分けの指針
Symbolを使う:
- Hashのキー({ name: "a" } のように)
- メソッド名・属性名を渡すとき(attr_accessor :name)
- 状態フラグ(status: :pending)
Stringを使う:
- ユーザ入力・外部データ
- 表示するテキスト
- 文字列操作が必要なとき
Hashのキーとしての慣習
RubyではHashのキーは Symbolが圧倒的に多いです。
# 現代的な書き方(Symbolキー)
user = { name: "Alice", age: 30 }
# 同等の書き方
user = { :name => "Alice", :age => 30 }
# 文字列キーは外部データ(JSON由来など)に
json_user = { "name" => "Alice", "age" => 30 }
{ key: value } の構文は Symbolキー専用の糖衣構文。これがRubyがSymbolを多用するカルチャーを定着させた一因です。
3-6. 数値型(Integer/Float/Rational/BigDecimal)
Integerは任意精度
2 ** 1000 # 巨大な整数も普通に扱える
(2 ** 1000).class # Integer
Rubyの Integer は元々 Fixnum(小さな整数、即値)と Bignum(任意精度)に分かれていましたが、Ruby 2.4で統合されて単に Integer になりました。
Floatの落とし穴(IEEE 754)
0.1 + 0.2 == 0.3 # false
0.1 + 0.2 # 0.30000000000000004
# 比較は誤差を考慮する
(0.1 + 0.2 - 0.3).abs < Float::EPSILON # true
Rational(有理数)
r = 1/3r # Rational(1, 3)
r + 1/6r # (1/2)
r.to_f # 0.3333...
リテラル 1/3r で書けます。金融計算などで誤差が許されない場面で重要。
BigDecimal
10進数の任意精度。
require "bigdecimal"
require "bigdecimal/util"
a = BigDecimal("0.1")
b = BigDecimal("0.2")
a + b # 0.3e0(誤差なし)
3-7. このセクションのまとめ
すべてはオブジェクト:
整数もnilもtrueもすべてオブジェクト
プリミティブ型は存在しない
演算子もメソッド呼び出し(1.+(2))
変数の4種類:
ローカル(普通)/ @インスタンス / @@クラス / $グローバル / 定数(大文字)
@@クラス変数は継承で共有されるので推奨されない
代わりに「クラスインスタンス変数」を使う
主要な組み込み型:
Integerは任意精度、FloatはIEEE 754
Stringはミュータブル(frozen_string_literalで固定化)
Symbolはイミュータブルで識別子向き
nilとfalse:
Rubyのfalsyはこの2つだけ
0や "" や [] はすべてtruthy
Python/JSの感覚を持ち込まない
Symbol vs String:
Hashキー・メソッド名・状態 → Symbol
ユーザ入力・テキスト → String
{ name: "a" } はSymbolキーの糖衣構文
次のセクションでは、Rubyの演算子と式――==/eql?/equal?/=== の使い分け、安全ナビゲーション、多重代入を扱います。
4. 演算子と式
Rubyの演算子は すべてメソッド呼び出しの糖衣構文であるという統一感が特徴。==/eql?/equal?/=== の4つの等価性、&.、多重代入を整理します。
4-1. 算術・比較・論理
5 + 2 # 7
5 - 2 # 3
5 * 2 # 10
5 / 2 # 2(整数同士は整数除算)
5.0 / 2 # 2.5(どちらかがFloatならFloat)
5 % 2 # 1(剰余)
5 ** 2 # 25(べき乗)
-5 / 2 # -3(負の整数除算は床関数)
整数除算は注意
RubyはPython 2と同じく、整数同士の / は整数除算です。Python 3やJavaScriptのように常にFloatになるわけではありません。
5 / 2 # 2
5.fdiv(2) # 2.5(明示的にFloat除算)
比較演算子
1 == 1 # true
1 != 2 # true
1 < 2 # true
1 <=> 2 # -1(宇宙船演算子、3値比較)
1 <=> 1 # 0
2 <=> 1 # 1
"a" <=> "b" # -1
<=> は 「左右の比較で -1, 0, 1を返す」 演算子で、Comparable モジュールに渡すと残りの比較演算子が自動的に揃います。
class Score
include Comparable
attr_reader :value
def initialize(value); @value = value; end
def <=>(other); value <=> other.value; end
end
Score.new(10) > Score.new(5) # true(自動的に動く)
論理演算子(and / or / notと && / || / !)
Rubyには2系統の論理演算子があります。
true && false # false
true || false # true
!true # false
true and false # false
true or false # true
not true # false
違い: 演算子の優先順位。&& と || は通常通りの高い優先順位。and と or は 代入よりも低い 優先順位を持ちます。
x = true && false # x = false
x = true and false # (x = true) and false → xはtrue!
慣例: 論理判断には && || を使い、and or は文脈フローのつなぎに限定する(後述のKernel#raiseなどと組み合わせるとき)。
4-2. 演算子はメソッド
Rubyのすべての演算子は メソッド呼び出しの糖衣です。
1 + 2 # 1.+(2) と等価
1 < 2 # 1.<(2)
[1] + [2] # [1].+([2])
"a" * 3 # "a".*(3) → "aaa"
これは、自作クラスでも演算子オーバーロードができることを意味します。
class Vec
attr_reader :x, :y
def initialize(x, y); @x, @y = x, y; end
def +(other); Vec.new(x + other.x, y + other.y); end
def *(scalar); Vec.new(x * scalar, y * scalar); end
def ==(other); x == other.x && y == other.y; end
def to_s; "(#{x}, #{y})"; end
end
a = Vec.new(1, 2)
b = Vec.new(3, 4)
a + b # (4, 6)
a * 3 # (3, 6)
a == Vec.new(1, 2) # true
オーバーロード可能な演算子: +, -, *, /, %, **, ==, <, >, <=, >=, <=>, [], []=, <<, >>, &, |, ^, ~, !, +@, -@, =~, ===。
4-3. ==・eql?・equal?・===
Rubyの等価判定は 4種類あり、それぞれ役割が違います。
| 演算子/メソッド | 意味 | 例 |
|---|---|---|
== |
値の等価(一般的に使う) | 1 == 1.0 → true |
eql? |
型まで含めた等価(Hashキー比較で使われる) | 1.eql?(1.0) → false |
equal? |
同一オブジェクトか(Object#equal? は再定義しない) | "a".equal?("a") → false |
=== |
case/inやgrepで使われる、左辺による「マッチする」判定 | Integer === 1 → true |
# == は値の等価
1 == 1.0 # true
"abc" == "abc" # true
# eql? は型を区別
1.eql?(1.0) # false(IntegerとFloat)
"abc".eql?("abc") # true
# equal? はobject_id比較(同一性)
a = "abc"
b = "abc"
a.equal?(b) # false(別オブジェクト)
a.equal?(a) # true
# === はcaseのwhenで使われる
Integer === 1 # true(左辺がクラスのときisinstance相当)
(1..10) === 5 # true(左辺がRangeのときinclude? 相当)
/foo/ === "foo bar" # true(左辺がRegexpのときmatch? 相当)
:approved === :approved # true
case文での ===
case x
when Integer then puts "int"
when String then puts "str"
when 1..10 then puts "small number"
when /foo/ then puts "matches foo"
end
これは内部的に Integer === x, String === x, (1..10) === x, /foo/ === x を順に評価します。=== の柔軟さがcase文の表現力の源です。
4-4. 真偽性(nilとfalse以外はすべて真)
第3章でも触れたが重要なので再掲。
# falsy
nil # ←
false # ← この2つだけ
# truthy(上記以外すべて)
0
""
[]
{}
"false"
:false
0.0
Object.new
Python・JavaScriptと異なるので、他言語からの移行時には特に注意。
イディオム
# 「nilでない場合」を確認
unless x.nil?
puts x
end
# 同等
if x
puts x
end
# 「値があれば使い、なければデフォルト」
name = user_input || "anonymous"
# Ruby 2.5+ の単一行
puts x if x
4-5. 安全ナビゲーション(&.)
Ruby 2.3で追加された 「ぼっち演算子」 こと &.。レシーバが nil の場合は nil を返し、エラーにしません。
user&.name # userがnilならnil
user&.profile&.address&.city # チェイン
# 従来の書き方
user && user.name
user && user.profile && user.profile.address && user.profile.address.city
# 似て非なる: tryメソッド(ActiveSupport)
user.try(:name) # Railsならこう書ける(&. と少し違う動作)
&. と try の違い: &. は nilのときだけ スキップ。try は メソッドが定義されていなくても スキップ(NoMethodErrorも飲み込む)。&. のほうが厳密で推奨。
4-6. 多重代入とスプラット演算子
多重代入
a, b = 1, 2
a, b = b, a # スワップ
a, b, c = [1, 2, 3]
スプラット(*)
first, *rest = [1, 2, 3, 4] # first=1, rest=[2,3,4]
*init, last = [1, 2, 3, 4] # init=[1,2,3], last=4
a, *mid, b = [1, 2, 3, 4, 5] # a=1, mid=[2,3,4], b=5
# 関数引数の展開
def f(a, b, c); ...; end
args = [1, 2, 3]
f(*args)
# Hashの展開(**)
def h(a:, b:); ...; end
hash = { a: 1, b: 2 }
h(**hash)
多重戻り値
def min_max(arr)
[arr.min, arr.max] # 配列を返す
end
lo, hi = min_max([3, 1, 4, 1, 5])
# lo=1, hi=5
4-7. このセクションのまとめ
算術:
/ は整数同士なら整数除算(5 / 2 == 2)
fdivで常にFloat除算
** はべき乗、<=> は3値比較
論理演算子:
&&, ||, ! は通常の優先順位(推奨)
and, or, notは代入より低い優先順位(注意)
演算子はメソッド:
1 + 2は1.+(2)
自作クラスで == や [] をオーバーロードできる
4つの等価性:
== 値の等価(普段使う)
eql? 型まで含めた等価(Hashキーで使われる)
equal? 同一オブジェクト(object_id比較)
=== case/grepで使われる、左辺による「マッチ」
真偽性:
falsyはnilとfalseのみ
0, "", [] はtruthy
安全ナビゲーション:
user&.nameでnilチェック付きアクセス
ActiveSupportのtryとは違う(&. は厳密)
多重代入とスプラット:
a, b = 1, 2 / a, *rest = arr
メソッド呼び出しでも *args, **kwargs
次のセクションでは、Rubyの制御フロー――if/unless/case/case/in/修飾子構文――を扱います。
5. 制御フロー
Rubyの制御フロー構文は すべてが式(expression) で、値を返します。これは「文(statement)」と「式」を区別するJava/Cと大きく違う性質です。
5-1. if / unless / case
if x > 0
"positive"
elsif x < 0
"negative"
else
"zero"
end
elsif(else if でも elif でもない)であることに注意。
ifは式
label = if x > 0
"positive"
elsif x < 0
"negative"
else
"zero"
end
if文全体が値を返すので、変数に代入できます。これはRubyの表現力の核です。
unless
if !cond の代わりに unless cond が使えます。
unless logged_in?
redirect_to login_path
end
# 同等
if !logged_in?
redirect_to login_path
end
unless を使うか if ! を使うかは流儀次第ですが、否定の else は避けるのが慣習。
# Bad(読みにくい)
unless x.zero?
...
else
...
end
# Good
if x.zero?
...
else
...
end
case式
case status
when 200
"OK"
when 404
"Not Found"
when 500..599
"Server Error"
else
"Other"
end
各 when は内部で === を使います(4-3参照)。クラス・Range・正規表現で柔軟にマッチできます。
case x
when Integer then "int"
when String then "str"
when 1..10 then "small"
when /^\d+$/ then "digits"
when nil then "nil"
end
5-2. while / until / loop / for
while / until
i = 0
while i < 10
puts i
i += 1
end
# untilはwhile !condと同等
until i >= 10
i += 1
end
# doは省略可
while i < 10 do
...
end
loop
無限ループ。break で抜ける。
loop do
input = gets.chomp
break if input == "quit"
process(input)
end
for
for x in iterable という構文もありますが、ほとんど使われません。慣習的に each を使います。
# 推奨されない
for x in [1, 2, 3]
puts x
end
# 推奨
[1, 2, 3].each do |x|
puts x
end
for のほうが優れている点はほぼ無く、each の方がブロックスコープを作るため安全です(forはループ変数がブロック外に漏れる)。
5-3. 修飾子としての制御フロー(後置if)
Rubyの特徴的な構文として、「右側にif/unless/whileを書ける」 修飾子構文があります。
puts "hello" if name
puts "warning" unless safe?
print "." while running?
# 同等
if name
puts "hello"
end
短い1行を簡潔に書きたいときに使う。ただし条件が複雑になると読みにくくなるので、ブロック式のifと使い分けます。
# Good(短く意図が明確)
return :error unless valid?
# Bad(条件が長くて読みにくい)
do_complex_thing if user.admin? && !maintenance_mode? && rate_limit.ok?
5-4. パターンマッチング(case/in、3.0+)
Ruby 3.0で導入された 構造的パターンマッチング。Pythonの match 文と同じく、強力な機能です。
値マッチ
case status
in 200
"OK"
in 404
"Not Found"
in (500..599)
"Server Error"
end
配列パターン
case point
in [0, 0]
"origin"
in [x, 0]
"on x-axis at #{x}"
in [0, y]
"on y-axis at #{y}"
in [x, y]
"(#{x}, #{y})"
end
Hashパターン
case event
in { type: "click", x:, y: }
"click at (#{x}, #{y})"
in { type: "key", key: }
"key #{key}"
end
x: は x: x の省略形(Ruby 3.1+)。マッチした値が同名のローカル変数に束縛されます。
クラスパターン
case obj
in Integer => i if i > 0
"positive int: #{i}"
in String => s
"string: #{s.upcase}"
in Array
"array"
end
ガード(if)
case x
in Integer => n if n.even?
"even: #{n}"
in Integer => n
"odd: #{n}"
end
inによる1行マッチ
case_value in pattern # Ruby 3.0
case_value => pattern # Ruby 3.0+ で右代入として
{ name: "Alice", age: 30 } => { name:, age: }
puts name # "Alice"
puts age # 30
case/inとcase/whenの違い
case/when: === で「マッチするか」判定(左辺 === 右辺)
case/in: パターンマッチで構造分解と束縛
case/in は 値の中身を取り出して分解できるのが核。「何かが等しいか」より「何かの形をしているか」を判定したいときに使います。
5-5. throw / catch
throw と catch は 例外とは別の、ループ脱出機構。深いネストから一気に抜けたいときに使います。
result = catch(:found) do
matrix.each do |row|
row.each do |cell|
throw :found, cell if cell == target
end
end
nil # 見つからなかった
end
例外(raise/rescue)とは違い、「正常な制御フローの一部としてのジャンプ」 を表現する仕組み。実務では使用頻度は低いですが、知っておくと役立ちます。
5-6. このセクションのまとめ
分岐:
if / elsif / else(elifではない)
unless(if ! の代わり、ただしelseを伴うのは避ける)
case/whenは === でマッチ
case/in(3.0+)は構造的パターンマッチング
ループ:
while / until / loop
forよりeachが慣習的(スコープが安全)
修飾子(後置):
puts "hi" if cond
簡潔な1行に有効
パターンマッチング(3.0+):
case ... in配列・Hash・クラス・ガード
Hashパターンで構造分解+変数束縛
値ではなく「形」を判定したいときに
throw / catch:
深いネストからの脱出
例外とは別の正常な制御フロー
次のセクションでは、Rubyの核――メソッド――を扱います。
6. メソッド
メソッドはRubyのロジックの単位。Pythonの関数より深い「呼び出し慣習」と「末尾の ? !」「キーワード引数の歴史」など、Rubyならではの世界が広がります。
6-1. メソッド定義の基本
def greet(name)
"hello, #{name}"
end
greet("Alice") # "hello, Alice"
暗黙のreturn
Rubyでは メソッドの最後の式の値が自動的に返り値になります。return は省略可能。
def add(a, b)
a + b # この値が返る
end
# 同等
def add(a, b)
return a + b
end
return は 早期リターンで使うのが慣習です。
def divide(a, b)
return nil if b.zero?
a / b
end
エンドレスメソッド(Ruby 3.0+)
短い1行メソッドを = で書ける構文。
def square(x) = x * x
def greet(name) = "hello, #{name}"
ただし可読性のため、複雑なロジックには使わないのが推奨。
括弧の省略
メソッド呼び出しの括弧は省略可能。多くの場面で「自然な英語に近づける」ために省きます。
puts "hello" # puts("hello") と同じ
greet "Alice" # 引数があっても省略可能(ただし慣習として「人間が読める」場面に限る)
# RailsのDSLでこれが多用される
has_many :books
validates :name, presence: true
6-2. 引数の種類と順序
Rubyのメソッド引数は 6種類あり、定義順序にも意味があります。
def f(a, b = 10, *args, c:, d: 20, **opts, &block)
# a必須位置引数
# b = 10デフォルト引数
# *args可変長位置引数
# c: 必須キーワード引数
# d: 20デフォルト付きキーワード引数
# **opts可変長キーワード引数
# &blockブロック引数
end
位置引数とデフォルト引数
def greet(name, greeting = "hello")
"#{greeting}, #{name}"
end
greet("Alice") # "hello, Alice"
greet("Alice", "hi") # "hi, Alice"
可変長引数(*args)
def sum(*nums)
nums.reduce(0, :+)
end
sum(1, 2, 3) # 6
sum(*[1, 2, 3]) # 6(配列を展開して渡す)
キーワード引数
Ruby 2.0で導入されたキーワード引数。呼び出し時に名前を明示する引数です。
def create_user(name:, age: 0, role: "user")
...
end
create_user(name: "Alice")
create_user(name: "Alice", age: 30)
# 順序は問わない
create_user(role: "admin", name: "Bob")
name: のように : の後ろに値が無いものは 必須。age: 0 のように値があるものは デフォルト付き(省略可能)。
**opts(残りのキーワード引数)
def f(a:, **opts)
puts a, opts
end
f(a: 1, b: 2, c: 3) # a=1, opts={b: 2, c: 3}
&block
最後にブロック引数を &block で受け取れます(7章で詳述)。
6-3. キーワード引数の歴史(Ruby 3.0の分離)
Rubyのキーワード引数には複雑な歴史があり、Ruby 3.0で大きな破壊的変更が起きました。理解しておく価値があります。
Ruby 2.xの暗黙変換
Ruby 2.xまでは、Hashとキーワード引数が暗黙的に相互変換されていました。
# Ruby 2.x(廃止された挙動)
def f(a:, b:)
...
end
# 以下が両方動く
f(a: 1, b: 2) # キーワード引数として
f({a: 1, b: 2}) # Hashがキーワードに自動変換される(曖昧)
これは便利な反面、「最後のHash引数がキーワードか普通の引数か曖昧」 という根深いバグの原因になりました。
# 2.xで問題になっていた例
def g(opts = {})
puts opts
end
g(a: 1) # Hashとして渡したつもり
# しかしキーワード引数を持つメソッドだと...
Ruby 3.0の分離
Ruby 3.0で **「Hashとキーワード引数は別物」**として完全に分離されました。
# Ruby 3.0+
def f(a:, b:)
...
end
f(a: 1, b: 2) # OK
f({a: 1, b: 2}) # ArgumentError!明示的に展開が必要
f(**{a: 1, b: 2}) # OK(** で明示的に展開)
これにより曖昧さは消えましたが、Ruby 2から3への移行時に大量の警告と修正が必要でした。Ruby 2.7 で警告が出ていた問題が、Ruby 3.0 でエラーになるパターンです。
教訓: 最初から明示的にキーワード引数を使い、**opts で展開するのが今後の標準です。
6-4. attr_accessor / attr_reader / attr_writer
Rubyのオブジェクトの属性アクセスを楽に定義するクラスメソッド。
class User
attr_accessor :name # nameとname= の両方
attr_reader :id # idだけ(読み取り専用)
attr_writer :password # password= だけ(書き込み専用)
def initialize(id, name)
@id = id
@name = name
end
end
u = User.new(1, "Alice")
u.name = "Bob" # name= が呼ばれる
u.name # nameが呼ばれる("Bob")
u.id = 2 # NoMethodError(writerなし)
これは メタプログラミングの典型例です。attr_accessor 自体はクラス本体の中で呼ばれるメソッドで、define_method を使ってアクセサメソッドを動的に生成しています。Javaのgetter/setterのようなboilerplateを1行で済ませるRuby流。
6-5. メソッドの可視性(public / protected / private)
Rubyのアクセス制御は3段階。
| 修飾子 | 意味 |
|---|---|
public |
どこからでも呼べる(デフォルト) |
protected |
同じクラス・サブクラスのインスタンスから呼べる |
private |
レシーバを書かずに(selfから)しか呼べない |
class A
def public_method; end
protected
def protected_method; end
private
def private_method; end
end
a = A.new
a.public_method # OK
a.protected_method # NoMethodError(外部から呼べない)
a.private_method # NoMethodError
RubyのprivateはJavaと違う
Javaのprivateは「クラス外からは呼べない」ですが、Rubyのprivateは 「明示的なレシーバ付きで呼べない(self.method_nameは不可)」 という独特の意味です。
class A
def call_private
private_method # OK(selfを省略)
end
def call_private_explicitly
self.private_method # NoMethodError!(selfを明示するとダメ)
end
private
def private_method; "private"; end
end
ただしRuby 2.7+ で self.private_method= のようなsetterは許可されました(代入の文法上の都合)。
protectedの使い所
class Account
def initialize(balance); @balance = balance; end
def richer_than?(other)
balance > other.balance # 同じクラスの他インスタンスからprotected呼び出し
end
protected
attr_reader :balance # 外部からは隠す
end
「同じクラスの仲間からは見えるが、外部からは隠したい」というユースケース。実用上はprivateより使用頻度が低い。
6-6. 真偽値返却メソッド(?)と破壊メソッド(!)の慣習
Rubyのメソッド名に ? と ! を付ける慣習があります。これは構文ではなく 命名慣習で、コードの意図を伝えます。
? 真偽値返却メソッド
[].empty? # true
1.even? # false
1.zero? # false
nil.nil? # true
"abc".include?("b") # true
「この値は何々か?」と質問する形で読めるメソッドには ? を付ける。
! 破壊的メソッド・危険メソッド
s = "hello"
s.upcase # "HELLO"(新しい文字列を返す、元は変わらない)
s.upcase! # "HELLO"(元の文字列を破壊的に変更)
s # "HELLO"
a = [3, 1, 2]
a.sort # [1, 2, 3](新しい配列)
a.sort! # 元の配列を破壊的にソート
「! 付きは元のオブジェクトを変更する、または例外を投げる可能性がある」と覚える。
Railsでも頻繁に登場
user.save # 失敗時falseを返す
user.save! # 失敗時ActiveRecord::RecordInvalidを投げる
! 付きはより**「危険」**な振る舞いを示すサイン。Rubyの慣習を読み解くキーになります。
6-7. このセクションのまとめ
基本:
def name(args); ...; end
最後の式が暗黙の戻り値(returnは早期離脱に)
エンドレスメソッド: def square(x) = x * x (3.0+)
括弧は省略可能(DSLで多用)
引数の6種類:
位置 / デフォルト付き / *args / キーワード / **opts / &block
Ruby 3.0でHashとキーワードが完全分離
→ 渡すときはf(**hash) と明示的に
アクセサ:
attr_accessor :name → 読み書き両方
attr_reader / attr_writerで個別
可視性:
public(デフォルト)
protected: 同クラス内から呼べる
private: レシーバ付き呼び出し不可(Javaとは少し違う)
命名慣習:
? 真偽値返却(empty? even? include?)
! 破壊的・危険(sort! save!)
これは構文ではなく強い慣習
次のセクションでは、Rubyの真髄――ブロック・Proc・Lambda――に入ります。
7. ブロック・Proc・Lambda
Rubyの表現力の核は ブロックにあります。each, map, select のようなメソッドに渡す do ... end や { ... } の正体を理解すると、Rubyの世界の見方が一段深くなります。さらに Proc と lambda の微妙な差異まで踏み込みます。
7-1. ブロックの基本
ブロックは メソッドに渡せるコードの塊。Rubyならではの第一級「コードオブジェクト」です。
[1, 2, 3].each do |x|
puts x
end
# 同等(短いとき { } を使う慣習)
[1, 2, 3].each { |x| puts x }
{ } とdo/endの使い分け
{ } 1行で書ける場合、戻り値を使う場合
do/end複数行、副作用主体(戻り値を使わない)の場合
# Good: 1行で戻り値を使う
result = [1, 2, 3].map { |x| x * 2 }
# Good: 複数行で副作用
[1, 2, 3].each do |x|
log(x)
process(x)
end
# Bad: 改行と { } を組み合わせる
[1, 2, 3].each {
|x| ...
}
ブロックは関数の引数ではなく特別な構文
ブロックは メソッド呼び出しの「特別な引数」 で、各メソッドに対して最大1つだけ渡せます。
[1, 2, 3].each(&block_var) # 既存のProcを渡したいときは &
7-2. yieldとblock_given?
ブロックを受け取るメソッドの中では yield でブロックを呼び出せます。
def repeat(n)
n.times { yield }
end
repeat(3) { puts "hi" }
# hi
# hi
# hi
引数を渡す
def each_squared(arr)
arr.each { |x| yield(x * x) }
end
each_squared([1, 2, 3]) { |sq| puts sq }
# 1
# 4
# 9
block_given? でブロックの有無を確認
def maybe_yield
if block_given?
yield
else
puts "ブロックが無い"
end
end
maybe_yield # "ブロックが無い"
maybe_yield { puts "あり" } # "あり"
block_given? を使わずに yield を呼ぶと、ブロックがない場合 LocalJumpError になります。
&block で明示的に受け取る
ブロックを オブジェクトとして受け取りたいなら、引数に &block を書く。
def repeat(n, &block)
n.times(&block) # blockはProcオブジェクト
end
repeat(3) { puts "hi" }
&block は 「ブロックをProc化して受け取る」 演算子。yield で呼ぶ場合よりも柔軟ですが、Proc化のオーバーヘッドが発生します。
7-3. Procとlambdaの違い(4つの非対称性)
ブロックを「オブジェクト化」したものが Proc。さらにその一種でより厳格なのが lambda。両者には4つの非対称性があります。
作り方
pr = Proc.new { |x, y| x + y }
pr = proc { |x, y| x + y } # 同義のショートカット
la = lambda { |x, y| x + y }
la = ->(x, y) { x + y } # 短縮記法(推奨)
違い1: 引数の数チェック
pr = Proc.new { |x, y| [x, y] }
la = lambda { |x, y| [x, y] }
pr.call(1) # [1, nil](多めや少なめでも許容)
pr.call(1, 2, 3) # [1, 2](余分を捨てる)
la.call(1) # ArgumentError(引数の数が違う)
la.call(1, 2, 3) # ArgumentError
lambda は メソッドと同じ厳格さで引数を扱う。Proc は緩い。
違い2: returnの挙動
def proc_test
p = Proc.new { return 1 }
p.call
return 2
end
proc_test # 1(Procのreturnが外側のメソッドから抜ける)
def lambda_test
l = lambda { return 1 }
l.call
return 2
end
lambda_test # 2(lambdaのreturnはlambdaから抜けるだけ)
これが最も大きな違い。Procは外側のメソッドを巻き込んで終了させる。lambdaは自分のスコープから抜けるだけ。
違い3: breakの挙動
Proc の中で break を使うと「lambda化されていない場合はLocalJumpError」「lambdaの場合はProcから抜ける」など微妙な違いがあります。
違い4: 多重代入
pr = Proc.new { |a, b| [a, b] }
la = lambda { |a, b| [a, b] }
pr.call([1, 2]) # [1, 2](自動で多重代入する)
la.call([1, 2]) # ArgumentError(自動展開しない)
どちらを使うべきか
普段使い: lambda(厳格、関数的、returnが直感的)
柔軟さが必要: Proc(既存メソッドのブロック挙動と互換)
現代のRubyでは lambda または -> がデフォルト。Proc は古いコードや特殊なケースに限られます。
7-4. &とブロックの渡し方
& でブロックをProcに / Procをブロックに
& は 「ブロックとProcの相互変換」 を行う演算子。
# Proc → ブロックとして渡す
double = ->(x) { x * 2 }
[1, 2, 3].map(&double) # [2, 4, 6]
# ブロックをProcとして受け取る(メソッド側)
def my_each(arr, &block)
arr.each { |x| block.call(x) }
end
Symbol#to_proc(最も使われるイディオム)
& にSymbolを渡すと、そのメソッドを呼ぶProcに変換されます。
[1, 2, 3].map(&:to_s) # ["1", "2", "3"]
["a", "b"].map(&:upcase) # ["A", "B"]
# 同等
[1, 2, 3].map { |x| x.to_s }
これは Symbolクラスに to_proc メソッドが定義されているためで、&:to_s は &:to_s.to_proc と同じ意味。Rubyらしい簡潔さの典型例。
Method#to_proc
fn = method(:puts)
[1, 2, 3].each(&fn) # 1, 2, 3が出力される
7-5. クロージャとしてのブロック
ブロックは クロージャ。外側のスコープの変数を捕捉します。
counter = 0
[1, 2, 3].each { |x| counter += x }
counter # 6(ブロック内で外側の変数を更新)
クロージャの作成
def make_counter
count = 0
-> { count += 1 }
end
c = make_counter
c.call # 1
c.call # 2
c.call # 3
外側の変数 count は、戻ってきたlambdaに捕捉されています。第8章のオブジェクトと並ぶ「状態を持たせる」もう一つの方法です。
ブロックローカル変数
ブロック内で同名の外側変数を使いたくないとき、; 後に変数名を書くとブロックローカルに。
x = 10
[1, 2, 3].each { |i; x| x = i } # 外側のxは変わらない
x # 10
実用上は使われることは少ないですが、メタプログラミングで安全性を高めるテクニック。
7-6. Method・UnboundMethod
オブジェクトのメソッドを オブジェクト化するには method を使う。
m = "hello".method(:upcase)
m.call # "HELLO"
m.() # 同等
m[] # 同等
method は特定のレシーバに束縛された Methodオブジェクトを返します。
UnboundMethod
レシーバから切り離されたメソッド。あとで別のオブジェクトに束縛できる。
um = String.instance_method(:upcase)
um.bind("hello").call # "HELLO"
um.bind_call("hello") # 同等(2.7+)
メタプログラミングで「メソッドを取り外して別のクラスに移植する」ような場面で使われます。
7-7. このセクションのまとめ
ブロック:
do ... endまたは { ... }
各メソッド呼び出しに最大1つだけ
yieldまたは &blockで実行
block_given? で存在確認
ProcとLambda(4つの違い):
1. 引数チェック: Procは緩く、lambdaは厳格
2. return: Procは外側を抜ける、lambdaは自分から抜けるだけ
3. break: 微妙な挙動差
4. 多重代入: Procは自動展開、lambdaはしない
推奨: lambda(または ->)
& 演算子:
Proc ⇄ blockの変換
&:method_nameはSymbol#to_procを使うイディオム
例: map(&:to_s)
クロージャ:
外側の変数を捕捉する
状態を持たせる手段の一つ
Method / UnboundMethod:
メソッドをオブジェクト化
method(:name) / instance_method(:name)
メタプロで活用
次のセクションでは、Rubyのクラスとオブジェクトモデルに入っていきます。
8. オブジェクトとクラス
Rubyは純粋オブジェクト指向。すべてがオブジェクトで、クラス自身もオブジェクト、メソッドもオブジェクト化できます。JavaやPythonとは少し違うRuby流のクラスモデルを順に解説します。
8-1. クラス定義
class Person
def initialize(name, age)
@name = name
@age = age
end
def greet
"hi, I'm #{@name}"
end
def adult?
@age >= 20
end
end
p = Person.new("Alice", 30)
p.greet # "hi, I'm Alice"
p.adult? # true
new と initialize
Person.new(...) を呼ぶと、内部的に Person.allocate でインスタンスを作成し、initialize を呼んで初期化します。new は Class クラスのメソッドで、コンストラクタの「外枠」を提供。
# 大雑把な等価
def new(*args)
obj = allocate
obj.send(:initialize, *args)
obj
end
これが「Rubyのコンストラクタは initialize」と言われる理由です。
クラスメソッド
class Pizza
def self.margherita # クラスメソッド
new(["mozzarella", "tomato"])
end
def initialize(toppings)
@toppings = toppings
end
end
p = Pizza.margherita # クラスメソッド呼び出し
def self.method_name という書き方が一般的。class << self ブロックで複数まとめて書くこともできる(後述)。
8-2. オープンクラス(モンキーパッチ)
Rubyの最も特徴的な仕組みが オープンクラス。既存のクラスをいつでも・どこでも開いて拡張できる。
# 標準ライブラリのStringを拡張
class String
def shout
upcase + "!"
end
end
"hello".shout # "HELLO!"
これは、Rubyのすべてのクラスが「閉じていない」という設計判断によります。「便利にできるのなら、後から追加すればいい」 という思想。
モンキーパッチの是非
オープンクラスは強力ですが、安易に使うとカオスを生む。
# 危険な例: 標準クラスの挙動を変える
class Integer
def +(other)
super + 1 # おかしな足し算!
end
end
1 + 1 # 3(バグ生成)
Integer#+ のような根本的なメソッドを書き換えると、Ruby全体が壊れます。
現代の指針
1. 標準クラスへのモンキーパッチは原則避ける
2. どうしても必要ならActiveSupportのように明確な責務とスコープで
3. プロジェクト内で限定的に使うならRefinements(9章)
4. 自前のクラスを拡張したいだけなら、サブクラスやモジュールで対応
Railsの ActiveSupport は「便利だが多すぎる」という批判もある一方、Ruby文化を象徴する存在。5.minutes.ago、"hello".underscore のような表現はオープンクラスの賜物です。
8-3. 継承とsuper
単一継承
Rubyは 単一継承。複数の親クラスを直接持つことはできません(その代わりModuleを include する)。
class Animal
def initialize(name); @name = name; end
def speak; "some sound"; end
end
class Dog < Animal
def speak; "Woof!"; end
end
Dog.new("Rex").speak # "Woof!"
super
親クラスの同名メソッドを呼ぶ。
class Dog < Animal
def initialize(name, breed)
super(name) # 親のinitializeを呼ぶ
@breed = breed
end
def speak
super + " " + super # 親の "some sound" を2回連結
end
end
superとsuper() の違い
class A
def greet(name)
puts "A: #{name}"
end
end
class B < A
def greet(name)
super # 引数なし → 現在の引数を全部そのまま渡す
super(name) # 引数あり → 指定の引数を渡す
super() # () あり → 引数なしで呼ぶ
end
end
super と super() は別物。括弧なしの super は「現在のメソッドの引数をそのまま親に渡す」、super() は「引数なしで呼ぶ」。これはRubyの独特な仕様で、嵌りどころです。
8-4. selfの意味(4つの文脈)
self は「現在のオブジェクト」。ただしどのコンテキストにいるかで指す対象が変わる。
| 文脈 | self が指すもの |
|---|---|
| トップレベル | main(Objectのインスタンス) |
| インスタンスメソッド内 | そのメソッドの呼び出し対象(インスタンス) |
| クラス本体内 | そのクラス自身 |
class << self ... end 内 |
そのクラスの特異クラス |
class A
puts self # A(クラス本体内)
def hello
puts self # インスタンス(呼び出し対象)
end
def self.cls_method
puts self # A(クラスメソッド内、selfはクラス)
end
class << self
puts self # #<Class:A>(特異クラス)
end
end
A.new.hello # #<A:0x...>
class << selfブロック
クラスメソッドをまとめて書くための慣習。
class User
class << self
def find(id); ...; end
def all; ...; end
def count; ...; end
private
def private_helper; ...; end
end
end
def self.find を3つ並べるよりすっきり。private も中で使えるのが利点。
8-5. オブジェクトの寿命とfreeze
freeze
オブジェクトを変更不能にできます。試みると FrozenError。
s = "hello"
s.freeze
s.frozen? # true
s.upcase! # FrozenError: can't modify frozen String
dup_s = s.dup # frozenではないコピー
clone_s = s.clone # こちらはfrozen状態をコピー
frozen_string_literalマジックコメント
# frozen_string_literal: true
s = "hello"
s.upcase! # FrozenError
ファイル冒頭に書くと、そのファイル内のすべての文字列リテラルが自動freeze される。文字列の意図しない変更を防ぎ、メモリ効率も上がります(同じリテラルが共有される)。
Ruby 3.4ではこれがデフォルトになる方向で議論が進んでいる(フロー予定)。新規コードでは付けるのが推奨。
Symbolは最初からfrozen
:foo.frozen? # true(常にfrozen)
これもSymbolが安全な理由のひとつ。
8-6. データクラス(Data・Struct)
Struct
古典的なクラス生成。
Point = Struct.new(:x, :y) do
def distance_from_origin
Math.sqrt(x ** 2 + y ** 2)
end
end
p = Point.new(3, 4)
p.x # 3
p.distance_from_origin # 5.0
p == Point.new(3, 4) # true(属性比較)
ただしStructは ミュータブル。p.x = 10 のように書き換えられます。
Data(Ruby 3.2+)
Structのイミュータブル版。
Point = Data.define(:x, :y)
p = Point.new(x: 3, y: 4)
p.x # 3
p.x = 10 # NoMethodError(イミュータブル)
p.with(x: 10) # 新しいインスタンスを作る
Data は イミュータブルなレコード型として導入されました。Pythonの @dataclass(frozen=True) やScalaのcase classに相当。
使い分け
ミュータブルが欲しい / 古い書き方互換: Struct
イミュータブルが欲しい / 新規コード: Data(3.2+)
複雑なロジックを持つ: 普通のクラス
8-7. このセクションのまとめ
クラス定義:
class Foo; end / def initialize / Foo.new
クラスメソッドはdef self.method_nameまたはclass << selfブロック
オープンクラス:
既存クラスを後から拡張可能(モンキーパッチ)
強力だが乱用注意、RefinementsやActiveSupport流儀で
継承:
単一継承(複数はModuleで実現)
super: 引数省略は現在の引数をそのまま渡す、super() は引数なし
selfの4文脈:
トップレベル / インスタンスメソッド / クラス本体 / class << self
freeze:
オブジェクトを変更不可に
frozen_string_literal: trueマジックコメント推奨
データクラス:
Struct(ミュータブル)/ Data(イミュータブル、3.2+)
次のセクションでは、Rubyのもう一つの核――モジュールとミックスイン――を扱います。
9. モジュールとミックスイン
Rubyの単一継承を補うのが モジュール(Module)。include/extend/prepend でクラスにメソッドを「混ぜ込む(mixin)」仕組みは、Rubyの構造化プログラミングの中核です。
9-1. モジュールの定義
module Greetable
def greet
"hello, #{name}"
end
end
モジュールは 直接インスタンス化できない点がクラスと違います(Module.new は別の話)。「機能の集まり」「名前空間」として使います。
9-2. include / extend / prepend
モジュールをクラスに混ぜ込む方法は3種類。
include(インスタンスメソッドとして混ぜ込む)
module Greetable
def greet; "hello, #{name}"; end
end
class User
include Greetable
attr_reader :name
def initialize(name); @name = name; end
end
User.new("Alice").greet # "hello, Alice"
include で混ぜ込まれたメソッドは、そのクラスのインスタンスメソッドとして使えます。
extend(特定オブジェクトのクラスメソッドとして混ぜ込む)
module Greetable
def greet; "hello, #{name}"; end
end
class User
extend Greetable # クラスメソッドとして混ぜる
def self.name; "Class"; end
end
User.greet # "hello, Class"
# または個別オブジェクトにextend
obj = Object.new
obj.extend(Greetable)
def obj.name; "obj"; end
obj.greet # "hello, obj"
prepend(includeだが、メソッド探索順で前に来る)
module Logger
def greet
puts "calling greet"
super
end
end
class User
prepend Logger
def greet; "hello"; end
end
User.new.greet
# "calling greet"
# "hello"
prepend はRuby 2.0で追加された機能で、「superで元のクラスのメソッドを呼べる前置モジュール」。alias_method_chain のような複雑なメタプロが要らなくなりました。
違いをまとめる
| 操作 | 意味 |
|---|---|
include M |
Mのメソッドをインスタンスメソッドとして混ぜる(クラスの後ろに追加) |
extend M |
Mのメソッドをクラスメソッド(または個別オブジェクトのメソッド)に |
prepend M |
Mのメソッドをインスタンスメソッドに(クラスの前に追加) |
9-3. メソッド探索順(ancestors)
クラスとモジュールが入り組むと、「同じ名前のメソッドが複数あるとき、どれが呼ばれるか?」 が問題になります。Rubyはこれを ancestors(先祖)チェーンで解決します。
class User
prepend M1
include M2
end
User.ancestors
# [M1, User, M2, Object, Kernel, BasicObject]
メソッドは 左から順に探索され、最初に見つかったものが使われます。
M1 → User → M2 → Object → Kernel → BasicObject
(prepend) (include)
prependは本体の前、includeは本体の後ろ
superで次の祖先のメソッドを呼べる
実例
module M1
def speak; "M1: " + super; end
end
module M2
def speak; "M2: " + super; end
end
class A
def speak; "A"; end
end
class B < A
prepend M1
include M2
end
B.new.speak
# "M1: M2: A"
# 探索順: M1 → B → M2 → A → Object → ...
ancestorsを覚えると謎が解ける
メソッド探索の謎は SomeClass.ancestors を見ると即座に解ける。「何でこのメソッドが呼ばれているの?」という疑問は、ancestorsを見て探索順を確認するのが基本デバッグ手順。
9-4. ComparableとEnumerable
標準ライブラリには「メソッドを1つ実装すれば大量のメソッドが手に入る」便利モジュールがあります。これはRubyのミックスイン文化の象徴。
Comparable
<=>(宇宙船演算子)を1つ実装すれば、<, <=, >, >=, ==, between?, clamp がもらえる。
class Score
include Comparable
attr_reader :value
def initialize(value); @value = value; end
def <=>(other); value <=> other.value; end
end
a = Score.new(10)
b = Score.new(20)
a < b # true
a.between?(Score.new(5), Score.new(15)) # true
[b, a].min # a
Enumerable
each を1つ実装すれば、map, select, reject, reduce, count, sort, min, max, take, drop, ……といった50以上のメソッドがもらえる。
class WordList
include Enumerable
def initialize(*words); @words = words; end
def each
@words.each { |w| yield w }
end
end
wl = WordList.new("apple", "banana", "cherry")
wl.map(&:upcase) # ["APPLE", "BANANA", "CHERRY"]
wl.select { |w| w.length > 5 } # ["banana", "cherry"]
wl.sort # ["apple", "banana", "cherry"]
wl.count # 3
wl.first(2) # ["apple", "banana"]
これは Rubyのミックスイン文化 の最大の恵み。Enumerable を理解すれば、ArrayやHashを使いこなせるようになります。
9-5. 名前空間としてのモジュール
モジュールはメソッド集合だけでなく、名前空間としても使われます。
module MyCompany
module Database
class Connection
...
end
end
end
conn = MyCompany::Database::Connection.new
:: で階層を辿ります(Javaの .、Pythonの . に相当)。
autoload
大規模なライブラリでは、必要になるまでクラスをロードしない仕組み。
module MyApp
autoload :User, "my_app/user"
autoload :Post, "my_app/post"
end
# MyApp::Userが初めて参照されたときだけrequireされる
Railsの eager_load / lazy_load の基盤になっています。
9-6. Refinements(限定的なオープンクラス)
オープンクラスは強力すぎて副作用がグローバル。「このファイル・このクラスの中だけで効く拡張がほしい」 というニーズに応えるのが Refinements。
module StringExtensions
refine String do
def shout
upcase + "!"
end
end
end
class Greeter
using StringExtensions # このファイル・このクラスのスコープ内だけ有効
def greet(name)
name.shout
end
end
Greeter.new.greet("alice") # "ALICE!"
# 別のファイル・別のクラスからは見えない
"hello".shout # NoMethodError(Refinementsの外)
Ruby 2.0で導入されました。実用上はあまり使われない(書き方が手間な割に得られる安全性が中途半端)のが現実ですが、「スコープ付きモンキーパッチ」が必要なときの選択肢として知っておく価値があります。
9-7. このセクションのまとめ
モジュール:
module M; endでメソッドや定数の集合
名前空間としても使う(::)
混ぜ込み3種:
includeインスタンスメソッドに(クラスの後ろ)
extendクラスメソッドや個別オブジェクトに
prependインスタンスメソッドに(クラスの前、superで本体を呼べる)
ancestors:
メソッド探索順を一覧表示
「なぜこのメソッドが?」のデバッグに必須
標準モジュール:
Comparable: <=> を実装すれば6つの比較メソッドが揃う
Enumerable: eachを実装すれば50+のメソッドが揃う
Refinements:
スコープ限定のオープンクラス
using ModuleNameでファイル/クラス内だけ有効
使用頻度は高くないが、スコープ付き拡張の選択肢
次のセクションでは、Rubyのもう一つの真髄――メタプログラミング――を扱います。
10. メタプログラミング
Rubyのメタプログラミング能力は 業界最高クラス。Rails、RSpec、Bundlerのような魔法めいたDSLは、すべてメタプログラミングで実現されています。このセクションではオブジェクトモデル、特異クラス、method_missing、define_method、instance_eval を体系的に整理します。
10-1. オブジェクトモデルと特異クラス
Rubyのオブジェクトモデルを正確に理解すると、メソッド探索の挙動がすべて説明できます。
すべてのオブジェクトには「クラス」と「特異クラス」がある
+---------+
| Class |
+---------+
^
+---------------+---------------+
| |
+----------+ +-------------+
| Object |←───── superclass ──┤ Module |
+----------+ +-------------+
^
|
+----------+
| User | ← クラス
+----------+
^
| (instance_of)
|
+----------+
| alice | ← インスタンス
+----------+
そして、各オブジェクトは「特異クラス(singleton class)」を持つ。これは「そのオブジェクトだけが持つ、暗黙のクラス」です。
class A; end
a = A.new
# A: aの通常クラス
# aの特異クラス: aだけが持つ匿名クラス
singleton_class = a.singleton_class
def a.special # aの特異メソッド(aだけが持つ)
"I'm special"
end
a.special # "I'm special"
A.new.special # NoMethodError(他のAインスタンスは持たない)
def obj.method_name という書き方は、そのオブジェクトの特異クラスにメソッドを定義する糖衣構文です。
クラスメソッドの正体
def self.foo は「クラスオブジェクト自身の特異メソッド」。
class A
def self.cls_method # Aの特異クラスにメソッドを定義
"I am A"
end
end
A.cls_method # "I am A"
A.new.cls_method # NoMethodError(インスタンスは持たない)
# 同等に書ける
class A
class << self # Aの特異クラスを開く
def cls_method
"I am A"
end
end
end
つまり「クラスメソッドはクラスオブジェクトの特異メソッド」。Rubyのオブジェクトモデルが一貫していることが分かります。
10-2. method_missing
オブジェクトが未定義のメソッドを呼ばれたとき、method_missing がフックされます。
class GhostMethods
def method_missing(name, *args, &block)
"called #{name} with #{args.inspect}"
end
def respond_to_missing?(name, include_private = false)
true # respond_to? を整合させる
end
end
g = GhostMethods.new
g.foo(1, 2) # "called foo with [1, 2]"
g.bar # "called bar with []"
g.respond_to?(:anything) # true
Railsの例
User.find_by_name_and_email("Alice", "...") のような動的メソッドは、method_missing で実現されています。実在しないメソッド名を解析して、内部でSQLを組み立てる仕組み。
method_missingの落とし穴
- デバッグが困難: タイポしたメソッドが「魔法のように動いてしまう」
- respond_to? が嘘になる:
respond_to_missing?を必ずペアで実装する - パフォーマンス: 通常のメソッド呼び出しより遅い
- 継承で動かないことがある:
superの使い方に注意
現代のRubyでは method_missing の代わりに define_method で動的にメソッドを「実体化」することが推奨。後者のほうがデバッグしやすく速いです。
10-3. define_method
method_missing のように呼ばれたときに反応するのではなく、事前にメソッドを動的に定義する方法。
class Configurable
[:host, :port, :user, :password].each do |attr|
define_method(attr) do
@config[attr]
end
define_method("#{attr}=") do |value|
@config[attr] = value
end
end
def initialize
@config = {}
end
end
c = Configurable.new
c.host = "example.com"
c.host # "example.com"
attr_accessor の本質はこの define_method。
attr_accessorの自前実装
class Module
def my_attr_accessor(*names)
names.each do |name|
define_method(name) { instance_variable_get("@#{name}") }
define_method("#{name}=") { |val| instance_variable_set("@#{name}", val) }
end
end
end
class User
my_attr_accessor :name, :age
end
「Rubyは標準ライブラリの大半が、ユーザコードと同じ仕組みで作られている」という事実が見えます。
10-4. send / public_send
send は メソッド名を文字列・Symbolで指定して呼び出すメソッド。
"hello".send(:upcase) # "HELLO"
[1, 2, 3].send(:sum) # 6
# 動的なメソッド呼び出し
method_name = :upcase
"hello".send(method_name) # "HELLO"
sendはprivateも呼べる
class A
private
def secret; "hidden"; end
end
A.new.secret # NoMethodError
A.new.send(:secret) # "hidden"(privateを貫通する)
これはセキュリティ上の問題になりかねないので、public_send を使うと privateは呼べない。
A.new.public_send(:secret) # NoMethodError
外部入力を元にメソッドを呼ぶときは 必ず public_send を使う、が鉄則です。
10-5. instance_eval / class_eval
instance_eval
ブロックを 特定オブジェクトの文脈で評価する。self がそのオブジェクトに切り替わります。
class Bottle
def initialize; @amount = 100; end
end
b = Bottle.new
b.instance_eval do
@amount # ブロック内ではself = bなので、bの @amountにアクセス可
end
# 100
これにより、通常はアクセスできないprivate状態にも触れられる。テストで内部状態を確認するときに重宝。
class_eval
クラスの文脈でブロックを評価する。def でメソッドを動的に追加できる。
String.class_eval do
def shout
upcase + "!"
end
end
"hello".shout # "HELLO!"
class String; ... end を文字列で書く版とも言えます。動的にクラスを開いて拡張するのに使います。
DSLの作り方
instance_eval は DSL(ドメイン特化言語)構築の核。Rake、RSpec、Sinatraなどはこれで実現されています。
# 自作DSLの例
class Configuration
attr_accessor :host, :port
def self.configure(&block)
config = new
config.instance_eval(&block) # block内でself = configになる
config
end
end
Configuration.configure do
self.host = "example.com"
self.port = 8080
end
# RSpec風のDSLもこれで実現される
10-6. const_missing / TracePoint
const_missing
未定義の定数が参照されたときのフック。
class Object
def self.const_missing(name)
puts "Constant #{name} not defined"
super
end
end
UndefinedConstant # "Constant UndefinedConstant not defined" → NameError
Railsの autoload(User を最初に参照したときに app/models/user.rb を自動requireする)はこれで実現されています。
TracePoint
実行中のイベントを観察できる強力なフック。
trace = TracePoint.new(:call) do |tp|
puts "called #{tp.method_id} in #{tp.path}:#{tp.lineno}"
end
trace.enable
[1, 2, 3].map { |x| x * 2 }
# called map in ...
デバッガ・プロファイラ・テスト計測などの基盤として使われます。
10-7. 落とし穴と現実的な指針
メタプログラミングは強力ですが、諸刃の剣。
よくある落とし穴
- タイポを許容してしまう:
method_missingで「未定義メソッドが何でも呼べる」状態を作るとバグの温床 - デバッグが困難: スタックトレースが意味不明になりがち
- 静的解析が効かない: IDEの補完・型チェッカが効かない
- パフォーマンス: 動的呼び出しは静的より遅い
- 可読性: 「魔法」が多すぎるコードはチームに辛い
現実的な指針
1. まず「メタプロが本当に必要か?」を問う。多くの場合、普通のメソッドで足りる
2. 使うならdefine_methodをmethod_missingより優先(デバッグしやすい)
3. sendよりpublic_send(セキュリティ)
4. instance_evalはDSL構築の用途に限定
5. 自分で書いたコードを「半年後の自分が読める」かをチェック
6. 標準ライブラリ・著名gemを参考にイディオムを学ぶ
メタプロは「書く快楽」と「読む苦痛」のトレードオフ。チームでの使用基準を決めておくのが賢い。
10-8. このセクションのまとめ
オブジェクトモデル:
すべてはオブジェクト、各オブジェクトは「特異クラス」を持つ
def obj.method_nameは特異メソッド
def self.method_nameはクラスオブジェクトの特異メソッド
class << selfは特異クラスを開く
method_missing:
未定義メソッド呼び出しのフック
respond_to_missing? とペアで実装
デバッグ困難、なるべくdefine_methodで代替
define_method:
事前にメソッドを動的定義(attr_accessorの本質)
ループで大量のアクセサを生成可能
send / public_send:
メソッドを文字列・Symbolで呼ぶ
sendはprivateも貫通、外部入力にはpublic_send
instance_eval / class_eval:
ブロックを別オブジェクトの文脈で評価
DSL構築の核(Rake, RSpec, Sinatra)
const_missing / TracePoint:
定数参照や実行イベントのフック
Rails autoload、デバッガなどの基盤
指針:
まず「本当に必要か」を問う
define_method > method_missing
public_send > send
半年後の自分が読めるかをチェック
次のセクションでは、Rubyの充実したコレクション操作――Array・Hash・Enumerableの世界――を扱います。
11. コレクション操作
Rubyのコレクションは Enumerable モジュールを中核に、極めて表現力豊かなメソッド群が揃います。Pythonの内包表記より直感的なメソッドチェーンで、複雑なデータ操作を1行で書けます。
11-1. Array
a = [1, 2, 3]
a[0] # 1
a[-1] # 3(負のインデックスは末尾から)
a[1..] # [2, 3](範囲スライス)
a[1, 2] # [2, 3](開始, 長さ)
a.first # 1
a.last # 3
a.length / a.size # 3
a << 4 # 末尾追加(破壊的)
a.push(5) # 同じ
a.unshift(0) # 先頭追加
a.pop # 末尾を取り出す
a.shift # 先頭を取り出す
a.include?(2) # true
a.index(2) # 1
配列の生成
Array.new(5) # [nil, nil, nil, nil, nil]
Array.new(5, 0) # [0, 0, 0, 0, 0]
Array.new(5) { |i| i * 2 } # [0, 2, 4, 6, 8]
(1..5).to_a # [1, 2, 3, 4, 5]
[1, 2] * 3 # [1, 2, 1, 2, 1, 2]
%w[a b c] # ["a", "b", "c"]
%i[a b c] # [:a, :b, :c]
配列の変形
[3, 1, 2].sort # [1, 2, 3]
[3, 1, 2].sort.reverse # [3, 2, 1]
[1, 1, 2, 3].uniq # [1, 2, 3]
[1, 2, 3].rotate(1) # [2, 3, 1]
[1, 2, [3, [4]]].flatten # [1, 2, 3, 4]
[1, 2, [3, [4]]].flatten(1) # [1, 2, 3, [4]]
[1, 2, 3].zip([4, 5, 6]) # [[1, 4], [2, 5], [3, 6]]
[1, 2, 3].each_slice(2).to_a # [[1, 2], [3]]
[1, 2, 3].each_cons(2).to_a # [[1, 2], [2, 3]]
集合演算
[1, 2, 3] & [2, 3, 4] # [2, 3](積)
[1, 2, 3] | [2, 3, 4] # [1, 2, 3, 4](和)
[1, 2, 3] - [2] # [1, 3](差)
11-2. Hash
h = { a: 1, b: 2 }
h[:a] # 1
h[:c] # nil(KeyErrorではない)
h.fetch(:c) # KeyError
h.fetch(:c, 0) # 0(デフォルト値)
h[:c] = 3 # 追加・更新
h.delete(:a) # 削除
h.size # 3
h.keys # [:b, :c]
h.values # [2, 3]
Hashの挙動
RubyのHashは 挿入順を保持(Pythonと同じ)。Ruby 1.9から保証されています。
h = { a: 1, b: 2, c: 3 }
h.each { |k, v| puts "#{k}=#{v}" }
# a=1
# b=2
# c=3(挿入順)
マージと変形
{ a: 1, b: 2 }.merge(b: 20, c: 3) # { a: 1, b: 20, c: 3 }
{ a: 1, b: 2 }.transform_values { |v| v * 10 } # { a: 10, b: 20 }
{ a: 1, b: 2 }.transform_keys(&:to_s) # { "a" => 1, "b" => 2 }
{ a: 1, b: 2 }.invert # { 1 => :a, 2 => :b }
{ a: 1, b: 2, c: 3 }.except(:b) # { a: 1, c: 3 }(3.4+ 標準化)
{ a: 1, b: 2, c: 3 }.slice(:a, :b) # { a: 1, b: 2 }
デフォルト値
# 単純なデフォルト
h = Hash.new(0)
h[:foo] += 1 # 0 + 1
h # {:foo=>1}
# ブロックでデフォルトを動的に
h = Hash.new { |hash, key| hash[key] = [] }
h[:fruits] << "apple"
h[:fruits] << "banana"
h # { fruits: ["apple", "banana"] }
これはPythonの defaultdict 相当の機能です。
11-3. Range
1..10 # 1..10(10を含む)
1...10 # 1..9(10を含まない)
'a'..'z' # 文字も範囲にできる
1..Float::INFINITY # 無限範囲
(..10) # 始端なし範囲(2.6+)
(1..) # 終端なし範囲(2.6+)
Rangeの用途
(1..5).to_a # [1, 2, 3, 4, 5]
(1..5).each { |i| ... } # 反復
(1..100).sum # 5050
(1..100).step(2).to_a # [1, 3, 5, ..., 99]
# 配列スライス
arr[1..3]
arr[1...3]
arr[2..] # 2以降全部
# caseで
case score
when 90..100 then "A"
when 80...90 then "B"
end
11-4. Set
Set は重複なしのコレクション。Arrayより要素検索が圧倒的に速い。
require "set"
s = Set.new([1, 2, 3])
s.add(4)
s.include?(2) # true
s.merge([5, 6]) # 別のSet/Enumerableを追加
a = Set.new([1, 2, 3])
b = Set.new([2, 3, 4])
a & b # 積
a | b # 和
a - b # 差
Ruby 3.2以降、Set は 標準でrequire不要になりました。
11-5. Enumerableの世界
Rubyの最強の特徴は Enumerable モジュール。each を実装するだけで、50以上のメソッドが揃います。
主要メソッド一覧
| カテゴリ | メソッド |
|---|---|
| 変換 | map, flat_map, collect, to_a, to_h |
| 絞り込み | select, filter, reject, find, detect, take_while, drop_while, grep, grep_v |
| 集約 | reduce, inject, sum, count, min, max, min_by, max_by, minmax |
| 順序 | sort, sort_by, reverse_each |
| グループ化 | group_by, partition, chunk, chunk_while, slice_when, tally |
| 検査 | any?, all?, none?, one?, include? |
| 走査 | each, each_with_index, each_with_object, each_slice, each_cons, zip |
| 取り出し | first, take, drop, lazy |
これらはArray, Hash, Range, Set, 自作クラス全てに等しく使えるのがRubyの強み。
11-6. map・select・reduce
map(変換)
[1, 2, 3].map { |x| x * 2 } # [2, 4, 6]
[1, 2, 3].map(&:to_s) # ["1", "2", "3"]
# Hashでも使える
{ a: 1, b: 2 }.map { |k, v| [k, v * 10] }.to_h # { a: 10, b: 20 }
select / reject(絞り込み)
[1, 2, 3, 4].select { |x| x.even? } # [2, 4]
[1, 2, 3, 4].reject { |x| x.even? } # [1, 3]
[1, 2, 3, 4].filter_map { |x| x * 2 if x.even? } # [4, 8]
filter_map は mapとselectを1度で行う、3.0+ で人気のメソッド。
reduce(畳み込み)
[1, 2, 3, 4].reduce(0) { |sum, x| sum + x } # 10
[1, 2, 3, 4].reduce(:+) # 10(Symbolで簡潔に)
[1, 2, 3, 4].sum # 10(さらに簡潔)
# 文字列の連結
%w[a b c].reduce("") { |acc, s| acc + s } # "abc"
%w[a b c].join # "abc"
# 最大値を求める
[3, 1, 4, 1, 5].reduce { |a, b| a > b ? a : b } # 5
[3, 1, 4, 1, 5].max # 5
group_by / tally
%w[apple banana cherry].group_by { |s| s[0] }
# { "a" => ["apple"], "b" => ["banana"], "c" => ["cherry"] }
%w[a b c a a b].tally # { "a" => 3, "b" => 2, "c" => 1 }
tally は Counter相当で2.7+。
partition
[1, 2, 3, 4].partition { |x| x.even? }
# [[2, 4], [1, 3]]
each_with_object
[1, 2, 3].each_with_object({}) do |x, hash|
hash[x] = x * 10
end
# { 1 => 10, 2 => 20, 3 => 30 }
reduce と似ていますが、毎回オブジェクトを返さなくて良いので可読性が高い。
11-7. lazy(遅延評価)
RubyのEnumerableは通常 即時評価ですが、lazy を挟むと遅延評価になります。
# 即時評価: 100万要素の中間配列を作るので遅い
(1..Float::INFINITY).map { |x| x * 2 }.first(5)
# 無限ループに陥る
# 遅延評価: 必要な分だけ計算
(1..Float::INFINITY).lazy.map { |x| x * 2 }.first(5)
# [2, 4, 6, 8, 10]
(1..Float::INFINITY).lazy.select { |x| x.even? }.first(3)
# [2, 4, 6]
無限ストリーム・大規模データ処理の必須機能。
11-8. このセクションのまとめ
Array:
リテラル [1, 2, 3] / %w[...] / %i[...]
push/<<・pop・shift・unshift
集合演算 &/|/-、each_slice / each_cons / zip
Hash:
挿入順保持(1.9+)
fetchでデフォルト指定、Hash.new { ... } で動的デフォルト
merge / transform_values / except / slice
Range:
1..10(含む)/ 1...10(含まない)
始端なし (..10)、終端なし (1..)
case / 配列スライス / eachで使える
Set:
重複なし、検索が速い
3.2+ でrequire不要
Enumerable:
eachを実装すれば50+メソッドが手に入る
map / select / reduce / group_by / tally / partition / each_with_object
filter_map(3.0+)、tally(2.7+)
lazy:
遅延評価。無限ストリーム・大規模データに必須
次のセクションでは、Rubyの文字列・正規表現を扱います。
12. 文字列・正規表現
Rubyの文字列は ミュータブルで、リテラルの種類が豊富。Perl譲りの 正規表現リテラルもスクリプト言語ならではの表現力を提供します。
12-1. 文字列リテラル
"hello" # 二重引用符(補間あり、エスケープあり)
'hello' # 一重引用符(補間なし、エスケープ最小限)
"hi\nthere" # 改行
'hi\nthere' # \nは文字列のまま(補間されない)
"3 + 4 = #{3 + 4}" # 補間 "3 + 4 = 7"
'3 + 4 = #{3 + 4}' # そのまま "3 + 4 = #{3 + 4}"
% 記法
%(hello) # "hello"(() を使うので " " を中で書ける)
%[hello] # "hello"([] でも)
%{hello} # "hello"({} でも)
%Q{double quoted} # 二重引用符相当
%q{single quoted} # 一重引用符相当
% は **「区切り文字を任意に選べる」**ので、文字列に ' や " を含むときに重宝。
12-2. ヒアドキュメント
複数行の文字列リテラル。
text = <<~HEREDOC
これは
複数行の
文字列です
HEREDOC
# <<~ はインデントを揃えてくれる(Ruby 2.3+)
# <<- は終端のインデントを許すが、内容のインデントは保たれる
# << は両方ともインデントを許さない(古典的)
補間とリテラル
name = "Alice"
<<~TEXT # 補間あり("" 相当)
hello, #{name}
TEXT
<<~'TEXT' # 補間なし('' 相当)
hello, #{name}
TEXT
シングルクォート版(<<~'TEXT')は 補間されない。SQLやJSONテンプレートなどで # を使いたいときに役立ちます。
12-3. 文字列補間と %w / %i
文字列補間
name = "Alice"
age = 30
"hi #{name}" # "hi Alice"
"#{name} is #{age} years old"
"sum: #{1 + 2}"
"object inspection: #{obj.inspect}"
# format風
"%s is %d" % [name, age]
format("%s is %d", name, age)
%w(単語配列)
%w[apple banana cherry]
# 同等
["apple", "banana", "cherry"]
スペース区切りで配列を作れる。空白の入る要素は使えない。
%i(Symbol配列)
%i[name age email]
# 同等
[:name, :age, :email]
RailsのDSL(attr_accessor :a, :b, :c など)で %i をよく見ます。
12-4. 正規表現
RubyはPerlの影響を受けて、正規表現が言語のファーストクラスとして組み込まれています。
re = /hello/
"hello world" =~ re # 0(マッチ位置)
"hi" =~ re # nil(マッチしない)
"hello" =~ /(\w+)/
$~ # MatchData
$1 # "hello"(最初のキャプチャ)
マッチ操作
"hello".match(/(\w+)/) # MatchData
"hello".match?(/\w+/) # true(マッチするかだけ、$~ を変更しない)
"a, b, c".scan(/\w+/) # ["a", "b", "c"](全マッチ)
"a, b, c".split(/,\s*/) # ["a", "b", "c"]
"hello".sub(/l/, "L") # "heLlo"(最初の1つを置換)
"hello".gsub(/l/, "L") # "heLLo"(全部置換)
caseで正規表現
case input
when /^\d+$/ then "数値"
when /^\w+@/ then "メール"
else "その他"
end
=== で正規表現マッチが定義されているので、caseで自然に書ける(4-3)。
よく使うフラグ
/pattern/i # 大文字小文字を区別しない
/pattern/m # 複数行モード(. が改行にマッチ)
/pattern/x # 拡張モード(コメントとスペースを許可)
# 拡張モードで読みやすく
EMAIL_REGEX = /
\A # 文字列の先頭
[\w.+-]+ # ローカル部
@ # @
[\w-]+ # ドメイン
(?:\.[\w-]+)+ # ドメイン .xxx部
\z # 文字列の末尾
/x
名前付きキャプチャ
re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
m = re.match("2024-04-28")
m[:year] # "2024"
m[:month] # "04"
12-5. エンコーディング(マジックコメント)
Rubyのソースファイルは UTF-8がデフォルトですが、明示することもできます。
# encoding: utf-8
# frozen_string_literal: true
これらは「マジックコメント」と呼ばれ、ファイル先頭に書きます。
文字列のエンコーディング
s = "こんにちは"
s.encoding # #<Encoding:UTF-8>
s.bytesize # 15(UTF-8で日本語は1文字3バイト)
s.length # 5(文字数)
s.bytes # [227, 129, 147, ...](バイト列)
s.encode("Shift_JIS") # 別エンコーディングへ変換
バイト列と文字列
"hello".bytes # [104, 101, 108, 108, 111]
"hello".each_char { |c| puts c }
"hello".each_byte { |b| puts b }
Rubyは StringとBytesが同じクラスで、エンコーディング情報を文字列が持つ設計(M17N: Multi-1-7 Nationalization)。これはRubyの独特な特徴で、Pythonの str/bytes 二分法とは違います。
12-6. このセクションのまとめ
リテラル:
"" 補間あり、'' 補間なし
% 記法(%(), %{} など)で区切り文字を選択
%w[], %i[] で配列を簡潔に
ヒアドキュメント:
<<~TEXTで書けば内容のインデントを揃えてくれる(2.3+)
シングルクォート(<<~'TEXT')は補間なし
文字列補間:
"name: #{name}" / "%s" % [...] / format(...)
正規表現:
/pattern/ がリテラル
=~, match, match?, scan, split, sub, gsub
フラグ: /i大小区別なし、/m複数行、/x拡張モード
名前付きキャプチャ (?<name>...)
エンコーディング:
UTF-8デフォルト
Stringはバイト列とエンコーディング情報の組
encoding: utf-8、frozen_string_literal: trueマジックコメント
次のセクションでは、Rubyの例外処理を扱います。
13. 例外処理
Rubyの例外処理は begin/rescue/else/ensure で構成されます。raise from のような明示的なチェインはなく、シンプルな仕組み。retry、修飾子 rescue といったRuby独特の機能があります。
13-1. begin / rescue / else / ensure
begin
risky_operation
rescue ArgumentError => e
puts "ArgumentError: #{e.message}"
rescue StandardError => e
puts "StandardError: #{e.message}"
else
puts "例外が起きなかった場合"
ensure
puts "例外の有無にかかわらず必ず実行"
end
| 節 | 役割 |
|---|---|
begin |
監視するブロックの開始 |
rescue |
例外をキャッチ。複数書けば最初にマッチしたもの |
else |
例外が起きなかったときだけ |
ensure |
例外の有無にかかわらず最後に必ず |
メソッド全体をbegin相当にする
メソッド本体全体は 暗黙のbegin とみなされます。begin を省略可能。
def safe_divide(a, b)
a / b
rescue ZeroDivisionError
nil
ensure
log("divide called")
end
これはRubyらしい簡潔さで、よく使われるイディオムです。
13-2. 例外の階層
Exception
├── SystemExit exit() で発生
├── SignalException SIGINTなどのシグナル
│ └── Interrupt Ctrl+C
├── NoMemoryErrorメモリ枯渇
├── ScriptErrorロード関連
│ ├── LoadError require失敗
│ └── SyntaxError
└── StandardError ← ユーザコードが捕まえるべき祖先
├── ArgumentError
├── IOError
│ └── EOFError
├── NameError
│ └── NoMethodError
├── RangeError
├── RuntimeError raise "msg" のデフォルト
├── TypeError
├── ZeroDivisionError
└── ...
rescueのデフォルト
begin
...
rescue => e # クラス省略 = StandardError相当
...
end
rescue だけ書くと StandardError 系のみキャッチします。Exception をキャッチしたいときは明示的に書く必要があります。
rescueでExceptionを捕まえてはいけない
# Bad: Ctrl+Cも握り潰す
begin
long_running
rescue Exception
...
end
# Good
begin
long_running
rescue StandardError
...
end
Exception は SystemExit や Interrupt(Ctrl+C)まで含むため、握り潰すとプロセスが止められなくなります。
13-3. raiseと例外の再送出
raise
raise "error message" # RuntimeError("error message")
raise ArgumentError, "invalid arg" # ArgumentError
raise ArgumentError.new("msg") # 同等
# rescue内で再送出
begin
...
rescue StandardError => e
log(e)
raise # 元の例外をそのまま再送出
end
引数なしの raise は 「現在処理中の例外を再送出」 という意味。これがRubyの例外チェインの基本。
Rubyの例外チェイン(cause)
Ruby 2.1+ では、rescue内でraiseすると元の例外が cause として自動的にぶら下がります。
begin
begin
raise "inner"
rescue => e
raise "outer"
end
rescue => e
puts e.message # "outer"
puts e.cause.message # "inner"
end
Pythonの raise X from Y のような明示的構文はなく、自動でチェインされるのがRuby流。
13-4. 修飾子rescue・retry
修飾子rescue
result = risky_method rescue nil # 例外が起きたらnil
簡潔ですが、何の例外をキャッチするか不明確になるので推奨されません。明示的な begin/rescue を使うほうがよい。
retry
attempts = 0
begin
attempts += 1
fragile_api_call
rescue NetworkError
retry if attempts < 3 # rescue節の中でretryするとbeginから再実行
end
retry は begin から再度実行します。リトライが必要なAPI呼び出しで重宝。ただし無限ループに注意。
13-5. カスタム例外の設計
class ParseError < StandardError; end
class NetworkError < StandardError; end
class TimeoutError < NetworkError; end
StandardError を継承するのが基本(その他のものを継承するのは特殊な場合のみ)。
モジュール内に階層を作る
ライブラリでは自前の基底例外を作ると、利用者が rescue MyLib::Error で全部キャッチできて便利。
module MyLib
class Error < StandardError; end
class ConfigError < Error; end
class NetworkError < Error; end
end
begin
...
rescue MyLib::Error => e
# ライブラリ全体の例外を一括キャッチ
end
例外に情報を持たせる
class HTTPError < StandardError
attr_reader :status
def initialize(status, message)
super(message)
@status = status
end
end
begin
raise HTTPError.new(404, "Not Found")
rescue HTTPError => e
puts "#{e.status}: #{e.message}"
end
13-6. このセクションのまとめ
基本構文:
begin / rescue / else / ensure
メソッド本体は暗黙のbegin
rescueだけはStandardErrorのみキャッチ
Exceptionはキャッチしない(Ctrl+C握り潰し)
raise:
raise "msg" → RuntimeError
raise ArgumentError, "msg"
rescue内の引数なしraiseは「元の例外を再送出」
例外チェイン:
rescue内でraiseすると自動でcauseにぶら下がる
Pythonのraise fromのような明示構文は無い
retry:
rescue節内で書くとbeginから再実行
リトライ処理に有効、無限ループに注意
カスタム例外:
StandardErrorを継承するのが基本
ライブラリ内に共通のError基底クラスを作る
attr_readerでメタ情報を持たせる
次のセクションでは、Rubyの並行・並列処理――Thread / Fiber / Ractor――を扱います。
14. 並行・並列処理(Thread / Fiber / Ractor)
Rubyの並行処理は 「Thread / Fiber / Ractor」 の3層に整理されます。第2章で触れたGVLの制約を踏まえて、それぞれの得意分野と使いどころを解説します。
14-1. 並行vs並列、IOバウンドvsCPUバウンド
並行(concurrency): 複数のタスクを「進行中」にする
並列(parallelism): 複数のタスクを「物理的に同時実行」する
IOバウンド: I/O待ちが律速(ネットワーク、DB、ファイル)
CPUバウンド: 計算が律速(数値処理、画像、暗号)
Rubyでの選択
| 性質 | 推奨 |
|---|---|
| IOバウンド少量 | Thread |
| IOバウンド大量・効率重視 | Fiber + Async gem |
| CPUバウンド | Ractor(実験的)または プロセス分散 |
| 既存コードを変えずに並行化 | Thread(GVLの制約を理解した上で) |
14-2. ThreadとGVL
基本
threads = 3.times.map do |i|
Thread.new(i) do |n|
puts "thread #{n} 開始"
sleep(2)
puts "thread #{n} 終了"
end
end
threads.each(&:join)
sleep は GVLを解放する I/O系の操作なので、3スレッドが並行に動き、全体で約2秒で終わります。
CPUバウンドでは並列化されない(GVL)
# CPUバウンド: GVLのため並列化されない
threads = 4.times.map do
Thread.new do
sum = 0
10_000_000.times { |i| sum += i }
sum
end
end
threads.each(&:join)
# シングルスレッドとほぼ同じ時間
GVLの詳細は第2章2-4。Ruby 3.xの世界では、CPUバウンド並列化は Ractor または別プロセスで行うのが原則です。
Threadのライフサイクル
t = Thread.new { sleep 5; "done" }
t.alive? # true
t.status # "sleep"
t.value # "done"(join + 戻り値取得)
t.kill # 強制終了(避けるべき)
Threadの落とし穴
例外が伝搬しない
t = Thread.new do
raise "oops"
end
# t.joinしないと、例外は親スレッドに伝わらず黙って消える
t.join # ← ここで例外が再送出される
Thread.report_on_exception = true(デフォルト)にしておくと、捕まえられない例外がSTDERRに出るのでデバッグしやすい。
共有状態の競合
複数スレッドが同じデータを書き換えると、GVLがあってもデータ競合が起こる。
counter = 0
threads = 1000.times.map do
Thread.new do
100.times { counter += 1 }
end
end
threads.each(&:join)
counter # 100000とは限らない(競合する)
GVLは1命令単位の直列化を保証しますが、counter += 1 は「読み出し → 加算 → 書き込み」の3命令なので、その途中で別スレッドに切り替わる可能性があります。
14-3. Mutex / Queue
Mutex
mutex = Mutex.new
counter = 0
threads = 1000.times.map do
Thread.new do
100.times do
mutex.synchronize do
counter += 1 # クリティカルセクション
end
end
end
end
threads.each(&:join)
counter # 100000(保証される)
Queue(スレッドセーフなFIFOキュー)
queue = Queue.new
# プロデューサ
producer = Thread.new do
10.times { |i| queue.push(i); sleep 0.1 }
end
# コンシューマ
consumer = Thread.new do
while item = queue.pop
puts "got #{item}"
end
end
Pythonの queue.Queue 相当。スレッド間のデータ受け渡しは基本これを使うのが安全。
14-4. Fiber(協調的並行)
Fiber は 協調的に切り替わる軽量スレッド。OSスレッドではなく、ユーザレベルでの実行コンテキスト切り替え。
fiber = Fiber.new do
puts "A"
Fiber.yield
puts "B"
Fiber.yield
puts "C"
end
fiber.resume # "A"
fiber.resume # "B"
fiber.resume # "C"
Fiber.yield で中断、fiber.resume で再開。ジェネレータとしても使えます。
def naturals
Fiber.new do
n = 1
loop { Fiber.yield(n); n += 1 }
end
end
f = naturals
f.resume # 1
f.resume # 2
f.resume # 3
FiberとThreadの違い
| 観点 | Thread | Fiber |
|---|---|---|
| 切り替え | OSによる | プログラムが明示的に |
| メモリ | 数MB / 個 | 数KB / 個 |
| GVL | 取得が必要 | 単一スレッド上で動く |
| 数 | 数千が限界 | 数十万も可能 |
Fiberは 超軽量で、大量のI/Oを効率的に並行できる。ただし手動で切り替えを管理する必要があり、煩雑でした。これを解消するのが Fiber Scheduler。
14-5. Fiber Scheduler(3.0+)
Ruby 3.0で導入された Fiber Scheduler は、「I/O待ちのときに自動で別のFiberに切り替える」仕組みを提供します。これによりPythonの asyncio 相当のことがRubyで書けるようになりました。
require "async"
Async do
10.times do |i|
Async do
sleep(rand) # 自動的にFiberが切り替わる
puts "task #{i}"
end
end
end
# 10タスクが並行に動く
Async gem(Samuel Williams氏作)がFiber Schedulerの事実上の標準実装。Rails 7.1以降は Async を使った非同期処理がサポートされました。
IO操作との統合
Async の中では、ネットワークI/O・ファイルI/O・sleep などが自動的にFiberを譲ります。
require "async"
require "async/http"
Async do
responses = 100.times.map do |i|
Async do
Async::HTTP::Internet.get("https://example.com/#{i}")
end
end
results = responses.map(&:wait)
end
# 100リクエストが並行
これにより、Threadを多数立てるよりはるかに軽量に大量のI/Oが処理できます。
14-6. Ractor(3.0+、真の並列)
第2章2-5でも触れた Ractor。GVLを超える並列処理。
基本
r1 = Ractor.new { fib(30) }
r2 = Ractor.new { fib(30) }
r3 = Ractor.new { fib(30) }
r4 = Ractor.new { fib(30) }
[r1, r2, r3, r4].map(&:take)
各Ractorは独自のGVLを持つので、マルチコアで真に並列に動きます。
Ractor間通信
Ractorは共有メモリではなく、メッセージパッシングで通信します。
r = Ractor.new do
msg = Ractor.receive # メッセージを待つ
Ractor.yield(msg.upcase) # 結果を送り返す
end
r.send("hello")
r.take # "HELLO"
共有可能オブジェクトの制約
Ractor間で渡せるのは イミュータブルなオブジェクトだけです。
- 渡せる: Integer, Symbol, true/false/nil, frozenなString, Module/Class
- 渡せない: 通常のArray, Hash, ミュータブル文字列、Procなど
r = Ractor.new("hello") do |s|
s.upcase
end
# 内部でdeep_freezeまたはcopyが行われる
これはバグを生みにくくする設計ですが、現実のコードをRactorに対応させるのは面倒です。Railsや多くのgemがまだRactor対応していないのが普及の障害。
現実的な評価
Ractorは 「将来性は明確だが、現在は実験的」。本番投入には慎重さが要ります。型システムや共有可能性のチェックが言語レベルでさらに整備されれば、本物のマルチコアRuby時代が来るでしょう。
14-7. Async gemと実用
現代Rubyで 大規模I/O並行処理を書くなら、Async gem が事実上の標準です。
require "async"
Async do |task|
3.times do |i|
task.async do
sleep 1
puts "task #{i}"
end
end
end
Falcon Webサーバ
Async を基盤にした Falcon というWebサーバがあり、非同期Ruby Webアプリを構築できます。Railsと組み合わせれば、ASGI風の非同期Webフレームワークになります。
実用上の選択
普通のWebアプリ: Puma + ThreadでだいたいOK
大量I/O効率重視: Falcon + Async + Fiber
CPUバウンド並列: RactorまたはProcess.fork
14-8. このセクションのまとめ
3つの並行プリミティブ:
Thread: OSスレッド、GVLあり、Mutex/Queueで同期
Fiber: 軽量・協調的、Fiber Schedulerで自動切り替え
Ractor: GVLを超える真の並列、共有制約あり(実験的)
GVLのおさらい:
CPUバウンド: Threadでは並列化されない → Ractorまたはプロセス
IOバウンド: Thread / Fiberで並行化可能
Mutex / Queue:
共有状態の保護に必須
Queueはスレッド間データ受け渡しの基本
Fiber Scheduler(3.0+):
Async gemが事実上の標準
自動的にI/O待ちでFiber切り替え
数万の並行タスクが可能
Ractor(3.0+):
各Ractorが独自GVLを持つ
メッセージパッシング、共有可能オブジェクトに制約
Ruby 3.xの段階ではまだ実験的、エコシステム対応待ち
次のセクションでは、Rubyの型システム――RBS / Steep / Sorbet / TypeProf――を扱います。
15. 型とRBS / Sorbet / Steep
Rubyの型システムはPythonとは違うアプローチで進化しています。ソースコードを変えず、別ファイル(.rbs)に型情報を書くのがRuby流。Stripe製の Sorbet はコメント形式で書く別流派。両者の状況を整理します。
15-1. なぜRubyに型が議論されるか
Rubyはもともと「書きやすさ・楽しさ重視」の言語で、型は議論の余地のないトピックでした。しかし時代が変わり、
- 大規模コードベースの保守困難: Shopify、GitHub、Stripeなど数百万行Rubyのサービスが増加
- IDE補完・リファクタリング支援の要求
- 型推論の発展: 動的言語にも段階的型付けが現実的に
これらの背景から、Ruby 3.0(2020)で公式型情報フォーマット RBS が導入されました。Matzは型システムについて慎重で、「Rubyソースに型を書きたくない」と一貫して述べています。これが「型情報は別ファイル」という独特のアプローチを生みました。
15-2. RBS(公式型情報フォーマット)
RBS(Ruby Signature) はRuby 3.0で標準化された 言語非依存の型情報フォーマット。.rbs ファイルに書きます。
基本構文
# user.rb
class User
def initialize(name, age)
@name = name
@age = age
end
def adult?
@age >= 20
end
def to_s
"#{@name} (#{@age})"
end
end
# sig/user.rbs
class User
@name: String
@age: Integer
def initialize: (String name, Integer age) -> void
def adult?: () -> bool
def to_s: () -> String
end
RBSの主な型表記
| 表記 | 意味 |
|---|---|
String, Integer, … |
具象クラス |
String? |
String または nil |
Array[String] |
文字列の配列 |
Hash[Symbol, Integer] |
SymbolキーInteger値のHash |
(Integer | String) |
Union型 |
^(Integer) -> String |
Proc/Lambdaの型 |
untyped |
型チェックを諦める(Any 相当) |
void |
返り値を使わない |
標準ライブラリのRBSは組み込み
Ruby標準ライブラリの型情報は gem_rbs_collection で公式に提供されており、bundle exec rbs collection install で導入できます。
RBSの良いところ
- 既存のRubyコードを一切変更しない
- 言語仕様としての合意(mypy/pyrightが違う形に進化したPythonと対照的)
- IDEが型情報を活用できる
課題
- 2ファイルの同期コスト:
.rbと.rbsの両方を維持する手間 - ジェネリクスの構文がやや複雑
- 実コードの広まりはまだ限定的
15-3. Steep(型チェッカ)
Steep はSoutaro Matsumoto氏(CRubyのコミッタ)が開発する RBSベースの型チェッカ。Rubyのリファレンス実装的位置付け。
gem install steep
steep init # Steepfileの生成
steep check # 型チェック実行
# Steepfile
target :app do
signature "sig"
check "lib"
end
特徴
- 構造的部分型: Rubyのダックタイピング哲学を尊重
- 段階的型付け: 一部だけ型付けする運用が可能
- エラーメッセージの分かりやすさ
LSP対応
SteepはLSPサーバとして動作し、VSCodeなどのIDEで型情報のリアルタイム表示・補完が可能です。
15-4. Sorbet(Stripe製・コメント型)
Sorbet はStripeが独自に開発した型チェッカ。RBSとは別の流派で、Rubyファイル内に直接型情報を書くアプローチ。
# typed: true
class User
extend T::Sig
sig { params(name: String, age: Integer).void }
def initialize(name, age)
@name = name
@age = age
end
sig { returns(T::Boolean) }
def adult?
@age >= 20
end
end
Sorbetの特徴
- 超高速: C++ で書かれており、巨大コードベースでも速い
# typed: ...でファイルごとの厳格度を選べる: false / true / strict / strong- runtimeチェック: コード実行時にも型チェックを行えるオプション
- Stripeの本番環境で実績: 数百万行のRubyを型管理
Sorbet vs RBS
Sorbet: 同じファイルに型を書く / 高速 / 実績あり / 構文が独特
RBS: 別ファイルに書く / 公式 / 構造的部分型 / Rubyらしい
両者の統合・歩み寄りは進んでおり、SorbetもRBSを読み込めるようになっています。Stripe出身プロジェクトならSorbet、新規・公式準拠ならRBS+Steepが選択基準になります。
15-5. TypeProf(型推論)
TypeProf は 「型情報なしのRubyコードから型を推論する」 ツール。Ruby 3.0で標準同梱されました。
typeprof user.rb
# => 推論結果をrbs形式で出力
# user.rb
class User
def initialize(name, age)
@name = name
@age = age
end
def to_s
"#{@name} (#{@age})"
end
end
User.new("alice", 30).to_s
# TypeProfの出力(rbs風)
class User
@name: String
@age: Integer
def initialize: (String name, Integer age) -> Integer
def to_s: -> String
end
完璧ではありませんが、手で書かなくても型情報のたたき台が手に入るのは大きな価値。RBSへの移行を支援するツールとして活用されています。
15-6. このセクションのまとめ
RBS(公式・3.0+):
別ファイル(.rbs)に型情報を書く
Rubyソースは一切変更しない
Matzの哲学: 「Rubyソースに型は書きたくない」
Steep:
RBSベースの型チェッカ(公式準拠)
LSP対応でIDE統合
構造的部分型でRuby哲学を保つ
Sorbet(Stripe製):
Rubyファイル内にsig DSLで型を書く
超高速、ランタイムチェックも可能
Stripeの本番で実績
TypeProf:
型推論ツール(標準同梱)
RBS移行のたたき台に有用
選択基準:
新規 + 公式準拠: RBS + Steep
Stripe系・実績重視: Sorbet
既存コードの型化: TypeProfでたたき台 → 修正
次のセクションでは、Rubyのパッケージング(gem / Bundler)を扱います。
16. パッケージング・依存管理(gem/Bundler)
Rubyのパッケージング世界は gem + Bundler という組み合わせで、Pythonよりも歴史的に整理されています。Gemfile.lock によるロックの仕組みも早くから整備されました。
16-1. gem / RubyGems
RubyGems はRubyのパッケージマネージャ。世界中のgemは rubygems.org で公開されています。
gem install rails # 最新版をインストール
gem install rails -v 7.1.0 # バージョン指定
gem list # インストール済み一覧
gem update # 全gemを更新
gem uninstall rails # アンインストール
gem cleanup # 古いバージョンを削除
gem search xxx # 検索
gem info rails # 情報表示
gem environment # gemの設定確認
gem コマンドはRuby 1.9から標準同梱で、別途インストール不要です。
16-2. Gemfile / Gemfile.lock
Bundler はプロジェクトの依存関係を管理するツール。Gemfile で指定したgemをインストールし、Gemfile.lock で正確なバージョンを固定します。
Gemfileの例
# Gemfile
source "https://rubygems.org"
ruby "3.3.0"
gem "rails", "~> 7.1"
gem "puma"
gem "pg", "~> 1.5"
group :development, :test do
gem "rspec-rails"
gem "factory_bot_rails"
gem "rubocop", require: false
end
group :production do
gem "redis"
end
バージョン指定子
gem "rails", "7.1.0" # 完全一致
gem "rails", ">= 7.1" # 7.1以上
gem "rails", "~> 7.1" # 7.1.x(マイナーは固定、パッチは動く)
gem "rails", "~> 7.1.0" # 7.1.0.x(パッチも固定)
gem "rails", "~> 7" # 7.x.x(メジャーは固定)
~> は 「悲観的バージョン演算子(pessimistic operator)」。「最後の桁だけ動かしていい」という意味で、SemVerの ^ に近い動作。Ruby文化での標準的書き方です。
Gemfile.lock
bundle install を実行すると Gemfile.lock が生成され、すべての依存(推移的依存も含む)の正確なバージョンが固定されます。
GEM
remote: https://rubygems.org/
specs:
rails (7.1.3)
actionpack (= 7.1.3)
...
Gemfile.lock を Gitにcommit し、チーム全員が同じバージョンを使えるようにするのが基本。Pythonのpip-tools / poetry.lockより長い歴史を持つ仕組みで、安定しています。
16-3. bundle install / exec
bundle install # Gemfileからgemをインストール
bundle update # gemを最新に更新(Gemfile.lockも更新)
bundle update rails # 特定gemだけ更新
bundle exec rails server # bundler経由で実行(プロジェクトのgemを使う)
bundle exec rspec
bundle exec rake db:migrate
bundle exec を付けないと、システムグローバルにインストールされた古いバージョンのgemが使われる可能性があります。プロジェクトの Gemfile.lock のバージョンで実行するには bundle exec 必須。
binstub(実行ファイルを直接呼ぶ)
bundle binstubs rspec-core # bin/ ディレクトリにrspecの実行ファイル生成
./bin/rspec # bundle execを使わずに実行できる
Railsではデフォルトで bin/rails などが生成されており、bundle exec を意識せずに使えます。
16-4. 自分のgemを作る
bundlerでひな形を生成
bundle gem mygem
cd mygem
これで以下の構造ができます。
mygem/
├── lib/
│ └── mygem.rb
├── lib/mygem/
│ └── version.rb
├── sig/
│ └── mygem.rbs
├── spec/
├── mygem.gemspec
├── Gemfile
└── README.md
gemspecの例
# mygem.gemspec
Gem::Specification.new do |spec|
spec.name = "mygem"
spec.version = Mygem::VERSION
spec.authors = ["Your Name"]
spec.email = ["you@example.com"]
spec.summary = "短い説明"
spec.description = "もう少し詳しい説明"
spec.homepage = "https://github.com/you/mygem"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.1.0"
spec.files = Dir["lib/**/*", "README.md", "LICENSE"]
spec.require_paths = ["lib"]
spec.add_dependency "rake", "~> 13.0"
spec.add_development_dependency "rspec"
end
ビルドと公開
gem build mygem.gemspec # mygem-0.1.0.gemができる
gem push mygem-0.1.0.gem # rubygems.orgに公開
公開には rubygems.org のアカウントとAPIキーが必要です。
16-5. rbenv / asdf / chruby(バージョン管理)
複数バージョンのRubyを切り替えるツール。
rbenv(人気)
brew install rbenv
rbenv install 3.3.0
rbenv install 3.2.2
rbenv global 3.3.0 # システム全体のデフォルト
rbenv local 3.2.2 # プロジェクトごとに設定(.ruby-version)
asdf(多言語対応)
Ruby以外(Node.js, Python, Erlangなど)も同じツールで管理。
asdf plugin add ruby
asdf install ruby 3.3.0
asdf global ruby 3.3.0
chruby(軽量)
rbenv のシム機構を使わず、シェル関数で実装した軽量版。
選び方
シンプル: rbenv(最も普及)
他言語と統一: asdf
パフォーマンス重視: chruby
新規開始: rbenvまたはasdfでOK
16-6. このセクションのまとめ
gem / RubyGems:
Rubyのパッケージマネージャ
rubygems.orgが公式リポジトリ
Bundler:
Gemfile + Gemfile.lockで依存固定
bundle install / bundle exec
~> は悲観的バージョン演算子(SemVerの ^ 風)
自作gem:
bundle gem mygemでひな形
gemspecに依存と情報を書く
gem build / gem pushで公開
バージョン管理:
rbenv(最も普及)
asdf(多言語対応)
chruby(軽量)
次のセクションでは、Rubyのテスト戦略を扱います。
17. テスト戦略
Rubyはテストを 言語のカルチャーとして根付かせた言語の代表格。Railsが「テスト駆動開発(TDD)」を強く推進したことで、Rubyコミュニティは「テストは書くもの」という常識を確立しました。標準はMinitest、現場の主流はRSpecです。
17-1. Minitest(標準)
Ruby 1.9以降、Minitest が標準ライブラリとして同梱されています。シンプル・高速・依存なしが売り。
# test_calculator.rb
require "minitest/autorun"
class CalculatorTest < Minitest::Test
def test_add
assert_equal 3, 1 + 2
end
def test_negative
assert_operator 0, :>, -1
end
def test_truthy
assert "hello"
refute_nil "x"
end
end
ruby test_calculator.rb
主要なアサーション
| メソッド | 意味 |
|---|---|
assert(value) |
valueがtruthyか |
refute(value) |
valueがfalsyか |
assert_equal(exp, act) |
exp == act か |
assert_includes(coll, x) |
coll.include?(x) か |
assert_raises(MyError) { ... } |
例外が発生するか |
assert_match(/re/, s) |
正規表現マッチするか |
Minitestのスタイル
Minitestには specスタイルもあります。
require "minitest/autorun"
describe Calculator do
it "adds two numbers" do
_(1 + 2).must_equal 3
end
end
xUnit風とRSpec風の両方で書ける柔軟さがあります。
17-2. RSpec(DSL)
RSpec はRuby界で最も人気のテストフレームワーク。英語に近いDSL で、テスト仕様を表現します。
# spec/calculator_spec.rb
require "calculator"
RSpec.describe Calculator do
describe "#add" do
it "adds two numbers" do
expect(Calculator.new.add(1, 2)).to eq(3)
end
context "when one number is negative" do
it "still adds correctly" do
expect(Calculator.new.add(-1, 1)).to eq(0)
end
end
end
end
bundle exec rspec
bundle exec rspec spec/calculator_spec.rb
bundle exec rspec spec/calculator_spec.rb:5 # 行指定
マッチャ
expect(value).to eq(other) # ==
expect(value).to be(other) # equal?(同一オブジェクト)
expect(value).to be_truthy
expect(value).to be_nil
expect(value).to include(:a)
expect { code }.to raise_error(MyError)
expect(value).to match(/regex/)
expect(coll).to all(be_positive)
to_not(または not_to)で否定形。
letとsubject
RSpec.describe User do
subject { User.new("Alice", 30) }
let(:adult_age) { 20 }
let(:young_user) { User.new("Bob", 10) }
it "is adult when age >= 20" do
expect(subject.adult?).to be true
end
it "is not adult when too young" do
expect(young_user.adult?).to be false
end
end
let は 遅延評価でそのitブロック内で初めて参照されたときに実行される。subject は暗黙のテスト対象。
before / after / around
before(:each) do
@user = User.new
end
before(:all) do
setup_database
end
after(:each) do
cleanup
end
shared_examples
共通テストを切り出して使い回し。
shared_examples "an enumerable" do
it { is_expected.to respond_to(:each) }
it { is_expected.to respond_to(:map) }
end
RSpec.describe Array do
it_behaves_like "an enumerable"
end
RSpecの毀誉褒貶
RSpecのDSLは 強力だが学習コストが高い。チームによってはMinitestを選ぶこともあります。Railsは Minitestをデフォルトにしていますが、現場ではRSpecが圧倒的に多い、というねじれた状況です。
17-3. fixture / factory_bot
fixture(Rails標準)
# test/fixtures/users.yml
alice:
name: Alice
age: 30
bob:
name: Bob
age: 25
def test_user
user = users(:alice)
assert_equal "Alice", user.name
end
シンプルで速いが、複雑なオブジェクト関係を表現するのが難しい。
factory_bot(モダンな代替)
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "Alice" }
age { 30 }
trait :young do
age { 10 }
end
factory :admin do
role { :admin }
end
end
end
# テストで
user = build(:user) # メモリ上のみ
user = create(:user) # DBに保存
admin = create(:admin)
young = create(:user, :young)
factory_bot は 「テスト用にオブジェクトを動的に作る」 ライブラリ。Rails開発の事実上の標準で、fixtureより柔軟。
17-4. mock / stub
外部依存(API、DB)を本物で動かしたくないときの代用品。
RSpecのmock
RSpec.describe Notifier do
it "sends email" do
mailer = double("Mailer")
expect(mailer).to receive(:send).with("hi").and_return(true)
Notifier.new(mailer).notify("hi")
end
end
allow vs expect
allow(obj).to receive(:method).and_return(value) # スタブ(呼ばれる前提なし)
expect(obj).to receive(:method).and_return(value) # モック(呼ばれることを期待)
スパイ
mailer = spy("Mailer")
Notifier.new(mailer).notify("hi")
expect(mailer).to have_received(:send).with("hi")
「先に動かして、あとで検証」のスタイル。Spyは「Then-WhenではなくWhen-Then」で書きたいときに自然です。
WebMock / VCR
外部HTTPリクエストをスタブしたいなら、
- WebMock: HTTPリクエストをモック化
- VCR: 一度本物のリクエストを記録して、次回からそれを再生
これらはRubyのテスト環境のデファクトです。
17-5. このセクションのまとめ
ツール選択:
Minitest(標準同梱、シンプル)
RSpec(DSL、現場主流)
どちらも一級、好みで選ぶ
RSpecの核:
describe / context / itでテスト構造化
expect(...).to matcherで表現的
let / subject / beforeで前準備
shared_examplesで共通化
ファクトリ:
fixture(YAML、シンプルだが硬い)
factory_bot(動的、現代の主流)
モック:
double / allow / expect / spy
WebMock / VCRで外部HTTPの再現
次のセクションでは、Rubyカルチャーを大きく形作った RailsとActiveSupportの世界 を扱います。
18. RailsとActiveSupportの世界
Rubyを語る上で Ruby on Rails を避けて通ることはできません。Rubyそのものではないが、Rubyカルチャーの大きな部分はRailsが形作ったと言って過言ではありません。このセクションはRails入門ではなく、「RailsがRubyに何をもたらしたか」「ActiveSupportの便利機能」を整理します。
18-1. RailsがRubyに与えた影響
Railsの登場(2004)
第1章でも触れたように、2004年にDHHがRuby on Railsを公開したことでRubyは世界的な人気言語になりました。Railsは 「Convention over Configuration(設定より規約)」 という強烈な思想を掲げ、Webアプリ開発を圧倒的に効率化しました。
# Railsのモデル
class User < ApplicationRecord
has_many :posts
validates :email, presence: true, uniqueness: true
end
# テーブル「users」、主キーid、created_at/updated_atが自動的に紐付く
「usersテーブルがあればUserクラスがその通りに対応する」という規約により、設定ファイルが激減。これが当時のJava EE / Springの重厚なXML設定との対比で衝撃的でした。
Railsが定着させたイディオム
# 5.minutes.ago / 1.day.from_now
# users.where(active: true).order(:name)
# user.posts.where(published: true)
このような自然言語的なコードは、RailsのDSLとActiveSupportのInteger / Time拡張で実現されています。
Rails由来の概念がRuby標準に
Rails文化から生まれて、後にRuby本体に取り込まれた機能もあります。
- Hashのショートハンド
{ name:, age: }(Ruby 3.1+) - キーワード引数の整理(Ruby 3.0)
- Integer#minutes などのActiveSupport拡張は本体には入らず、独自路線
Matzは「RubyはRailsのためだけの言語ではない」と一貫して述べていますが、現実にはRailsコミュニティがRubyの発展を強くドライブしてきた事実があります。
18-2. ActiveSupportの便利モジュール
ActiveSupport はRails内のコアライブラリで、Ruby標準ライブラリの拡張集です。Railsを使わなくても gem install activesupport で使えます。
require "active_support"
require "active_support/all"
時間・日付
5.minutes # 5分(ActiveSupport::Duration)
1.day.ago # 24時間前
3.weeks.from_now # 3週間後
Time.current # タイムゾーン考慮
Date.today.beginning_of_month
文字列
"hello world".titleize # "Hello World"
"User".pluralize # "Users"
"users".singularize # "user"
"camelCase".underscore # "camel_case"
"my_class".camelize # "MyClass"
"hello".truncate(3) # "..."(先頭3文字)
"hello".starts_with?("he") # true(標準はstart_with?)
Array / Hash
[1, 2, 3].second # 2(最初はfirst / 2番目はsecond / ... / fifthまで)
[1, 2, 3].in_groups_of(2) # [[1, 2], [3, nil]]
{ a: 1, b: 2 }.with_indifferent_access # 文字列・Symbol両方でアクセス可
{ a: 1, b: 2 }.deep_merge(b: 20)
[1, nil, 2, nil, 3].compact_blank # [1, 2, 3](空文字も除く)
Object拡張
"".blank? # true(nil/false/空文字/空配列/空ハッシュ)
"hello".present? # true(blankの逆)
"hello".presence # "hello" or nil
obj.try(:method) # nil safe method call
obj.then { |x| x * 2 } # tapの値返却版(標準にも入った)
blank? / present? はRails開発で 異常に頻出します。Ruby標準の nil? や empty? だけでは「nilでも空文字でもなく、何かが入っている」を表現しにくいため。
Concern
module SoftDeletable
extend ActiveSupport::Concern
included do
scope :active, -> { where(deleted_at: nil) }
end
class_methods do
def restore_all
...
end
end
def soft_delete
update(deleted_at: Time.current)
end
end
class User < ApplicationRecord
include SoftDeletable
end
Concern は 「includeしたときの初期化処理」「クラスメソッドとインスタンスメソッドの両方を1ファイルに」 というRails流のModule拡張パターンを綺麗に書ける仕組み。
18-3. CoC・DRY・Rails Way
Convention over Configuration(CoC)
「規約に従えば設定不要」。テーブル名・カラム名・ファイル配置などの規約を守れば、ほとんど何も書かなくても動きます。
class User → usersテーブル / app/models/user.rb
class UsersController → users_controller.rb / GET /users
Don’t Repeat Yourself(DRY)
「同じことを2箇所に書くな」。Railsのヘルパーメソッド・部分テンプレート・ConcernなどはすべてDRYを実現する仕組み。
Rails Way
「Railsの流儀」。Railsが想定する書き方に従うと最短経路で開発できる、という思想。
ただし大規模化すると 「Rails Wayから外れる必要がある」 場面が増え、Service Object・Form Object・Decoratorなどのデザインパターンが議論されます。Sandi Metzの『Practical Object-Oriented Design in Ruby』が定番の参考書。
批判と反論
Railsには批判もあります。
それでも、「速く動くプロダクトを作る」という観点ではRailsは圧倒的に強く、Shopify、GitHub、Airbnb、Stripe、Cookpadなどが今なおRailsを中核に使い続けている理由です。
18-4. このセクションのまとめ
Railsの影響:
Rubyを世界的言語にした立役者
「Convention over Configuration」「DRY」を業界標準に
Rails由来のカルチャーがRuby全体に波及
ActiveSupport:
Ruby標準ライブラリの拡張集
時間: 5.minutes.ago / 1.day.from_now
文字列: titleize / pluralize / underscore / camelize
Object: blank? / present? / try
Concern: 規約に沿ったModule拡張パターン
Rails Way:
規約に従えば最短で動く
大規模化すると別のパターンが必要に
批判もあるが今なお主流
次のセクションでは、Rubyのパフォーマンスチューニングを扱います。
19. パフォーマンス
「Rubyは遅い」と言われ続けて30年。しかしYJIT・型情報・Ractorなどの整備により、状況は劇的に変わりつつあります。プロファイリング、ベンチマーク、YJIT、メモリ最適化を整理します。
19-1. プロファイリング(StackProf)
「推測ではなく計測」。まずどこが遅いかを特定する。Ruby界では StackProf が標準的なサンプリングプロファイラ。
require "stackprof"
StackProf.run(mode: :wall, out: "stackprof.dump", raw: true) do
expensive_method
end
gem install stackprof
stackprof stackprof.dump --text
stackprof stackprof.dump --flamegraph > flame.html
mode: :cpu / :wall / :object を切り替えて、CPU時間 / 経過時間 / オブジェクト割り当てを計測できます。
rack-mini-profiler(Webアプリ向け)
Railsアプリの開発時に、ブラウザで各リクエストのプロファイル結果を確認できるgem。
ruby-prof(より詳細)
require "ruby-prof"
result = RubyProf.profile do
...
end
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
サンプリングではなく精密計測。オーバーヘッドが大きいので大規模コードではStackProf推奨。
19-2. ベンチマーク(Benchmark / benchmark-ips)
標準のBenchmark
require "benchmark"
Benchmark.bm do |x|
x.report("a") { 100_000.times { "a" + "b" } }
x.report("b") { 100_000.times { "ab" } }
end
benchmark-ips(推奨)
「1秒間に何回実行できるか(iterations per second)」を比較。
require "benchmark/ips"
Benchmark.ips do |x|
x.report("string concat") { "a" + "b" }
x.report("string interp") { "#{'a'}#{'b'}" }
x.compare!
end
string concat: 8.5M i/s
string interp: 3.2M i/s - 2.66x slower
時間ではなくiterations/secで比較するので、実行時間に依存せず信頼できます。
19-3. YJITの効果と使いどころ
第2章2-3で触れたYJIT。有効化するだけで Railsアプリで15〜20% 速くなることが多い。
RUBY_YJIT_ENABLE=1 ruby app.rb
RUBY_YJIT_ENABLE=1 rails server
# プログラム内で有効化
RubyVM::YJIT.enable
効果が出やすい場面
- メソッド呼び出しが多い(OOPコード)
- 同じ型の引数で繰り返し呼ばれる
- ホットなコードパスがある
効果が薄い場面
- 起動して数秒で終わるスクリプト(JITのウォームアップが間に合わない)
- 数値計算ばかり(YJITよりNumPy/Numoが強い)
- メタプログラミング多用(型特化が効きにくい)
統計を見る
RubyVM::YJIT.runtime_stats
実行後のYJIT統計(コンパイルされたメソッド数、ヒット率など)が取れます。
19-4. メモリ最適化
文字列の freeze でメモリ削減
# frozen_string_literal: true
s1 = "hello" # 同じリテラルは内部で共有される
s2 = "hello" # s1と同じオブジェクト
Symbol vs String
ループ内で同じ文字列を多数生成するならSymbolのほうが軽い。
# Bad: 100万個のStringオブジェクト
1_000_000.times { puts "status".upcase }
# Good: Symbolを使う(hashableで軽量)
status = :active
Arrayの効率
# Bad: 巨大配列の中間生成
huge_array.map { |x| x * 2 }.select { |x| x > 100 }
# Good: lazyで中間配列を作らない
huge_array.lazy.map { |x| x * 2 }.select { |x| x > 100 }.to_a
GC統計
GC.stat # GCの統計情報
GC.compact # メモリコンパクション
GC.count # GCが走った回数
ObjectSpace.count_objects # クラスごとのオブジェクト数
19-5. このセクションのまとめ
プロファイリング:
StackProf(サンプリング、定番)
rack-mini-profiler(Rails開発時)
ruby-prof(精密計測)
ベンチマーク:
Benchmark.bm(標準)
Benchmark.ips(推奨、iterations/sec比較)
YJIT:
RUBY_YJIT_ENABLE=1で有効化
Railsで15〜20% 高速化
起動の速いスクリプトでは効果薄
メモリ最適化:
frozen_string_literal: true
Symbolを活用
Enumerable.lazyで中間配列を回避
GC.stat / GC.compact
次のセクションでは、Ruby 3.0〜3.4の新機能を一望します。
20-A. パフォーマンス最適化の実践
Ruby のパフォーマンスボトルネックを特定・改善する手法です。
プロファイリング
require 'ruby-prof'
RubyProf.start
# あなたのコード
result = slow_method()
prof = RubyProf.stop
printer = RubyProf::FlatPrinter.new(prof)
printer.print(STDOUT)
出力:
%self total self wait child calls name
45.23 0.92 0.42 0.00 0.50 1000 Array#each
23.12 0.47 0.21 0.00 0.26 500 String#upcase
...
ホットスポット最適化
# 遅い:毎回配列を生成
def process(items)
items.map { |x| x.upcase }.select { |x| x.length > 3 }
end
# 最適化:複数操作を1パス
def process(items)
result = []
items.each do |x|
upper = x.upcase
result << upper if upper.length > 3
end
result
end
文字列操作の最適化
# 遅い:String#+ は毎回新規割当
result = ""
1000.times { |i| result = result + i.to_s }
# 高速化:<< で in-place
result = ""
1000.times { |i| result << i.to_s }
# さらに高速:StringIO
require 'stringio'
result = StringIO.new
1000.times { |i| result << i.to_s }
result.string
キャッシング戦略
# memoization(結果をキャッシュ)
class Fibonacci
def initialize
@cache = {}
end
def fib(n)
return @cache[n] if @cache[n]
return n if n <= 1
@cache[n] = fib(n-1) + fib(n-2)
end
end
fib = Fibonacci.new
fib.fib(100) # フルスキャン
fib.fib(101) # キャッシュ活用
並列処理による高速化
require 'parallel'
# 単一プロセス
result = items.map { |item| expensive_operation(item) }
# 並列処理(4コア)
result = Parallel.map(items, in_processes: 4) { |item| expensive_operation(item) }
20-B. Ruby での型安全性と Typecheck ツール
Ruby 3.0+ では型チェックツールが充実しました。
Sorbet(Stripe製)
# typed: true(strictレベルを指定)
class User
extend T::Sig
sig { params(name: String, age: Integer).returns(User) }
def initialize(name, age)
@name = name
@age = age
end
sig { returns(String) }
def display_name
"#{@name} (#{@age})"
end
end
# 型チェック
srb tc # type check
Sorbet は RBS(Ruby Signature)を読みながら、複数のチェック方針(strict, ignore など)でコード品質を高めます。
Steep(Soutaro製)
RBS + Steep でより標準的な型チェック:
# main.rb
x: Integer = "string" # エラー: String は Integer に代入不可
# steep check
RBS(Ruby Signature)
型情報をシグネチャファイル(.rbs)に分離:
# user.rbs
class User
attr_accessor name: String
attr_accessor age: Integer
def initialize: (String, Integer) -> void
def display_name: () -> String
def valid?: () -> bool
end
# user.rb
class User
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def display_name
"#{@name} (#{@age})"
end
def valid?
@name.length > 0 && @age > 0
end
end
チェック:
steep check user.rb
20-C. Ruby での並行・並列の詳細
Thread の注意点
# ❌ これは race condition
counter = 0
10.times do
Thread.new { 10.times { counter += 1 } }
end
sleep 0.1
puts counter # 50 前後(期待値:100)
# ✅ Mutex で保護
counter = 0
mutex = Mutex.new
10.times do
Thread.new {
10.times {
mutex.synchronize { counter += 1 }
}
}
end
Thread.list.each(&:join) # すべてのスレッド完了を待つ
puts counter # 100
Ractor(Ruby 3.1+)
スレッド単位ではなく、完全に独立した actor:
# 従来のThread(GVL で共有)
shared_data = []
Thread.new { shared_data << 1 }
Thread.new { shared_data << 2 }
# Ractor(独立、メッセージパッシング)
r1 = Ractor.new { Ractor.receive } # メッセージ待機
r2 = Ractor.new { Ractor.receive }
r1.send(100)
r2.send(200)
puts r1.take # 100
puts r2.take # 200
Ractor は Ruby の将来的な並列処理の有望な方向ですが、まだ実験的機能です。
Fiber(Coroutine)
スレッドより軽い並行:
fiber1 = Fiber.new do
puts "Fiber 1: start"
Fiber.yield
puts "Fiber 1: resume"
end
fiber2 = Fiber.new do
puts "Fiber 2: start"
Fiber.yield
puts "Fiber 2: resume"
end
fiber1.resume # "Fiber 1: start"
fiber2.resume # "Fiber 2: start"
fiber1.resume # "Fiber 1: resume"
fiber2.resume # "Fiber 2: resume"
Fiber は手作業で制御フローを管理する必要がありますが、コンテキストスイッチ費用が非常に低いため、数万個の軽い処理に向きます。
20-D. メタプログラミングの実践パターン
method_missing の活用
class OpenObject
def method_missing(name, *args)
if name.to_s.end_with?("=")
@data ||= {}
@data[name.to_s.chomp("=").to_sym] = args[0]
else
@data ||= {}
@data[name]
end
end
def respond_to_missing?(name, include_private = false)
true # すべてのメソッド呼び出しに応答可能
end
end
obj = OpenObject.new
obj.name = "Alice"
obj.age = 30
puts obj.name # "Alice"
puts obj.age # 30
define_method による動的メソッド生成
class DynamicModel
ATTRIBUTES = [:id, :name, :email]
def initialize(attrs)
@attrs = attrs
end
ATTRIBUTES.each do |attr|
define_method(attr) { @attrs[attr] }
define_method("#{attr}=") { |value| @attrs[attr] = value }
end
end
user = DynamicModel.new(id: 1, name: "Alice", email: "alice@example.com")
user.name = "Bob"
puts user.name # "Bob"
Class#attr_* の内部実装
class MyModel
# attr_accessor :name は以下のように実装されている:
define_method(:name) { @name }
define_method(:name=) { |value| @name = value }
# attr_reader :id は getter のみ
define_method(:id) { @id }
end
20. Ruby 3.0〜3.4の新機能
ここ数年のRubyの主要な追加機能をバージョン別に整理します。
20-1. Ruby 3.0(2020年12月)
「Ruby 3x3」を達成した節目のメジャーリリース。
Ractor(実験的)
第14章で詳述。GVLを超えた並列実行。
パターンマッチング(PEP 634相当)
第5章で詳述。
case event
in { type: "click", x:, y: }
...
end
キーワード引数の完全分離
Hashとキーワード引数が完全に別物に。第6章6-3で詳述。
エンドレスメソッド
def square(x) = x * x
RBS標準同梱
第15章で詳述。型情報フォーマットRBSが言語仕様の一部に。
20-2. Ruby 3.1(2021年12月)
YJIT実験的搭載
RUBY_YJIT_ENABLE=1 ruby app.rb
ShopifyによるJITコンパイラ。3.1ではまだ実験的。
Hash短縮記法
x = 1
y = 2
h = { x:, y: } # { x: 1, y: 2 } と等価(3.1+)
x: x と書く必要がなくなりました。コードの記述量を減らす地味だが嬉しい変更。
debug.gem標準同梱
ruby -rdebug app.rb # ブレークポイントで停止可
長らく byebug などが使われていたデバッガが、公式に標準同梱。
20-3. Ruby 3.2(2022年12月)
YJIT量産レベル
3.2でYJITが production ready に。Shopify本番投入で実績。
Dataクラス
イミュータブルなレコード型。第8章8-6で詳述。
Point = Data.define(:x, :y)
p = Point.new(x: 1, y: 2)
p.with(x: 10) # 新インスタンス
WASI / WebAssembly対応
Rubyが ブラウザで動くようになりました。WASI対応により、CRuby自体をWebAssemblyにコンパイルできる。
<script src="https://cdn.jsdelivr.net/npm/@ruby/3.2-wasm-wasi/dist/browser.script.iife.js"></script>
<script type="text/ruby">
puts "Hello from Ruby on WebAssembly!"
</script>
これにより、ブラウザ・Node.js・サーバレス環境でRubyが動かせるように。
Setが標準同梱
require "set" が不要に。
s = Set[1, 2, 3] # 即使える
20-4. Ruby 3.3(2023年12月)
YJIT大幅高速化
3.3ではYJITがさらに効率化され、Railsアプリでの体感差がはっきり出るレベルに。
Prismパーサ統合
新しいパーサ Prism が標準ライブラリに統合(第2章2-2で詳述)。3.4でデフォルトに。
M:Nスレッドスケジューラ(実験的)
RactorのOSスレッドへのマッピングをM:N化(M個のRactorをN個のOSスレッドで動かす)。Goroutine風の効率化。
IRBの改善
REPLであるIRBが大幅に強化。シンタックスハイライト、補完、コンソールでの編集機能などが向上。
20-5. Ruby 3.4(2024年12月)
Prismがデフォルトパーサ
parse.y の長い時代が終わり、Prismがデフォルトに。
it ブロック引数(3.4+)
ブロックの暗黙の引数を it で参照できる。
[1, 2, 3].map { it * 2 } # [2, 4, 6]
# 同等
[1, 2, 3].map { |x| x * 2 }
[1, 2, 3].map(&:succ)
短いブロックがさらに簡潔に。Kotlinの it に着想を得た機能。
Hash#except(標準化)
RailsのActiveSupportでは既にあった except が 標準ライブラリに。
{ a: 1, b: 2, c: 3 }.except(:b) # { a: 1, c: 3 }
Range#stepの挙動改善
(1..10).step(2).to_a # [1, 3, 5, 7, 9]
よりよいエラーメッセージ
NoMethodError: undefined method `nme' for "hello":String
Did you mean? name?
20-6. このセクションのまとめ
3.0 (2020): Ractor、パターンマッチング、キーワード引数分離、エンドレスメソッド、RBS
3.1 (2021): YJIT実験搭載、Hash短縮記法 { x:, y: }、debug.gem
3.2 (2022): YJIT量産化、Dataクラス、WASI対応、Set標準同梱
3.3 (2023): YJIT大幅高速化、Prism統合、M:Nスケジューラ、IRB改善
3.4 (2024): Prismデフォルト、itブロック引数、Hash#except標準化
トレンド:
- YJITによる継続的高速化
- 並列処理(Ractor、M:N)の進化
- 型システム(RBS)の整備
- エラーメッセージ・REPLの改善(学習者に優しく)
- Ruby on WASMで実行環境が広がる
次のセクションでは、よくある落とし穴をFAQ形式でまとめます。
21. よくある落とし穴FAQ
実務で頻発するRubyの落とし穴を一問一答形式でまとめます。
Q1. == と eql? と equal? の違いは?
==: 値の等価(一般的に使う)。1 == 1.0はtrueeql?: 型まで含めた等価(Hashキー比較)。1.eql?(1.0)はfalseequal?: 同一オブジェクト(再定義しない)
詳細は第4章4-3。
Q2. nil チェックでよく見る .nil? と unless、どっち?
# どちらも有効
unless x.nil?
puts x
end
# 値があれば実行(nilでもfalseでもfalsyなので除外)
if x
puts x
end
「nil専用にチェックしたい」なら nil?。「何かtruthyな値があるか」なら if x。意図によって使い分け。
Q3. 文字列リテラルの " と '、どっち?
「補間や \n を使いたいなら "、そうでなければ '」が伝統的。ただしRuboCopは #{} がないなら ' をデフォルトにする設定が一般的。frozen_string_literal: true を使うなら、両者の差はほぼ無くなる。
Q4. attr_accessor を使うとprivateにできない?
正解。attr_accessor はpublicメソッドを生成する。private の後に書いてもprivateにはならない(古いバージョンで罠だった)。
class A
private
attr_accessor :x # 旧バージョンではprivateにならなかった
end
Ruby 3.0+ では一応privateにもなる挙動が改善されている。
Q5. puts と print と p の違いは?
puts: 末尾に改行、配列を渡すと要素ごとに改行print: 改行なしp:inspectを使ってデバッグ表示(オブジェクト構造が見える)pp:pの整形版(pretty print)
puts "hello" # hello\n
print "hello" # hello(改行なし)
p "hello" # "hello"(クォート付き)
p [1, 2, 3] # [1, 2, 3](配列のまま)
puts [1, 2, 3] # 1\n2\n3\n
デバッグ時は p を使うのが鉄則。
Q6. = と === の違いは?
==: 値の等価===: caseのwhenで使われる「マッチする」判定
Integer === 1 # true
(1..10) === 5 # true
/foo/ === "foo" # true
第4章4-3で詳述。
Q7. ブロック内の変数が外に漏れる?
ブロックのローカル変数は ブロックスコープに閉じる(forループは漏れる)。
[1, 2, 3].each do |x|
y = x * 2
end
# yは外で参照できない
# ただしforは漏れる(だからforは使わない)
for x in [1, 2, 3]
z = x * 2
end
# zは外でも生きてしまう
Q8. Proc.new と lambda、結局どっちを使う?
新規コードでは lambda(または ->) が推奨。第7章7-3で詳述。理由は「returnの挙動が直感的」「引数チェックが厳密」。
Q9. nil に .nil? 以外のメソッドを呼んでもエラーにならないのはなぜ?
nil は NilClass のインスタンスで、to_s to_a などのメソッドが定義されているため。これは「安全側に倒した設計」だが、見えにくいバグの温床にもなる。&. や Kernel#tap でnilチェックを意識的に。
Q10. &:method_name ってどういう意味?
Symbol#to_proc の糖衣構文。
[1, 2, 3].map(&:to_s)
# 等価
[1, 2, 3].map { |x| x.to_s }
第7章7-4で詳述。
Q11. ||= ってどう動く?
「変数がnil/falseなら代入」というメモ化イディオム。
@cache ||= compute_expensive_value
# 等価
@cache = @cache || compute_expensive_value
「1度しか計算したくない」値のキャッシュに頻出。
Q12. キーワード引数をHashで渡せないのはなぜ?
Ruby 3.0でキーワード引数とHashが完全分離されたため(6-3参照)。
def f(a:, b:); end
opts = { a: 1, b: 2 }
f(opts) # ArgumentError(3.0+)
f(**opts) # OK
Q13. each の中で next / break / return の違いは?
next: 次の繰り返しへ(current iterationをスキップ)break: ループから抜けるreturn: メソッドそのものから抜ける
def find_first(arr)
arr.each do |x|
return x if x.even? # メソッドから抜ける
end
nil
end
Q14. freeze した配列の中身もfreezeされる?
されない。freeze は そのオブジェクト1つだけをfreezeする。
arr = [[1, 2], [3, 4]].freeze
arr << [5] # FrozenError(外側はfrozen)
arr[0] << 999 # OK!中身はfreezeされていない
深くfreezeしたいなら、各要素を再帰的にfreezeする必要があります。
Q15. なぜ private def method_name が動く?
def は メソッド名をSymbolで返すので、それを private メソッドに渡している。
private def method_name
...
end
# 等価
def method_name
...
end
private :method_name
これが「Rubyのあらゆる構文が式である」良さです。
Q16. Comparable と Enumerable をincludeしても何も起きない?
両者とも、ある特定のメソッド(<=> または each)が定義されていることを前提にして、その上に他のメソッドを構築します。前提メソッドを実装し忘れると NoMethodError。
第9章9-4で詳述。
Q17. クラス変数(@@)を使ってはいけないと言われるのは?
サブクラスで値が共有されてしまうため。
class Parent
@@count = 0
end
class Child < Parent
@@count = 100 # Parent::@@countも書き換える!
end
代わりに「クラスインスタンス変数」を使う(第3章3-2参照)。
Q18. self を省略しないほうがいい場合は?
setterを呼ぶときは self. 必須。
class A
def name=(value); @name = value; end
def update(v)
name = v # ローカル変数として扱われる!(バグ)
self.name = v # OK
end
end
このせいで「selfだけはsetterのときに必須」という独特のルールがあります。
Q19. yield と &block.call の違いは?
yield: ブロックを直接実行する。最速&block.call: ブロックをProc化してから呼ぶ。やや遅いが、ブロックを別のメソッドに渡すなど柔軟
def speed
yield
end
def flexible(&block)
block.call
end
ブロックを「ただ実行する」ならyield、「他に渡す・保存する」なら &block。
Q20. Array#map と each の違いは?
map: 戻り値の配列を作るeach: 副作用主体、戻り値は元の配列
[1, 2, 3].map { |x| x * 2 } # [2, 4, 6]
[1, 2, 3].each { |x| x * 2 } # [1, 2, 3](戻り値は捨てる用途)
意図を表すために、戻り値を使うならmap、使わないならeach、と区別する。
22. 図解: オブジェクトモデルとメソッド探索
文字だけでは伝わりにくいRubyの挙動を、図で整理します。
22-1. オブジェクトモデル
+-------------+
| BasicObject |
+-------------+
^
|
+----------+
| Object |
+----------+
^
|
+----------+
| Module |
+----------+
^
|
+----------+
| Class | ← クラスの「クラス」
+----------+
すべてのクラスはClassのインスタンス
Class自体もClassのインスタンス(自己参照)
22-2. メソッド探索(include + prepend)
module M1; end
module M2; end
class A
prepend M1
include M2
end
A.ancestors
[M1, A, M2, Object, Kernel, BasicObject]
↑prepend ↑include ↑Objectは自動で先祖
メソッド呼び出し時の探索:
M1 → A → M2 → Object → Kernel → BasicObject
最初に見つかったメソッドが呼ばれる
superで次の祖先へ
22-3. 特異クラス
+----------+
| Class |
+----------+
^
|
+----------+
| A |
+----------+
^
| (instance_of)
|
+----------+ +-------------+
| obj | ----- | obj専用 |
+----------+ ←────┐ | 特異クラス |
│ +-------------+
│
def obj.fooは ─┘
この特異クラスにメソッドを追加する
クラスメソッド(def self.foo)は
クラスオブジェクト自身の特異クラスにメソッドを追加する
22-4. ブロック・Proc・Methodの関係
┌──────┐
│block │ ← メソッド呼び出しに付随する特殊な引数(1つだけ)
└──┬───┘
│ &blockで受け取る
▼
┌──────┐ ┌────────┐
│ Proc │ ⇄ │ Lambda │ ← オブジェクト化(lambda? で区別)
└──┬───┘ └────────┘
│ & で再びブロックに展開
▼
メソッド呼び出しに渡せる
Method(メソッドオブジェクト化)
┌──────┐
│Method│ ← obj.method(:name) で取り出す
└──────┘ to_procでProcになる
22-5. GVLとスレッド
時間軸 →
Thread A: ▓▓▓▓▓░░░░░▓▓▓▓▓░░░░░
Thread B: ░░░░░▓▓▓▓▓░░░░░▓▓▓▓▓
Thread C: ░░░░░░░░░░░░░░░░░░░ (待機)
▓ = 実行中(GVLを保持)
░ = I/O待ちなどでGVL解放、または取得待ち
I/O系操作(sleep, ファイル, ネットワーク)はGVLを解放する
→ I/Oバウンドな処理はThreadで並行化できる
純RubyのCPUバウンドはGVLのため並列化されない
→ RactorまたはProcessを使う
22-6. Ractorの世界
+--------+ +--------+ +--------+
|Ractor 1| |Ractor 2| |Ractor 3|
| GVL #1 | | GVL #2 | | GVL #3 |
+--------+ +--------+ +--------+
↕ メッセージ ↕ メッセージ ↕
CPUコア1 CPUコア2 CPUコア3 ← 物理的に並列実行
各Ractorは独自のGVLを持つので並列実行できる
共有メモリは無し、メッセージパッシングで通信
共有可能オブジェクトはイミュータブルなものだけ
23. 学習ロードマップ(30日)
体系的にRubyを学ぶための30日プラン。1日1〜2時間を想定。
Week 1(基礎)
- Day 1: 環境構築(rbenvまたはasdf)、IRBに慣れる、Ruby Style Guideを読む
- Day 2: 変数・主要なデータ型(Integer/Float/String/Symbol/Array/Hash)
- Day 3: 制御フロー(if/unless/case/while)
- Day 4: メソッドの基本(def, return, 引数)
- Day 5: ブロックの基本(each, map, select)
- Day 6: 自分用のスクリプトを書く(ファイル処理、CSV加工など)
- Day 7: 復習、簡単なミニプロジェクト
Week 2(オブジェクト指向)
- Day 8: クラス定義、attr_accessor、initialize
- Day 9: 継承・super・モジュールinclude
- Day 10: extend、prepend、ancestors
- Day 11: Enumerable / Comparableのミックスイン
- Day 12: 例外処理、独自例外
- Day 13: ブロック・Proc・Lambdaの違い
- Day 14: 復習。簡単なOOPアプリ(タスク管理など)
Week 3(応用)
- Day 15: メタプログラミング基礎(define_method, send)
- Day 16: method_missingとrespond_to_missing?
- Day 17: instance_eval / class_eval、DSLの作り方
- Day 18: 文字列・正規表現・ヒアドキュメント
- Day 19: gemの使い方、Bundler、Gemfile
- Day 20: テスト(MinitestまたはRSpec)の基本
- Day 21: 復習、テスト付きのアプリを書く
Week 4(実装)
- Day 22: 並行処理基礎(Thread、Mutex、Queue)
- Day 23: FiberとAsync gem
- Day 24: Ractorの基本
- Day 25: 型ヒント(RBS、Steep)
- Day 26: プロファイリング・ベンチマーク
- Day 27: YJITを試す、メモリ最適化
- Day 28: 自作gemを1本作って公開
- Day 29: RailsかSinatraで簡単なWebアプリ
- Day 30: 全体振り返り、好きなgemのソースコードを読む
30日後の到達目標
- 中規模のRubyコードベースを読み書きできる
- メタプログラミングを「使うべき場面」と「避けるべき場面」を判断できる
- ブロック・Proc・Lambdaの違いを説明できる
- RSpec / Minitestでテストを書ける
- Bundlerで自分のgemを作って公開できる
- RailsのDSLの中身がだいたい想像できる
おすすめの実践プロジェクト
| プロジェクト | 学べる要素 |
|---|---|
| CLIツール(thor / dry-cli) | コマンドラインオプション、ファイルI/O |
| 自作DSL(Rake風) | instance_eval、メタプロ |
| Webスクレイパー | Nokogiri、HTTPクライアント |
| Web API(Sinatra) | HTTP、JSON、ルーティング |
| Webアプリ(Rails) | Active Record、ビュー、フォーム |
| 自作gem | gemspec、Bundler、テスト、CI |
24. 用語集
本ガイドに登場した主要な用語を50音順・アルファベット順で。
あ行
- アクセサ(attr_accessor): インスタンス変数のgetter/setterを1行で定義する仕組み
- アクター: 独立した状態と計算単位を持ち、メッセージで通信する並行モデル。RubyのRactorの元
- アンカー(@、@@): 変数の種類を示す接頭辞(@: インスタンス、@@: クラス)
- 暗黙のreturn: メソッドの最後の式が自動的に戻り値になるRubyの仕組み
- イテレータ: eachなどの繰り返しメソッドの総称
- イミュータブル: 変更不可。RubyではSymbol、freeze済みString、Dataなど
か行
- キーワード引数: 名前付きで渡す引数(Ruby 3.0でHashと完全分離)
- クロージャ: 外側のスコープの変数を捕捉する関数(RubyのProc/Lambdaが該当)
- 継承: クラス < 親クラス で実現される単一継承
- コルーチン: 中断・再開できる関数。RubyのFiberが該当
さ行
- シンボル(Symbol): イミュータブル・一意の識別子。
:nameで書く - 修飾子: 後置のif/unless/whileなどの構文
- 真偽性(truthiness): nilとfalse以外はすべて真
- ストラクチュラルタイピング: 型ではなくメソッドの存在で判断する。Rubyのduck typingが該当
- スプラット(
*): 配列展開・可変長引数の演算子 - 生成(new): クラスからインスタンスを作る
Class.new
た行
- ダックタイピング: 「アヒルのように歩けばアヒル」、必要な操作ができれば型を問わない
- 特異クラス(singleton class): そのオブジェクトだけが持つ匿名クラス
- 特異メソッド: 特定のオブジェクトだけが持つメソッド(
def obj.method) - 動的型付け: 値が型を持ち、変数は型を持たない。実行時に型が決まる
な行
- 名前空間(namespace): モジュールを使った識別子のグループ化
- 内部DSL: Rubyのメソッド呼び出しで実現される特定領域向け言語
は行
- バイトコード: Rubyのソースをコンパイルした中間表現(YARV命令列)
- パターンマッチング(case/in): 構造的に値を分解してマッチさせる構文(3.0+)
- ブロック: メソッドに渡せるコード。
do/endまたは{}で書く - プロトコル: 「特定のメソッドを持っていれば良い」という構造的な型契約
- プロパティ(メソッド): getter/setterで属性を表現する慣習
- プロセス(fork): マルチプロセス並列の単位
- ベンチマーク: 性能計測(Benchmark / benchmark-ips)
ま行
- ミックスイン(mixin): モジュールをクラスにinclude / extend / prependする仕組み
- モンキーパッチ: 既存のクラスを後から拡張すること。オープンクラス機能
- メタクラス: 特異クラスの別名
- メタプログラミング: コード自身を扱うコード(method_missing、define_methodなど)
や〜わ行
- 遅延評価(lazy): 必要になるまで計算しないアプローチ。Enumerable.lazy
A〜Z
- ABI: Application Binary Interface
- ActiveSupport: Railsのコアライブラリで、Ruby標準を拡張した便利機能の集合
- AST: Abstract Syntax Tree、抽象構文木
- CRuby(MRI): 公式のC言語実装。世界のRubyのほぼすべて
- EAFP: Easier to Ask Forgiveness than Permission(Rubyでも例外で書くのが一般的)
- GVL: Global VM Lock。CRubyで同時にバイトコードを実行できるスレッドを1つに制限
- JIT: Just-In-Timeコンパイル。Ruby 3.1+ のYJITが代表
- MJIT: Ruby 2.6で導入された旧JIT。3.1+ でYJITに主役交代
- Module: メソッドや定数の集まり、ミックスインや名前空間に使う
- PEP: Pythonの概念(Rubyには無い、RubyはCRubyのチケット駆動)
- Rake: Ruby製ビルドツール。MakefileのRuby版
- REPL: Read-Eval-Print-Loop(RubyではIRB)
- RSpec: BDDスタイルのテストフレームワーク
- TDD: Test-Driven Development。Railsコミュニティが強く推進
- WASI/WASM: WebAssembly System Interface。Ruby 3.2+ でサポート
- YARV: Yet Another Ruby VM。Ruby 1.9+ の標準仮想マシン
- YJIT: Yet Another Just-In-Time。Shopify主導のJITコンパイラ
まとめ
Rubyは、オブジェクト指向を自然な記述感で扱える言語です。ブロック、Enumerable、メタプログラミング、Railsエコシステムを理解すると、少ない記述で表現力の高いアプリケーションを作れます。一方で、暗黙性が増えすぎないように、命名、テスト、境界設計を丁寧に保つことが重要です。