Skip to content

Ruby チートシート

開発者の幸福度のために最適化された、動的でエレガントな言語。

01

基礎

変数と型

Ruby は動的型付けです—変数に型宣言は不要です。型を検査するには .class を、型チェックには is_a? を使用してください。数値やブール値を含めすべてがオブジェクトです。

ruby
name = "Alice"    # string
age = 30           # integer
pi = 3.14          # float
is_dev = true      # boolean
nums = [1, 2, 3]   # array
puts name.class    # String
puts age.is_a?(Integer)  # true

シンボル

シンボル(:name)は不変でインターン化された文字列です—メモリに1つのコピーしか存在しません。アイデンティティがコンテンツより重要なハッシュキー、メソッド名、識別子に使用してください。繰り返し使用には文字列よりメモリ効率が良いです。

ruby
status = :active
puts status.class        # Symbol
puts status.to_s         # "active"
# Symbols are immutable, reusable strings
hash = { name: "Alice", status: :active }
puts hash[:status]       # active

Nil と真偽性

Ruby では nil と false のみが偽です—それ以外(0、''、[] を含む)はすべて真です。これは 0 が偽となる多くの言語とは異なります。デフォルトには || を、nil の具体的なチェックには nil? を使用してください。

ruby
x = nil
puts x.nil?        # true
puts x || "default"  # default
# Only nil and false are falsy; 0 and "" are truthy!
puts 0 ? "truthy" : "falsy"  # truthy
puts "" ? "truthy" : "falsy" # truthy
puts nil ? "truthy" : "falsy" # falsy

型変換

to_i/to_s/to_f は寛容な変換です(失敗時に0を返します)。Integer()/Float() は厳密です(ArgumentError を発生)。検証が必要な場合は厳密な変換を、graceful な降格が必要な場合は寛容な変換を使用してください。

ruby
puts "42".to_i      # 42
puts 42.to_s        # "42"
puts "3.14".to_f    # 3.14
puts 3.14.to_i      # 3
puts "abc".to_i     # 0 (no error)
puts Integer("42")  # 42 (raises if invalid)

文字列補間

二重引用符文字列は #{expr} 補間をサポートします—任意の Ruby 式を埋め込めます。単一引用符文字列はリテラルです(\\ と \' を除き補間やエスケープなし)。補間やエスケープシーケンスが必要な場合は二重引用符を使用してください。

ruby
name = "Alice"
age = 30
puts "Name: #{name}, Age: #{age}"
puts "5 + 3 = #{5 + 3}"
puts "Upper: #{name.upcase}"
# Single quotes don't interpolate
puts 'No #{name} here'  # No #{name} here
02

文字列

一般的な文字列メソッド

Ruby の文字列には豊富なメソッドがあります。ほとんどは新しい文字列を返します(Ruby では文字列は可変です)。インプレース変更には ! バリアント(upcase!、gsub!)を使用してください—これらはレシーバを変更し、変更がない場合は nil を返します。

ruby
s = "Hello, World"
puts s.length         # 12
puts s.upcase         # HELLO, WORLD
puts s.downcase       # hello, world
puts s.reverse        # dlroW ,olleH
puts s.split(", ")    # ["Hello", "World"]
puts s.gsub("o", "0") # Hell0, W0rld

文字列変更(Bang メソッド)

! で終わるメソッドはオブジェクトをインプレースで変更し、多くの場合より効率的です。変更がなかった場合 nil を返すことがあります。コピーの作成を避けたい場合に使用しますが、副作用に注意してください。

ruby
s = "hello"
s.upcase!    # s is now "HELLO"
s.gsub!(/L/, "1")  # s is now "HE11O"
puts s       # HE11O
# Bang methods modify in-place; use with caution
arr = [3, 1, 2]
arr.sort!    # arr is now [1, 2, 3]

ヒアドキュメントと複数行

ヒアドキュメント(<<TEXT ... TEXT)は複数行文字列を作成します。<<~(波線ヒアドキュメント)は共通の先頭空白を削除しクリーンなコードにします。SQL クエリ、HTML テンプレート、長いメッセージに有用です。

ruby
text = <<~HEREDOC
  Hello,
  World!
  Indentation is stripped.
HEREDOC
puts text
# <<~ strips leading whitespace (Ruby 2.3+)
# <<HEREDOC preserves indentation

フォーマット(sprintf)

C スタイルの文字列フォーマットには % 演算子または format/sprintf を使用してください。%-10s は10文字で左寄せ、%08.2f は8文字にゼロパディングし小数点以下2桁です。表形式出力と固定幅フォーマットに有用です。

ruby
puts sprintf("%s is %d", "Alice", 30)
puts "%-10s|%5d" % ["Name", 42]
puts format("%.2f", 3.14159)  # 3.14
puts "%08.2f" % 3.14          # 00003.14
# %s string, %d integer, %f float

文字列連結

+ は新しい文字列を作成し、<< はインプレースで追加します(文字列構築に効率的)。* は文字列を繰り返します。join は配列要素をセパレータで結合します。パフォーマンスのため繰り返し + より << または join を優先してください。

ruby
s1 = "Hello" + ", " + "World"
s2 = ["a", "b", "c"].join("-")  # a-b-c
s3 = "Hello"
s3 << " " << "World"  # in-place append
s4 = "x" * 3          # xxx (repetition)
puts s1, s2, s3, s4
03

データ構造

配列

配列は順序付き、ゼロインデックスで、混合型を保持できます。<< と push は末尾に追加します。メンバーシップには include? を、合計には sum を使用してください。配列は可変です—不変にするには freeze を使用してください。

ruby
nums = [1, 2, 3, 4, 5]
nums.push(6)          # [1,2,3,4,5,6]
nums << 7             # same as push
nums[0] = 0
puts nums.first       # 0
puts nums.length      # 7
puts nums.sum         # 28
puts nums.include?(3) # true

ハッシュ

ハッシュはキーと値のディクショナリです。シンボルキー(name:)が慣用的で効率的です。存在には key? を、安全なアクセスには fetch を(欠落時に発生)、一括更新には transform_values を使用してください。

ruby
user = { name: "Alice", age: 30 }
user[:email] = "[email protected]"
puts user[:name]          # Alice
puts user.key?(:name)     # true
user.each { |k, v| puts "#{k}: #{v}" }
puts user.values          # ["Alice", 30, "[email protected]"]
puts user.transform_values(&:to_s)

範囲

'..' は終端を含み、'...' は除外します。範囲は遅延で大きなシーケンスにメモリ効率が良いです。反復、スライシング、シーケンス生成に有用です。任意の Comparable 型で使用できます。

ruby
(1..5).each { |n| puts n }      # 1 2 3 4 5
puts (1...5).to_a                # [1, 2, 3, 4]
puts (1..10).select(&:even?)     # [2, 4, 6, 8, 10]
puts ('a'..'e').include?('c')    # true
puts (1..5).map { |n| n ** 2 }   # [1, 4, 9, 16, 25]

Enumerable メソッド

Enumerable は Ruby の最も強力なミックスインです—map、select、reject、reduce、find、group_by、sort_by など。{ |x| x.method } の短縮形として &:method を使用してください。これらは表現力豊かなデータ変換パイプラインを可能にします。

ruby
nums = [1, 2, 3, 4, 5]
puts nums.map { |n| n * 2 }.inspect    # [2,4,6,8,10]
puts nums.select(&:even?).inspect      # [2, 4]
puts nums.reject(&:odd?).inspect       # [2, 4]
puts nums.reduce(0) { |sum, n| sum + n } # 15
puts nums.find { |n| n > 3 }           # 4
puts nums.group_by(&:even?).inspect    # {false=>[1,3,5], true=>[2,4]}

セット

Set('set' ライブラリから)は O(1) のルックアップでユニークな要素を格納します。重複排除とセット操作(和集合、積集合、差集合)に使用してください。配列が必要な場合は to_a で変換します。'set' の require が必要です。

ruby
require 'set'
a = Set.new([1, 2, 3])
b = Set.new([3, 4, 5])
puts a.union(b).to_a.inspect        # [1,2,3,4,5]
puts a.intersection(b).to_a.inspect # [3]
puts a.subtract([1]).to_a.inspect   # [2, 3]
puts a.subset?(Set.new([1,2,3,4]))  # true
a.add(6)
04

制御フロー

If / Elsif / Unless

if/elsif/else は標準的な分岐です。unless は if の逆です(条件が false のときに実行)。修飾子形式(statement if condition)は単一行ガードの慣用的な方法です—単純なケースの可読性を向上させます。

ruby
score = 85
if score >= 90
  puts "A"
elsif score >= 80
  puts "B"
else
  puts "C"
end
# Modifier form
puts "Pass" if score >= 60
puts "Fail" unless score >= 60

Case(When)

case/when はマッチングに === を使用し、範囲、クラス、正規表現を可能にします。複数の値はカンマで区切ります。then は単一行本体を許可します。対象がない場合、case はよりクリーンな if/elsif チェーンとして機能します。

ruby
grade = "B"
case grade
when "A" then puts "Excellent"
when "B", "C" then puts "Good"
when "D".."F" then puts "Poor"
else puts "Unknown"
end
# Case without value = multi-condition if
case
when score > 90 then puts "Top"
when score > 60 then puts "Pass"
end

While / Until / Loop

while は true の間実行し、until は true になるまで実行します(while false)。loop は無限です—break で抜けます。コレクションには while よりイテレータ(each、map)を優先してください—より慣用的でエラーが少ないです。

ruby
count = 0
while count < 3
  puts count
  count += 1
end
# until = opposite of while
n = 3
until n == 0
  puts n
  n -= 1
end
# Infinite loop with break
loop do
  puts "forever"
  break if rand > 0.8
end

イテレータ(Each/Times/Upto)

Ruby のイテレータは for ループより慣用的です。カウントには times、範囲には upto/downto、インデックス+値には each_with_index を使用します。step がインクリメントを制御します。これらは Ruby の反復の基盤です。

ruby
3.times { |i| puts i }          # 0 1 2
1.upto(3) { |n| puts n }        # 1 2 3
3.downto(1) { |n| puts n }      # 3 2 1
[1,2,3].each_with_index do |n, i|
  puts "#{i}: #{n}"
end
(1..3).step(1) { |n| puts n }   # 1 2 3

Break / Next / Redo

next は次の反復にスキップし(continue のようなもの)、break はループを早期終了します。break はブロックから値を返せます。redo は条件を再チェックせずに現在の反復を再開します—滅多に使用されません。

ruby
[1, 2, 3, 4, 5].each do |n|
  next if n.even?    # skip even
  break if n > 4     # stop at 5
  puts n             # prints 1, 3
end
# break value returns from the block
result = [1,2,3].each { |n| break n * 10 if n == 2 }
puts result  # 20
05

メソッドとブロック

メソッド定義

メソッドは def/end を使用します。キーワード引数(key:)はパラメータが多い場合の可読性を向上させます。*args は余分な位置引数を配列に集め、**kwargs はキーワード引数をハッシュに集めます。デフォルト値は = を使用します。

ruby
def greet(name, greeting: "Hello")
  "#{greeting}, #{name}!"
end
puts greet("Alice")               # Hello, Alice!
puts greet("Bob", greeting: "Hi") # Hi, Bob!
def add(*nums)  # splat (variadic)
  nums.sum
end
puts add(1, 2, 3, 4)  # 10

ブロックと Yield

ブロックはメソッドに渡される匿名のコード塊です。yield がブロックを呼び出します。ブロックは Ruby の至る所にあります(each、map など)。yield を使用してメソッドを柔軟にしてください—呼び出し側が振る舞いを提供します。

ruby
def repeat(n)
  n.times { yield }
end
repeat(3) { puts "hi" }  # prints hi 3 times
def with_result
  yield(5)
end
puts with_result { |x| x * 2 }  # 10

Proc と Lambda

Proc と lambda は変数に格納された再利用可能なブロックです。主な違い:lambda は引数の数をチェックし(不一致で発生)、自身からのみ return します。proc は寛容で外側のメソッドから return します。厳密な振る舞いには lambda を優先してください。

ruby
square = proc { |x| x * x }
puts square.call(5)   # 25
puts square.(5)       # 25 (shortcut)
double = lambda { |x| x * 2 }
puts double.call(5)   # 10
# Lambda checks arity; Proc doesn't
# Lambda returns from itself; Proc returns from enclosing method

メソッドオブジェクト(&)

&:method はメソッド名を Proc に変換します。単純な1メソッドブロックの慣用的な短縮形です:map { |s| s.to_i } の代わりに map(&:to_i)。単純な変換にクリーナーで読みやすいです。

ruby
nums = ["1", "2", "3"]
ints = nums.map(&:to_i)  # [1, 2, 3]
puts ints.inspect
# &:to_i is shorthand for { |s| s.to_i }
names = ["alice", "bob"]
up = names.map(&:upcase)  # ["ALICE", "BOB"]
puts up.inspect

戻り値

Ruby のメソッドは最後に評価された式を暗黙的に返します—明示的な return は不要です。早期退出に return を使用してください。複数の値は配列として返され、分解できます。これによりコードが簡潔になります。

ruby
def status(ok)
  return "error" unless ok
  "ok"  # implicit return of last expression
end
puts status(true)   # ok
puts status(false)  # error
# Multiple assignment
def coords
  return 1, 2  # returns [1, 2]
end
x, y = coords
06

クラスとオブジェクト指向

クラスとインスタンス変数

@var = インスタンス変数(オブジェクトごと)、@@var = クラス変数(共有)。attr_accessor が getter+setter を生成し、attr_reader は getter のみ、attr_writer は setter のみ。self.method がクラスメソッドを定義します。

ruby
class Person
  attr_accessor :name, :age
  attr_reader :id
  @@count = 0  # class variable
  def initialize(name, age)
    @name = name  # instance variable
    @age = age
    @@count += 1
  end
  def self.count; @@count; end
end
p = Person.new("Alice", 30)
puts p.name, Person.count

継承と Super

< は継承を示します(単一継承のみ)。super は現在のメソッドの親バージョンを呼び出します。親の振る舞いを拡張するには super を使用してください(引数を渡すには括弧付き、同じ引数を渡すには括弧なし)。Ruby は単一継承 + ミックスインを使用します。

ruby
class Animal
  def initialize(name); @name = name; end
  def speak; "..."; end
end
class Dog < Animal
  def speak; "#{@name}: Woof!"; end
end
class Puppy < Dog
  def speak; "#{super} (small)"; end
end
puts Puppy.new("Rex").speak  # Rex: Woof! (small)

モジュール(ミックスイン)

モジュールは再利用可能なメソッドをグループ化します。include はインスタンスメソッドを追加し、extend はクラスメソッドを追加します。これは Ruby の多重継承の解決策です。Enumerable は有名なミックスインです—include して each を定義すれば map、select などが得られます。

ruby
module Walkable
  def walk; "#{@name} is walking"; end
end
module Swimmable
  def swim; "#{@name} is swimming"; end
end
class Duck
  include Walkable    # instance methods
  extend Swimmable    # class methods
  def initialize(n); @name = n; end
end
puts Duck.new("Donald").walk  # Donald is walking

アクセス制御

public(デフォルト)、private(明示的なレシーバなしでのみ呼び出し可能)、protected(クラス階層内で呼び出し可能)。内部ヘルパーには private を、同じクラスのインスタンス間で共有するメソッドには protected を使用してください。

ruby
class BankAccount
  def initialize(bal); @balance = bal; end
  def deposit(amt); @balance += amt; end
  def balance; @balance; end
  private
  def audit; "auditing..."; end
  protected
  def compare(other); @balance > other.balance; end
end
a = BankAccount.new(100)
puts a.balance  # 100
# a.audit  # Error: private method

Self とクラスメソッド

self は現在のオブジェクトを指します。クラス本体内では self はクラスです—def self.method がクラスメソッドを定義します。クラスメソッドはクラスで呼び出され(Counter.total)、インスタンスメソッドはインスタンスで呼び出されます。alias_method がメソッドのエイリアスを作成します。

ruby
class Counter
  @@total = 0
  def initialize; @@total += 1; end
  def self.total; @@total; end
  def self.reset!; @@total = 0; end
  def instance_method; "I'm an instance"; end
  alias_method :count, :instance_method
end
Counter.new; Counter.new
puts Counter.total  # 2
07

エラー処理

Begin / Rescue / Ensure

begin/rescue は Ruby の try/catch です。対象を絞った処理のために特定の例外クラスを rescue してください。=> e が例外オブジェクトをキャプチャします。ensure は常に実行されます—クリーンアップ(ファイルのクローズ、ロックの解放)に使用してください。

ruby
begin
  result = 10 / 0
rescue ZeroDivisionError => e
  puts "Caught: #{e.message}"
rescue => e
  puts "Other error: #{e.class}"
ensure
  puts "Always runs (cleanup)"
end
# ensure runs regardless of success/failure

Raise とカスタム例外

raise は例外をスローします(引数なしの raise は再スロー)。カスタム例外は StandardError(またはより具体的なクラス)を継承します。Error サフィックスで名前を付けてください。特定の失敗モードを処理するためにクラスで rescue してください。

ruby
class InvalidAgeError < StandardError; end
def set_age(age)
  raise InvalidAgeError, "Age cannot be negative" if age < 0
  raise ArgumentError, "Must be Integer" unless age.is_a?(Integer)
  @age = age
end
begin
  set_age(-5)
rescue InvalidAgeError => e
  puts "Custom: #{e.message}"
end

Retry

retry は begin ブロックを最初から再開します。一時的な失敗(ネットワーク、レート制限)にカウンタとともに使用し、無限ループを避けてください。制限がないと retry はプログラムをハングさせる可能性があります—常にガードしてください。

ruby
attempts = 0
begin
  attempts += 1
  fetch_data  # might fail
rescue NetworkError
  retry if attempts < 3
  puts "Failed after 3 attempts"
end
# retry re-runs the begin block from the top

Rescue 修飾子

修飾子としての rescue はフォールバック値を提供する簡潔な方法です。StandardError をキャッチし右側を返します。単純なケースに使用してください—複雑なロジックにはエラーを隠すため避けてください。解析やオプション操作に最適です。

ruby
result = risky_operation rescue "default"
puts result
# Same as:
# result = begin; risky_operation; rescue; "default"; end
# Catches StandardError only
json = JSON.parse(str) rescue nil
puts "Invalid JSON" if json.nil?

Throw / Catch(制御フロー)

throw/catch は例外処理ではありません—深いネストからの早期退出のための制御フローメカニズムです(他の言語とは異なります)。throw :symbol が一致する catch にジャンプします。ネストされたループから抜けるために使用し、実際のエラーには begin/rescue を使用してください。

ruby
catch(:done) do
  [1, 2, 3, 4, 5].each do |n|
    throw :done, n if n > 3
    puts n
  end
end
# Prints 1, 2, 3 and returns 4
# Not for errors—use for early exit from nested loops
08

ファイル I/O

ファイルの読み書き

File.write/File.read はシンプルなワンショットメソッドです。ブロック付き File.open はファイルを自動クローズします。File.foreach はファイル全体を読み込まず1行ずつ読みます—大きなファイルにメモリ効率が良いです。chomp が末尾の改行を削除します。

ruby
# Write
File.write("test.txt", "Hello, File!")
# Read
content = File.read("test.txt")
puts content  # Hello, File!
# Append
File.open("log.txt", "a") { |f| f.puts "new line" }
# Read line by line
File.foreach("test.txt") { |line| puts line.chomp }

ファイルブロック(自動クローズ)

常にブロック付きで File.open を使用してください—エラーが発生してもファイルがクローズされることを保証します。ブロック形式はファイルを扱う慣用的で安全な方法です。ブロックなしの場合、手動で close を呼ぶ必要があります。

ruby
File.open("data.txt", "w") do |f|
  f.puts "Line 1"
  f.puts "Line 2"
  f.write("No newline")
end  # file auto-closed here
File.open("data.txt", "r") do |f|
  f.each_line.with_index { |line, i| puts "#{i}: #{line}" }
end

ファイルの存在と情報

File クラスはファイルシステムクエリを提供します。file?/directory? が型を区別します。mtime/ctime/atime がタイムスタンプを与えます。rename/delete がファイルシステムを変更します。エラーを避けるため操作前に常に存在を確認してください。

ruby
puts File.exist?("test.txt")  # true
puts File.file?("test.txt")   # true (regular file)
puts File.directory?(".")     # true
puts File.size("test.txt")    # bytes
puts File.mtime("test.txt")   # modification time
File.rename("old.txt", "new.txt")
File.delete("new.txt") if File.exist?("new.txt")

ディレクトリ操作

Dir はディレクトリを管理します。mkdir が作成し、chdir が変更します(ブロック形式は後に復元)。glob がファイルパターンにマッチします—* は任意に、** は再帰的にマッチします。ファイル発見とバッチ処理に有用です。

ruby
Dir.mkdir("test_dir") unless Dir.exist?("test_dir")
Dir.chdir("test_dir") do
  File.write("a.txt", "a")
  puts Dir.pwd  # current path
end
Dir.glob("*.txt") { |f| puts f }  # list .txt files
Dir.glob("**/*.rb") { |f| puts f } # recursive

CSV と JSON

CSV と JSON は標準ライブラリにあります。CSV.foreach は行をストリーミングします(メモリ効率)。JSON.parse は文字列キーを持つハッシュ/配列を返します。to_json が任意のオブジェクトをシリアライズします。これらはデータ交換に不可欠です。

ruby
require 'csv'
require 'json'
CSV.write("data.csv", [["a", 1], ["b", 2]])
CSV.foreach("data.csv") { |row| puts row.inspect }
data = { name: "Alice", age: 30 }
File.write("data.json", data.to_json)
parsed = JSON.parse(File.read("data.json"))
puts parsed["name"]  # Alice
09

Date/Time と正規表現

Time と Date

Time は瞬間を表します(タイムゾーン付き)。Date はカレンダー日付を表します(時間なし)。Time の算術は秒を使用します。strftime は %Y(年)、%m(月)、%d(日)、%H:%M(時間)でフォーマットします。ISO8601 解析には 'time' を require してください。

ruby
require 'time'
now = Time.now
puts now                    # 2024-01-15 14:30:00 +0800
puts now.strftime("%Y-%m-%d %H:%M")  # 2024-01-15 14:30
tomorrow = now + 86400      # +1 day (seconds)
puts tomorrow.strftime("%A") # weekday name
date = Date.today
puts date.next_day          # tomorrow

Date の解析と算術

Date の算術は自然に機能します—整数を加えると日を加えます。next_month/prev_month が月の境界を処理します。Date - Date は Rational(日数)を返します。upto/downto が日付範囲を反復します。カレンダーロジックには Date を、タイムスタンプには Time を使用してください。

ruby
require 'date'
d = Date.parse("2024-01-15")
puts d.year   # 2024
puts d + 7    # 2024-01-22 (+7 days)
puts d.next_month  # 2024-02-15
diff = (Date.today - d).to_i
puts "#{diff} days since"
puts Date.today.upto(Date.today + 6).map(&:wday)

正規表現マッチング

=~ はマッチ位置または nil を返します。$1、$2 がマッチ後にキャプチャグループを保持します。.match はより詳細な MatchData オブジェクトを返します。単純なチェックには =~ を、キャプチャの抽出には .match を使用してください。正規表現リテラルは /pattern/ を使用します。

ruby
s = "Phone: 123-4567"
if s =~ /(\d+)-(\d+)/
  puts "Area: #{$1}, Number: #{$2}"
end
m = /\w+@(\w+)/.match("[email protected]")
puts m[1]  # example
puts "abc123" =~ /\d/ ? "has digit" : "no digit"

正規表現置換

gsub はすべてのマッチを置換します(sub は最初のみ)。動的置換のためにブロックを渡してください。scan はすべてのマッチを配列に抽出します。置換文字列内の後方参照(\1、\2)はキャプチャグループを参照します。テキスト処理に強力です。

ruby
s = "Hello, World"
puts s.gsub(/o/, "0")       # Hell0, W0rld
puts s.gsub(/\w+/) { |w| w.capitalize }
puts s.scan(/\w+/).inspect  # ["Hello", "World"]
puts "2024-01-15".gsub(/(\d+)-(\d+)-(\d+)/, '\3/\2/\1')
# => 15/01/2024

正規表現オプション

正規表現オプション:i(大文字小文字を区別しない)、m(複数行—ドットが改行にマッチ)、x(拡張—パターン内の空白/コメントを許可)。%r{} はパターンにスラッシュが含まれる場合(URL のような)に有用な代替デリミタです。

ruby
puts /hello/i =~ "HELLO"  # 0 (case-insensitive)
puts /line/m =~ "a\nb"    # 0 (multiline: . matches \n)
puts /x.y/x =~ "x y"       # 0 (extended: ignore whitespace)
# Common patterns
email = /[\w.]+@[\w]+\.[a-z]+/
url = %r{https?://[\w./]+}
puts email.match("[email protected]") ? "valid" : "invalid"
10

メタプログラミングと並行性

動的メソッド定義

define_method は実行時にメソッドを作成します。複数の類似メソッドの生成や DSL 構築に使用してください。これはメタプログラミング—コードを書くコードです。強力ですが慎重に使用してください;コードを理解しにくくする可能性があります。

ruby
class Dynamic
  [:foo, :bar, :baz].each do |name|
    define_method(name) { puts "Called #{name}" }
  end
end
d = Dynamic.new
d.foo  # Called foo
d.bar  # Called bar
# Useful for generating similar methods

method_missing

method_missing は未定義メソッドへの呼び出しをインターセプトします。柔軟な API やプロキシの構築に使用してください。リフレクションが機能するよう respond_to_missing? も常にオーバーライドしてください。控えめに使用してください—バグを隠し静的解析を混乱させる可能性があります。

ruby
class Proxy
  def method_missing(name, *args)
    puts "Called: #{name} with #{args.inspect}"
  end
  def respond_to_missing?(name, include_private = false)
    true
  end
end
p = Proxy.new
p.anything(1, 2)  # Called: anything with [1, 2]

スレッド

スレッドはコードを並行実行します。join が完了を待機します。MRI(標準 Ruby)には GIL があるため、CPU バウンドのスレッドは真の並行では動作しません—I/O バウンドのスレッドは動作します。真の並行性には Ractor(Ruby 3.0 以降)または複数プロセスを使用してください。

ruby
threads = [1, 2, 3].map do |n|
  Thread.new { puts "Thread #{n}: #{n * n}" }
end
threads.each(&:join)  # wait for all
# MRI has GIL—threads don't run truly parallel for CPU work
# Use for I/O concurrency (network, file)

ファイバー(協調的)

ファイバーは軽量で協調的な並行性です—yield/resume で手動で一時停止と再開をします。スレッドとは異なり並行では動作しません。ジェネレータ、遅延評価、一時停止可能な計算に使用してください。スレッドよりオーバーヘッドが低いです。

ruby
fiber = Fiber.new do
  Fiber.yield 1
  Fiber.yield 2
  3
end
puts fiber.resume  # 1
puts fiber.resume  # 2
puts fiber.resume  # 3
# Fibers yield control cooperatively, not preemptively

Send と Eval

send は名前で任意のメソッドを呼び出します(private を含む)。public_send は可視性を尊重します。eval は文字列を Ruby コードとして実行します—非常に強力ですが信頼できない入力には危険です(コードインジェクション)。動的ディスパッチには send を使用し、本番では eval を避けてください。

ruby
class Obj
  def secret; "hidden"; end
end
o = Obj.new
puts o.send(:secret)  # hidden (calls private too)
puts o.public_send(:secret)  # hidden (respects visibility)
x = 5
result = eval("x * 2")
puts result  # 10
# eval runs a string as code—avoid with untrusted input!
11

ブロック、Proc と Lambda

ブロックの基礎

ブロックは Ruby の最も一般的なクロージャです—{ } または do...end でメソッドに渡される匿名コード。yield がブロックを呼び出します。|n| がブロックパラメータを宣言します。block_given? がブロックが渡されたかチェックします。ブロックはオブジェクトではありません(変数に格納できません)—そのためには Proc/Lambda を使用してください。すべてのメソッドは暗黙的なブロックを受け取れるため、DSL が自然になります(Rails がこれを多用)。

ruby
# Block: anonymous chunk of code passed to a method
[1, 2, 3].each { |n| puts n }          # do-end for multi-line
[1, 2, 3].each do |n|
  puts n * 2
end

# yield: call the block from a method
def greet
  puts "before"
  yield  # invokes the block
  puts "after"
end
greet { puts "in block" }
# before / in block / after

# yield with arguments
def compute
  result = yield(10, 20)
  puts "got: #{result}"
end
compute { |a, b| a + b }  # got: 30

# block_given? checks if a block was passed
def maybe_yield
  return "no block" unless block_given?
  yield
end

Proc と Lambda

Proc と Lambda はどちらも呼び出し可能なオブジェクトです(ブロックをオブジェクトにしたもの)。Proc は寛容です:余分な引数は nil になり、欠落した引数は nil で、'return' は外側のメソッドから抜けます。Lambda は厳密です:引数の数をチェックし、'return' は lambda からのみ抜けます。メソッドのような振る舞いには lambda を、ブロックのような振る舞いには Proc を使用してください。->(stabby lambda)はモダンで簡潔な構文です。

ruby
# Proc: a block stored as an object
p = Proc.new { |x| puts x * 2 }
p.call(5)      # 10
p.(5)          # 10 (shorthand)
p[5]           # 10 (another shorthand)

# Lambda: stricter Proc
l = lambda { |x| puts x * 2 }
l = ->(x) { puts x * 2 }  # stabby lambda syntax
l.call(5)

# Key differences:
# 1. Argument checking
p = Proc.new { |a, b| puts a }
p.call(1)        # 1 (b is nil, no error)
l = ->(a, b) { puts a }
l.call(1)        # ArgumentError (wrong number of args)

# 2. return behavior
def proc_test
  p = Proc.new { return 1 }
  p.call
  return 2  # never reached—Proc return exits method
end
proc_test  # 1

def lambda_test
  l = lambda { return 1 }
  l.call  # returns from lambda, not method
  return 2
end
lambda_test  # 2

メソッドオブジェクト

method(:name) はメソッドを Method オブジェクトとして取得します(Proc のように呼び出し可能)。&:symbol はシンボルをそのメソッドを送る proc に変換します—非常に一般的なイディオム:array.map(&:to_s)。& プレフィックスは Proc をブロックに(またはその逆)変換します。これによりメソッドを引数としてエレガントに渡せます。Method オブジェクトはレシーバを保持するため、オブジェクトにバインドされます。

ruby
class Calculator
  def add(a, b) a + b end
end

calc = Calculator.new
# method(:name) gets a Method object
m = calc.method(:add)
m.call(2, 3)  # 5
m.(2, 3)      # 5
m[2, 3]       # 5

# Convert method to Proc
p = m.to_proc
p.call(4, 5)  # 9

# &:method shorthand (common in map, each)
["a", "b", "c"].map(&:upcase)  # ["A", "B", "C"]
# Equivalent to: .map { |s| s.upcase }

# Symbol#to_proc
:upcase.to_proc.call("hello")  # "HELLOW"

# Useful for passing methods as blocks
[1, 2, 3].each(&method(:puts))  # prints 1, 2, 3

クロージャとバインディング

クロージャ(ブロック、proc、lambda)は変数を参照でキャプチャします—キャプチャされた変数の更新が見えます。これにより状態を持つクロージャ(カウンタ、アキュムレータ)が可能になります。Proc#binding がクロージャの環境へのアクセスを与えます(高度なメタプログラミング用)。Kernel#binding メソッドが eval 用の現在の実行コンテキストをキャプチャします。クロージャは Ruby のブロックがコールバックとイテレータで強力な理由です。

ruby
# Closures capture surrounding variables
counter = 0
increment = lambda { counter += 1 }
increment.call  # 1
increment.call  # 2
puts counter    # 2 (closure modified it)

# Multiple closures share the same variables
x = 10
add = lambda { |n| x += n }
get = lambda { x }
add.call(5)
get.call  # 15

# Proc#binding: access the closure's environment
def make_counter
  count = 0
  lambda { count += 1 }
end
c = make_counter
c.call  # 1
# c.binding.eval("count")  # 1 (peek at captured var)

# Binding for eval in a specific context
b = binding
x = 42
eval("x", b)  # 42

ブロックを使ったカスタムイテレータ

Enumerable を include して #each を定義すると map、select、reduce と50以上のメソッドが無料で得られます—これが Ruby のイテレータプロトコルです。アキュムレータ構築には inject より each_with_object がクリーンです。tap がメソッドチェーンに副作用を挿入します(デバッグに最適)。yield を使ったカスタムメソッドで独自の DSL を作成できます(with_timing、with_database など)。ブロックが Ruby の反復とコールバックパターンをエレガントにします。

ruby
# Define your own iterator using yield
class LinkedList
  include Enumerable  # gets map, select, etc. for free

  def each
    node = @head
    while node
      yield node.value
      node = node.next
    end
  end
end

# each_with_object (memo pattern)
result = [1, 2, 3].each_with_object({}) do |n, hash|
  hash[n] = n * n
end
# {1=>1, 2=>4, 3=>9}

# tap (for debugging chains)
[1, 2, 3].map { |n| n * 2 }
         .tap { |arr| puts "after map: #{arr}" }
         .select { |n| n > 2 }

# Custom method with block
def with_timing
  start = Time.now
  yield
  puts "took #{Time.now - start}s"
end
with_timing { sleep(1) }
12

Enumerable とイテレータ

コア Enumerable メソッド

Enumerable は Ruby の最も強力なミックスインです—include して #each を定義すると50以上のメソッドが得られます。map が変換、select/reject がフィルタ、reduce/inject が集計、find が最初のマッチを返し、group_by/partition がクラスタリングします。シンボルショートカット(reduce(:+))が慣用的です。これらのメソッドは配列、ハッシュ、範囲、ファイル—#each を持つ任意のもので機能します。Enumerable の習得は慣用的な Ruby の鍵です。

ruby
nums = [1, 2, 3, 4, 5, 6]

# map/collect: transform
nums.map { |n| n * 2 }        # [2, 4, 6, 8, 10, 12]

# select/filter: keep matching
nums.select { |n| n.even? }   # [2, 4, 6]
nums.reject { |n| n.even? }   # [1, 3, 5]

# reduce/inject: accumulate
nums.reduce(0) { |sum, n| sum + n }  # 21
nums.reduce(:+)                        # 21 (symbol shorthand)
nums.reduce(:*)                        # 720

# find/detect: first match
nums.find { |n| n > 3 }       # 4

# group_by: cluster
nums.group_by { |n| n.even? } # {false=>[1,3,5], true=>[2,4,6]}

# partition: split into two
nums.partition { |n| n.even? } # [[2,4,6], [1,3,5]]

# chunk: consecutive grouping
[1,1,2,2,3].chunk { |n| n }.to_a

遅延評価

lazy は enumerable を遅延なものに変換します—値は必要なときにのみ計算されます。これにより無限シーケンスが可能になり、少数の結果のみ必要なチェーンのためにコレクション全体を計算することを避けます。lazy がないと map/select は中間配列を作成します。lazy があるとパイプラインは一度に1つの値をプルします。大きな/無限のデータセットや高コストな変換に lazy を使用してください。トレードオフ:lazy は要素ごとのオーバーヘッドがあるため、小さなコレクションでは遅くなります。

ruby
# Lazy: evaluate on demand (infinite sequences possible)
require 'prime'

# Without lazy: infinite loop!
# primes = (1..Float::INFINITY).select(&:prime?).first(5)

# With lazy: works!
primes = (1..Float::INFINITY).lazy.select(&:prime?).first(5)
# [2, 3, 5, 7, 11]

# Chain without intermediate arrays
(1..1_000_000).lazy
  .map { |n| n * 2 }
  .select { |n| n > 1_000_000 }
  .first(10)  # only computes what's needed

# Build a lazy enumerator
enum = Enumerator::Lazy.new(1..Float::INFINITY) do |yielder, n|
  yielder << n if n.prime?
end
enum.first(5)  # [2, 3, 5, 7, 11]

Enumerator と外部反復

Enumerator は反復をオブジェクトとしてラップします—ブロック(内部反復)の代わりに .next を手動で呼び出せます(外部反復)。これにより反復の一時停止/再開、ピーク、無限シーケンスの作成が可能になります。ブロック付き Enumerator.new でカスタムイテレータ(ジェネレータ)を構築できます。ほとんどの Enumerable メソッドはブロックなしで呼び出すと Enumerator を返します:[1,2,3].map はチェーンできる Enumerator を返します。

ruby
# Enumerator: an iterator object you can control
enum = [10, 20, 30].each
enum.next  # 10
enum.next  # 20
enum.next  # 30
enum.next  # StopIteration

# External iteration with loop (handles StopIteration)
enum = [1, 2, 3].each
loop do
  puts enum.next
end

# Create custom Enumerator
fib = Enumerator.new do |yielder|
  a, b = 0, 1
  loop do
    yielder << a
    a, b = b, a + b
  end
end
fib.first(10)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# Convert to array
fib.take(10).to_a

ソートと比較

sort は2つの要素を比較するブロックを取ります(-1、0、1 を返す)。sort_by はより効率的です—各比較ごとではなく要素ごとにソートキーを一度計算します(シュワルツ変換)。複雑なキーには sort_by を使用してください。<=>(宇宙船)演算子は -1/0/1 を返し Ruby のソートの基盤です。min/max/min_by/max_by が極値を見つけます。Comparable を include して <=> を定義するとオブジェクトの自然な順序付けができます。

ruby
arr = [3, 1, 4, 1, 5, 9, 2, 6]

# sort: returns new array
arr.sort                    # [1, 1, 2, 3, 4, 5, 6, 9]
arr.sort { |a, b| b <=> a } # descending (9, 6, 5, ...)

# sort_by: more efficient (Schwartzian transform)
arr.sort_by { |n| -n }      # descending
words = ["banana", "apple", "cherry"]
words.sort_by { |w| w.length }  # ["apple", "banana", "cherry"]

# min/max
arr.min        # 1
arr.max        # 9
arr.minmax     # [1, 9]
arr.min_by { |n| n.abs }  # 1

# min/max with block
people.max_by { |p| p[:age] }

# <=> (spaceship): -1, 0, 1
1 <=> 2   # -1
2 <=> 2   # 0
3 <=> 2   # 1

ハッシュの反復と変換

ハッシュも Enumerable です。each/each_pair がキーと値のペアを反復します。transform_keys/transform_values(Ruby 2.5 以降)が変更されたキー/値で新しいハッシュを作成します。select/reject が新しいハッシュにフィルタします。merge がハッシュを結合します(ブロックが競合を解決)。group_by が配列からハッシュを構築します。ハッシュの反復は Ruby でのデータ処理の基盤です—クリーンで表現力豊かなデータ操作のためにこれらを習得してください。

ruby
hash = { a: 1, b: 2, c: 3 }

# Iterate
hash.each { |k, v| puts "#{k}=#{v}" }
hash.each_key { |k| puts k }
hash.each_value { |v| puts v }
hash.each_pair { |k, v| puts "#{k}: #{v}" }

# Transform keys/values
hash.transform_keys { |k| k.to_s }  # {"a"=>1, "b"=>2, "c"=>3}
hash.transform_values { |v| v * 10 } # {a:10, b:20, c:30}

# Filter
hash.select { |k, v| v > 1 }  # {b:2, c:3}
hash.reject { |k, v| v > 1 }  # {a:1}

# Merge
{ a: 1 }.merge({ b: 2 })  # {a:1, b:2}
{ a: 1 }.merge({ a: 2 }) { |k, old, new| old + new }  # {a:3}

# Invert (values become keys)
{ a: 1, b: 2 }.invert  # {1=>:a, 2=>:b}

# Group arrays into hash
%w[apple apricot banana].group_by { |w| w[0] }
# {"a"=>["apple", "apricot"], "b"=>["banana"]}
13

Gems と Bundler

Gem の基礎

Gem は Ruby のパッケージ(ライブラリ)です。gem install がシステム gem を管理します。プロジェクトには Bundler と Gemfile でバージョンを固定し依存関係を管理してください。バージョン指定子:'~> 1.4'(悲観的、パッチを許可)、'>= 5.0'(楽観的)。グループ(:development、:test、:production)で環境ごとに必要な gem のみを読み込めます。require: false は Bundler が自動 require しないことを意味します(必要時に手動で require)。

ruby
# Install a gem
$ gem install rails
$ gem install rails -v 7.0.0
$ gem install rails --pre  # pre-release

# List installed gems
$ gem list
$ gem list rails

# Uninstall
$ gem uninstall rails

# Gemfile: project dependencies
# Gemfile
source 'https://rubygems.org'
gem 'rails', '7.0.0'
gem 'pg', '~> 1.4'  # pessimistic: >= 1.4, < 2.0
gem 'puma', '>= 5.0'  # optimistic: >= 5.0
gem 'rspec', group: :test  # group-specific
gem 'pry', require: false  # don't auto-require

# Groups
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

group :production do
  gem 'newrelic_rpm'
end

Bundler コマンド

Bundler はプロジェクトが指定された正確な gem バージョンを使用することを保証します。bundle install は Gemfile を読み取り Gemfile.lock を書き込みます(再現性のための正確なバージョン—これをコミットしてください!)。bundle exec が正しい gem バージョンでコマンドを実行します(競合を回避)。bundle update がバージョンを変更します(注意—壊れる可能性があります)。正しい gem が読み込まれることを保証するため rake/rspec/rails には常に bundle exec を使用してください。Gemfile.lock がデプロイを再現可能にします。

ruby
# Install all gems from Gemfile
$ bundle install
$ bundle install --without production  # skip group

# Update gems
$ bundle update              # update all
$ bundle update rails        # update specific gem
$ bundle outdated            # show outdated gems

# Run commands in bundle context
$ bundle exec rails server
$ bundle exec rspec
$ bundle exec rake db:migrate

# Check for dependency issues
$ bundle check
$ bundle doctor

# Lock file: Gemfile.lock
# Records exact versions installed (for reproducibility)
# Commit Gemfile.lock to version control!

# Add/remove gems
$ bundle add rspec
$ bundle remove rspec

# Clean old gems
$ bundle clean

Gemspec(Gem の作成)

.gemspec ファイルは gem のメタデータと依存関係を定義します。spec.files が含まれるファイルをリストし、require_paths が Ruby にどこで見つけるかを伝えます。ランタイム依存には add_dependency を、テスト/ビルド依存には add_development_dependency を使用します。required_ruby_version が Ruby バージョンを強制します。gem build が .gem パッケージを作成し、gem install がローカルにインストールします。gem push で rubygems.org に公開します。構造:lib/ にコード、spec/ にテスト。

ruby
# my_gem.gemspec
Gem::Specification.new do |spec|
  spec.name = "my_gem"
  spec.version = "0.1.0"
  spec.summary = "A useful Ruby gem"
  spec.description = "Longer description..."
  spec.authors = ["Alice"]
  spec.email = ["[email protected]"]
  spec.homepage = "https://github.com/alice/my_gem"
  spec.license = "MIT"

  spec.files = Dir["lib/**/*.rb"] + ["README.md"]
  spec.require_paths = ["lib"]

  spec.required_ruby_version = ">= 3.0"

  spec.add_dependency "httparty", "~> 0.21"
  spec.add_development_dependency "rspec", "~> 3.12"
end

# Directory structure:
# my_gem/
#   lib/my_gem.rb        (main file)
#   lib/my_gem/version.rb
#   my_gem.gemspec
#   Gemfile
#   spec/                (tests)

# Build and install
$ gem build my_gem.gemspec  # creates my_gem-0.1.0.gem
$ gem install ./my_gem-0.1.0.gem

Rake タスク

Rake は Ruby の make です—タスクランナー。task :name do ... end でタスクを定義します。名前空間が関連タスクをグループ化します。ファイルタスクは依存関係を持ちます(ソース変更時に再ビルド)。sh がシェルコマンドを実行します。Rake はテスト、ビルド、デプロイ、データベースタスクに使用されます(Rails が多用)。rake task_name で実行します。default タスクは rake だけと入力したときに実行されます。rake greet[Alice] で引数を渡します。

ruby
# Rakefile
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

task default: :spec

# Custom task
task :greet, [:name] do |t, args|
  puts "Hello, #{args.name}!"
end
# $ rake greet[Alice]

# Namespace
namespace :db do
  task :migrate do
    puts "migrating..."
  end
  task :seed do
    puts "seeding..."
  end
end
# $ rake db:migrate

# File tasks (build dependencies)
file 'output.txt' => 'input.txt' do |t|
  sh "cp #{t.source} #{t.name}"
end

# Multi-line task
task :deploy do
  sh 'git push heroku main'
  sh 'heroku run rails db:migrate'
end

Rbenv と RVM(Ruby バージョン)

rbenv と RVM は1台のマシンで複数の Ruby バージョンを管理します。rbenv は軽量(シム)、RVM はより重い(シェルコマンドをオーバーライド)。.ruby-version ファイル(コミット済み)が全員が同じ Ruby バージョンを使用することを保証します。Gemset(RVM)または bundle config path がプロジェクト gem を分離します。本番では bundle config set path でプロジェクトにローカルに gem をインストールし、システム gem の汚染を避けてください。常に .ruby-version で Ruby バージョンを固定してください。

ruby
# rbenv: lightweight Ruby version manager
$ rbenv install 3.2.0        # install a version
$ rbenv global 3.2.0         # set global version
$ rbenv local 3.1.0          # set per-project (.ruby-version)
$ rbenv versions             # list installed

# .ruby-version file (committed to project)
# 3.2.0

# RVM: alternative version manager
$ rvm install 3.2.0
$ rvm use 3.2.0
$ rvm gemset create myapp    # isolated gem sets
$ rvm use 3.2.0@myapp

# Bundler config
$ bundle config set path 'vendor/bundle'  # install locally
$ bundle config set without 'development test'  # for production

# Check versions
$ ruby -v
$ gem -v
$ bundle -v
14

Rails の基礎

MVC 構造

Rails は MVC フレームワークです:モデル(ActiveRecord)がデータを扱い、コントローラ(ActionController)が HTTP リクエストを処理し、ビュー(ActionView)がレスポンスをレンダリングします。resources が7つの RESTful ルートを自動生成します。ルートが URL をコントローラアクションにマッピングします。設定より規約:モデルに User、コントローラに UsersController と名前付けすると、Rails がすべてを結線します。この構造がすべての Rails アプリの基盤です。

ruby
# Rails follows Model-View-Controller
# app/
#   models/    (ActiveRecord: data + business logic)
#   controllers/ (ActionController: handle requests)
#   views/     (ActionView: render responses)
#   helpers/   (view helpers)
# config/routes.rb (URL routing)

# Route → Controller → Model → View
# GET /users → UsersController#index → User.all → index.html.erb

# config/routes.rb
Rails.application.routes.draw do
  resources :users  # generates 7 RESTful routes
  # GET    /users          → index
  # GET    /users/new      → new
  # POST   /users          → create
  # GET    /users/:id      → show
  # GET    /users/:id/edit → edit
  # PATCH  /users/:id      → update
  # DELETE /users/:id      → destroy

  root 'pages#home'  # root path
  get 'about', to: 'pages#about'  # custom route
end

ActiveRecord モデル

ActiveRecord は Rails の ORM です—モデルがデータベーステーブルにマッピングされます。validates がデータ整合性を強制します。関連(has_many、belongs_to、has_one)がリレーションシップを定義します。コールバック(before_save、after_create)がライフサイクルにフックします。スコープは再利用可能なクエリ断片です。ActiveRecord は規約を使用:User モデル → users テーブル、created_at/updated_at カラム。これが Rails の心臓部です—効果的な Rails 開発のために習得してください。

ruby
# app/models/user.rb
class User < ApplicationRecord
  # Validations
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, uniqueness: true,
            format: { with: URI::MailTo::EMAIL_REGEXP }

  # Associations
  has_many :posts, dependent: :destroy
  has_one :profile
  belongs_to :company
  has_and_belongs_to_many :tags

  # Callbacks
  before_save :normalize_email
  after_create :send_welcome_email

  # Scopes (reusable queries)
  scope :active, -> { where(active: true) }
  scope :recent, -> { order(created_at: :desc).limit(10) }

  private
  def normalize_email
    self.email = email.downcase
  end
end

# Usage
User.create(name: "Alice", email: "[email protected]")
User.active.recent
User.find(1)
User.where("age > ?", 18)

コントローラと Strong Params

コントローラは HTTP リクエストを処理し、モデル/ビューを調整します。before_action がフィルタ(認証、リソース読み込み)を実行します。Strong パラメータ(permit)がマスアサインメントの脆弱性を防ぎます—ホワイトリストのフィールドのみ設定可能です。redirect_to がユーザーを別の場所に送信、render がビューを表示します。@instance 変数はビューで利用可能です。RESTful アクション(index、show、new、create、edit、update、destroy)が慣例です。

ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: %i[show edit update destroy]

  def index
    @users = User.all
  end

  def show; end  # @user set by before_action

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user, notice: 'User created'
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def set_user
    @user = User.find(params[:id])
  end

  # Strong parameters: whitelist allowed fields
  def user_params
    params.require(:user).permit(:name, :email, :age)
  end
end

ビューとヘルパー

ERB(Embedded Ruby)は Rails のデフォルトテンプレートです:<%= %> が出力、<% %> が実行。link_to/button_to が HTML リンク/フォームを生成します。form_with がモデルに結びついたフォームを構築します。パーシャル(_form.html.erb)は render でレンダリングされる再利用可能なビュー断片です。パスヘルパー(new_user_path、user_path(user))がルートから URL を生成します。ヘルパーがビューをクリーンに保ちます。複雑なロジックにはビューヘルパー(app/helpers/)やデコレータを使用してください。

ruby
<!-- app/views/users/index.html.erb -->
<h1>Users</h1>
<%= link_to "New User", new_user_path %>
<ul>
  <% @users.each do |user| %>
    <li>
      <%= link_to user.name, user_path(user) %>
      (<%= user.email %>)
      <%= link_to "Edit", edit_user_path(user) %>
      <%= button_to "Delete", user, method: :delete %>
    </li>
  <% end %>
</ul>

<!-- Partials: reusable view fragments -->
<!-- app/views/users/_form.html.erb -->
<%= form_with model: user do |form| %>
  <% if user.errors.any? %>
    <div class="errors">
      <% user.errors.full_messages.each do |msg| %>
        <p><%= msg %></p>
      <% end %>
    </div>
  <% end %>
  <%= form.text_field :name %>
  <%= form.email_field :email %>
  <%= form.submit %>
<% end %>

<!-- Render partial -->
<%= render "form", user: @user %>

マイグレーションとデータベース

マイグレーションはデータベーススキーマを時間とともに進化させ、バージョン管理されます。create_table がテーブルを定義し、add_column/remove_column がそれらを変更します。t.references が外部キーを作成します。t.timestamps が created_at/updated_at を追加します。db:migrate が保留中のマイグレーションを適用し、db:rollback が最後のものを取り消します。schema.rb ファイルが現在のスキーマの権威あるソースです(自動生成)。schema.rb を直接編集しないでください—マイグレーションを使用してください。これによりデータベース変更が環境間で再現可能になります。

ruby
# Generate a migration
$ rails generate migration CreateUsers name:string email:string

# db/migrate/20240101_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.string :email, null: false, index: { unique: true }
      t.integer :age, default: 0
      t.text :bio
      t.references :company, foreign_key: true

      t.timestamps  # created_at, updated_at
    end
  end
end

# Run migrations
$ rails db:migrate
$ rails db:rollback      # undo last migration
$ rails db:seed          # load seed data
$ rails db:reset         # drop + create + migrate + seed

# Schema file (don't edit manually)
# db/schema.rb reflects current database structure

# Add a column later
$ rails generate migration AddAgeToUsers age:integer
# creates: add_column :users, :age, :integer
15

RSpec テスト

基本構文(describe、it、expect)

RSpec は Ruby の主要なテストフレームワークです。describe が関連テストをグループ化し、it が単一テストを定義します。expect(...).to / not_to がアサーションを行います。let が遅延メモ化変数を定義します(初回アクセス時に一度だけ計算)。context は describe のエイリアスで、分岐(when...)に使用します。shoulda-matchers gem が一般的な Rails バリデーション/関連のワンライナー構文を提供します。テストは app/ 構造をミラーする spec/ に配置します。

ruby
# spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  # Setup with let (lazy, memoized)
  let(:user) { User.new(name: "Alice", email: "[email protected]") }

  describe '#name' do
    it 'returns the name' do
      expect(user.name).to eq("Alice")
    end

    # Multiple expectations with context
    context 'when name is blank' do
      let(:user) { User.new(name: "") }
      it 'is invalid' do
        expect(user).not_to be_valid
      end
    end
  end

  # One-liner syntax
  it { should validate_presence_of(:email) }
  it { should have_many(:posts) }
end

モックとスタブ

スタブ(allow)がメソッドの戻り値を置き換え、モック(expect)がメソッドが呼ばれたことを検証します。ダブルはテスト用のフェイクオブジェクトです(実オブジェクトより高速)。スタブを使用してテスト対象コードを外部依存(API、データベース)から分離します。モックを使用して相互作用を検証します。過剰なモックはテストを脆くします—十分に高速な場合は実オブジェクトを優先してください。FactoryBot がテストデータを作成し、create(DB に保存)または build(メモリ内のみ)を使用します。

ruby
RSpec.describe PaymentService do
  let(:user) { create(:user) }

  it 'charges the card' do
    # Stub: replace a method's return value
    allow(user).to receive(:premium?).and_return(true)

    # Mock: expect a method to be called
    expect(Stripe::Charge).to receive(:create).with(
      amount: 1000, currency: 'usd'
    ).and_return(double(id: 'ch_123'))

    service = PaymentService.new(user)
    result = service.charge(10)
    expect(result).to eq('ch_123')
  end

  it 'handles failure' do
    # Stub to raise an error
    allow(Stripe::Charge).to receive(:create)
      .and_raise(Stripe::StripeError.new("card declined"))

    service = PaymentService.new(user)
    expect { service.charge(10) }.to raise_error(PaymentError)
  end
end

# Double: test stand-in for an object
fake_card = double('Card', last4: '4242', brand: 'Visa')

FactoryBot とフィクスチャ

FactoryBot がデフォルト値を持つテストオブジェクトを作成します。トレイトがバリエーション(:admin、:inactive)を作成します。シーケンスが一意の値(メール)を生成します。create はデータベースに永続化し、build はしません。create_list が複数作成します。属性を渡すことで任意の属性をオーバーライドできます。ファクトリはフィクスチャ(YAML)より柔軟ですが遅いです(DB 書き込み)。DB にヒットしない高速なテストには build_stubbed を使用してください。ファクトリをシンプルに保ってください—複雑なファクトリは複雑なモデルを示唆します。

ruby
# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { "Alice" }
    email { "[email protected]" }
    age { 30 }
    active { true }

    # Traits: variations
    trait :admin do
      role { "admin" }
    end

    trait :inactive do
      active { false }
    end

    # Associations
    company { association(:company) }

    # Sequences for unique values
    sequence(:email) { |n| "user#{n}@example.com" }
  end
end

# Usage in specs
let(:user) { create(:user) }           # saved to DB
let(:admin) { create(:user, :admin) }  # with trait
let(:user) { build(:user) }            # not saved
let(:users) { create_list(:user, 5) }  # 5 users

# Override attributes
let(:alice) { create(:user, name: "Alice") }

Before/After フック

before フックがセットアップコードを実行します:before(:each)(最も一般的)が各テストの前に、before(:all) がグループごとに一度実行します。after フックがクリーンアップします。DatabaseCleaner がテストデータベースの状態を管理します(速度用のトランザクション、徹底性用のトランケーション)。before(:each) より let を優先してください—let は遅延(使用時のみ計算)かつメモ化されますが、before はテストがそれを必要としなくても実行されます。必須の副作用(ロギング、時間固定)には before を使用してください。

ruby
RSpec.describe User do
  before(:all) do
    # Runs once before all tests in this group
    @shared_data = load_expensive_data
  end

  before(:each) do  # or just 'before'
    # Runs before each test
    @user = User.create(name: "test")
  end

  after(:each) do
    # Runs after each test (cleanup)
    User.delete_all
  end

  after(:all) do
    # Runs once after all tests
    @shared_data = nil
  end

  # Database cleaner for transactional tests
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end
end

# Use before for setup, after for cleanup
# Prefer let over before(:each) for lazy evaluation

インテグレーションとシステムテスト

リクエストスペックは HTTP 経由でフルスタック(ルーティング → コントローラ → モデル → ビュー)をテストします。システムスペック(Capybara)は実ブラウザを駆動します—フォーム入力、クリック、ページ内容の確認。ユニットテスト(モデルスペック)は高速で分離されていますが、インテグレーション/システムテストは遅いが結線バグを捕捉します。テストピラミッドを使用してください:多数の高速ユニットテスト、少数のインテグレーションテスト、最小限のシステムテスト。have_http_status がレスポンスコードをチェックし、visit/fill_in/click_button がブラウザを駆動します。

ruby
# spec/requests/users_spec.rb (integration)
require 'rails_helper'

RSpec.describe 'Users API', type: :request do
  describe 'GET /users' do
    before { create_list(:user, 3) }

    it 'returns all users' do
      get '/users'
      expect(response).to have_http_status(200)
      expect(JSON.parse(response.body).size).to eq(3)
    end
  end

  describe 'POST /users' do
    it 'creates a user' do
      post '/users', params: { user: { name: 'Bob' } }
      expect(response).to have_http_status(:created)
    end
  end
end

# spec/system/login_spec.rb (browser tests)
require 'rails_helper'

RSpec.describe 'Login', type: :system do
  it 'logs in a user' do
    user = create(:user, password: 'secret')
    visit login_path
    fill_in 'Email', with: user.email
    fill_in 'Password', with: 'secret'
    click_button 'Log in'
    expect(page).to have_content('Welcome')
  end
end
16

ファイルとディレクトリ操作

ファイルの読み込み

File.read はファイル全体をメモリに読み込みます—小さなファイルには適しています。File.foreach は行ごとに読み込みます—大きなファイルには必須です(メモリを消費しません)。File.open をブロック付きで使用すると自動的にファイルを閉じます(RAII)。readlines は行の配列を返します(改行付き—chomp を使用)。バイナリファイルには binread。ファイルが存在しない可能性がある場合は読み込み前に File.exist? でチェックするか、Errno::ENOENT を rescue してください。

ruby
# Read entire file
content = File.read("data.txt")

# Read line by line (memory efficient)
File.foreach("large.txt") do |line|
  puts line.chomp  # chomp removes trailing newline
end

# Read all lines into array
lines = File.readlines("data.txt")

# With a block (auto-closes)
File.open("data.txt") do |f|
  f.each_line { |line| puts line }
end

# Read with options
File.read("data.txt", encoding: "utf-8")
File.binread("image.png")  # binary mode

# Check existence
File.exist?("data.txt")
File.file?("data.txt")   # is it a regular file?
File.directory?("path")  # is it a directory?

ファイルの書き込み

File.write はファイルを書き込む最も簡単な方法です(デフォルトで上書き)。mode: 'a' で追加します。File.open をブロック付きで使用すると例外が発生してもファイルが確実に閉じられます。puts は改行を追加し、write はしません。<< は write のエイリアスです(Ruby で一般的)。ログには一度開いて複数回書き込みます(パフォーマンス用にバッファリング)。リソースリークを避けるため常にファイルを閉じるかブロック形式を使用してください。

ruby
# Write (overwrites)
File.write("output.txt", "Hello, World!")

# Append
File.write("output.txt", "More text\n", mode: "a")

# With a block (buffered, auto-closes)
File.open("log.txt", "w") do |f|
  f.puts "Line 1"
  f.puts "Line 2"
  f.write("No newline")
  f << "appended"  # << is an alias for write
end

# Binary write
File.binwrite("data.bin", binary_data)

# Modes: r (read), w (write/truncate), a (append),
#        r+ (read/write), w+ (read/write/truncate),
#        b (binary, Windows)

# Flush buffer
f = File.open("log.txt", "w")
f.write("data")
f.flush  # write without closing
f.close

ディレクトリ操作

Dir.glob とパターンでファイルを検索します(** で再帰的)。FileUtils が堅牢なファイル操作を提供します:mkdir_p はネストしたディレクトリを作成し、cp_r は再帰的にコピーし、rm_rf は強制的に削除します(注意!)。Dir.chdir が作業ディレクトリを変更します(一時的に変更するにはブロック形式を使用)。Dir.entries は . と .. を含みますが、glob は含みません。クロスプラットフォームの安全性のため、手動の File 操作より FileUtils を優先してください。

ruby
require 'fileutils'

# List directory contents
Dir.entries(".")  # [".", "..", "file.txt", ...]
Dir.glob("*.rb")  # ["script.rb", "test.rb"]
Dir.glob("**/*.rb")  # recursive
Dir.glob("src/**/*.{rb,erb}")  # multiple extensions

# Create directories
Dir.mkdir("new_dir")
Dir.mkdir("nested/path") rescue Errno::ENOENT  # fails if parent missing
FileUtils.mkdir_p("nested/path/deep")  # creates all parents

# Remove
Dir.rmdir("empty_dir")  # only works if empty
FileUtils.rm_rf("dir")  # recursive force (dangerous!)

# Copy/Move
FileUtils.cp("a.txt", "b.txt")
FileUtils.mv("old.txt", "new.txt")
FileUtils.cp_r("src_dir", "dest_dir")  # recursive

# Change directory
Dir.chdir("/tmp") { puts Dir.pwd }  # temporarily
Dir.chdir("/tmp")  # permanently for process

# Check if directory
File.directory?("path")
File.exist?("path")

Pathname と Tempfile

Pathname はファイルパスのオブジェクト指向ラッパーです—文字列操作よりクリーンです。dirname、basename、extname、join、ファイルチェックをメソッドとして提供します。Tempfile は自動的に削除される一時ファイルを作成します(ブロック形式を使用)。Dir.mktmpdir が一時ディレクトリを作成します。クリーンで安全なパス処理と一時ファイル管理にこれらを使用してください。Pathname はプラットフォーム間でパスを安全に構成します(/ と \ を処理)。

ruby
require 'pathname'
require 'tempfile'

# Pathname: object-oriented path manipulation
path = Pathname.new("/home/user/docs/file.txt")
path.dirname   # #<Pathname:/home/user/docs>
path.basename  # #<Pathname:file.txt>
path.extname   # ".txt"
path.parent    # #<Pathname:/home/user/docs>
path.join("sub", "file.rb")  # /home/user/docs/sub/file.rb

path.exist?
path.directory?
path.file?
path.readable?

# Tempfile: auto-deleted file
Tempfile.create("prefix") do |f|
  f.write("temporary data")
  f.rewind
  puts f.read
end  # file deleted after block

# Tempfile without block (must close/unlink manually)
tf = Tempfile.new("prefix")
tf.write("data")
tf.close
tf.unlink  # delete

# Dir.mktmpdir for temp directories
Dir.mktmpdir do |dir|
  # work in dir
end  # dir deleted after

CSV と JSON

CSV と JSON は Ruby の標準ライブラリに組み込まれています。CSV.foreach は行ごとに読み込みます(メモリ効率的)、CSV.read はすべて読み込みます。headers: true は最初の行を列名として扱います。JSON.parse は JSON を Ruby ハッシュ/配列に変換し、to_json は Ruby オブジェクトをシリアライズします。symbolize_names はシンボルキーを与えます(よりクリーン)。YAML の場合は require 'yaml' と YAML.load_file を使用します。これらは Ruby スクリプトや Web アプリでのデータ交換に不可欠です。

ruby
require 'csv'
require 'json'

# CSV reading
CSV.foreach("data.csv", headers: true) do |row|
  puts row['name']  # access by header
  puts row[0]       # access by index
end

# CSV reading all at once
rows = CSV.read("data.csv", headers: true)
rows.first['name']  # first row's name

# CSV writing
CSV.open("output.csv", "w") do |csv|
  csv << ["name", "age"]  # header
  csv << ["Alice", 30]
  csv << ["Bob", 25]
end

# JSON
data = { name: "Alice", age: 30 }
json = data.to_json  # '{"name":"Alice","age":30}'
parsed = JSON.parse(json)  # {"name"=>"Alice", "age"=>30}

# JSON with symbols
JSON.parse(json, symbolize_names: true)  # {name: "Alice", age: 30}

# Read/write JSON files
File.write("data.json", data.to_json)
loaded = JSON.parse(File.read("data.json"))
17

エンコーディングと文字列内部

文字列エンコーディング

Ruby の文字列はエンコーディングを保持します(通常 UTF-8)。force_encoding はバイトを別のエンコーディングとして再解釈します(変換なし—バイトが既にそのエンコーディングであることが分かっている場合に使用)。encode は実際にエンコーディング間を変換します。valid_encoding? はバイトが文字列のエンコーディングに対して有効かチェックします。エンコーディングの問題が恐ろしい Encoding::CompatibilityError を引き起こします。データのエンコーディングを常に把握し、UTF-8 をデフォルトにしてください。

ruby
# Ruby strings have an encoding
"hello".encoding  # #<Encoding:UTF-8>
"café".encoding   # #<Encoding:UTF-8>

# Default external encoding (for file I/O)
Encoding.default_external  # #<Encoding:UTF-8>

# Force an encoding (reinterprets bytes, doesn't convert)
bytes = "café".bytes  # [99, 97, 102, 195, 169]
latin1 = bytes.pack("C*").force_encoding("ISO-8859-1")
# "caf" + é (as single byte)

# Convert encoding (transcodes)
utf8 = "café"
latin1 = utf8.encode("ISO-8859-1")  # converts
back = latin1.encode("UTF-8")        # converts back

# Check if valid
"abc".valid_encoding?  # true
"\xff".valid_encoding?  # false (invalid byte)

# Common encodings: UTF-8, ASCII, ISO-8859-1, Windows-1252
# Always use UTF-8 unless you have a specific reason

エンコーディング変換と I/O

ファイル I/O は読み込みに Encoding.default_external を使用します。encoding: オプションでファイルごとにエンコーディングを指定します。'source:target' 構文(ISO-8859-1:UTF-8)はソースエンコーディングで読み込みターゲットに変換します。default_internal を設定すると Ruby が読み込んだすべての文字列をそのエンコーディングに自動変換します。Web アプリではすべて UTF-8 にすべきです。レガシーデータを処理する際は、破損を避けるためエンコーディングを明示的に指定してください。

ruby
# Read a file with specific encoding
content = File.read("data.txt", encoding: "ISO-8859-1")
# content.encoding is ISO-8859-1

# Read and convert to UTF-8
content = File.read("data.txt", encoding: "ISO-8859-1:UTF-8")
# The "source:target" syntax converts while reading

# Write with specific encoding
File.write("output.txt", "café", encoding: "UTF-8")

# Open with encoding
File.open("data.txt", "r:ISO-8859-1") do |f|
  f.read  # ISO-8859-1 encoded string
end

# Convert while opening
File.open("data.txt", "r:ISO-8859-1:UTF-8") do |f|
  f.read  # UTF-8 encoded string
end

# Set default encodings
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8

文字列メソッドの深掘り

Ruby の文字列には豊富なメソッドがあります。検査(length、include?、start_with?)がプロパティをチェックします。変換メソッドは新しい文字列を返します(Ruby では文字列は可変ですが、これらは変更しません)。部分文字列アクセスは [start, length] または範囲を使用します。sub は最初の一致を置換し、gsub はすべて置換します(正規表現とブロックをサポート)。split/join が文字列と配列間で変換します。注意:Ruby 3.0+ の frozen_string_literal プラグマはパフォーマンスのために文字列を不変にします—構築には << または + を使用してください。

ruby
s = "Hello, World"

# Inspection
s.length    # 12
s.empty?    # false
s.include?("World")  # true
s.start_with?("Hello")  # true
s.end_with?("World")  # true

# Transformation (return new string)
s.upcase     # "HELLO, WORLD"
s.downcase   # "hello, world"
s.capitalize # "Hello, world"
s.swapcase   # "hELLO, wORLD"
s.reverse    # "dlroW ,olleH"
s.strip      # remove leading/trailing whitespace
s.chomp(",") # remove trailing substring
s.chop       # remove last char

# Substrings
s[0, 5]      # "Hello" (start, length)
s[7..11]     # "World" (range)
s[-5..]      # "World" (negative index)

# Replace
s.sub("World", "Ruby")    # first match
s.gsub("o", "0")          # all matches
s.gsub(/\w+/) { |w| w.upcase }  # with block

# Split/Join
"a,b,c".split(",")  # ["a", "b", "c"]
["a", "b"].join("-")  # "a-b"

フリーズされた文字列とパフォーマンス

frozen_string_literal: true(ファイル先頭のマジックコメント)はすべての文字列リテラルを不変にします—これはパフォーマンス最適化(フリーズされた文字列はメモリを共有可能)であり、意図しない変更のバグを防ぎます。文字列構築には +=(毎回新しい文字列を作成、O(n²))ではなく <<(インプレース追加、O(n))を使用してください。配列には join が最もクリーンです。StringIO はファイルのように振る舞いますが文字列に書き込みます—複雑な出力の構築に有用です。Ruby 3.x はデフォルトでフリーズされた文字列を推奨しています。

ruby
# frozen_string_literal: true  (at top of file)
# Makes all string literals in the file frozen (immutable)

# Mutable vs frozen
s = "hello"
s << " world"  # "hello world" (mutates)

f = "hello".freeze
# f << " world"  # Error: can't modify frozen String

# Why freeze? Performance: frozen strings share memory
# "abc".freeze is the same object everywhere
"a".freeze.equal?("a".freeze)  # true (same object)

# Building strings efficiently
# BAD: creates many intermediate strings
result = ""
items.each { |i| result += i.to_s }  # O(n²)

# GOOD: use << or join
result = ""
items.each { |i| result << i.to_s }  # O(n)
result = items.map(&:to_s).join  # cleanest

# StringIO for buffered building
require 'stringio'
io = StringIO.new
io << "line 1\n"
io << "line 2\n"
result = io.string

シンボルと文字列

シンボル(:name)は不変のシングルトン識別子です—メモリに :foo は1つしか存在しません。文字列は複数のインスタンスを持つ可変のテキストデータです。ハッシュキー(より高速な等価チェック)、メソッド名、enum のような値にはシンボルを使用してください。実際のテキストには文字列を使用します。シンボルはハッシュキーと比較でわずかに高速です。現代の Ruby(2.2+)ではシンボルはガベージコレクション可能なので、古い「シンボルのメモリリーク」懸念はなくなりました。Rails はキーとステータス値にシンボルを広く使用しています。

ruby
# Symbol: immutable, reusable identifier
:hello
:world
status = :active

# String: mutable, can have many instances
"hello"
"world"
status = "active"

# Key difference: identity
"hello".equal?("hello")  # false (different objects)
:hello.equal?(:hello)    # true (same object, singleton)

# Memory: symbols are singletons
1000.times { :foo }  # one :foo object
1000.times { "foo" }  # 1000 "foo" objects (unless frozen)

# Use symbols for:
# - Hash keys (faster comparison)
# - Method names (send(:method_name))
# - Enum-like values (:active, :pending, :closed)
# - Identifiers (not text data)

# Use strings for:
# - Text data (names, content)
# - Things that change

# Conversion
:hello.to_s  # "hello"
"hello".to_sym  # :hello
"hello world".to_sym  # :"hello world" (valid but ugly)
18

モジュールとミックスイン

モジュールの基礎

モジュールは2つの目的を果たします:名前空間(関連コードのグループ化、名前衝突の防止)とミックスイン(継承なしの振る舞いの共有)。モジュールメソッド(def self.method)はモジュールに対して呼び出されます。インスタンスメソッド(def method)はクラスにミックスインするためのものです。モジュールはインスタンス化できません。モジュールを使用してクラスを名前空間化(MyApp::User)し、定数とユーティリティ関数を整理します。これが Ruby の多重継承の代替です。

ruby
# Module: a namespace + collection of methods
module MathUtils
  PI = 3.14159

  def self.circle_area(radius)  # module method
    PI * radius ** 2
  end

  def square(x)  # instance method (for mixins)
    x * x
  end
end

# Access constants and module methods
MathUtils::PI  # 3.14159
MathUtils.circle_area(5)  # 78.54

# Namespacing classes
module MyApp
  class User
    # MyApp::User
  end
end

# Prevent instantiation (modules can't be instantiated)
# MathUtils.new  # NoMethodError

Include と Extend と Prepend

モジュールをミックスインする3つの方法:include(インスタンスメソッドを追加、ルックアップではクラスの下に配置)、extend(クラスメソッドを追加)、prepend(インスタンスメソッドを追加、クラスの上に配置—ラップ/オーバーライド可能)。prepend は before/after フックに強力です(元のを呼ぶには super を使用)。メソッドルックアップ:prepend → クラス → include → スーパークラス。通常のミックスインには include、既存メソッドをラップする必要がある場合は prepend、クラスレベルの機能には extend を使用してください。

ruby
module Greetable
  def greet
    "Hello from #{self.class}"
  end
end

class User
  include Greetable  # adds as instance methods
end
User.new.greet  # "Hello from User"

class Service
  extend Greetable  # adds as class methods
end
Service.greet  # "Hello from Service"

# Method lookup order:
# prepend → class → include → super
module Logging
  def save
    puts "before save"
    super  # calls the original save
    puts "after save"
  end
end

class Record
  prepend Logging  # Logging#save runs first
  def save; puts "saving"; end
end
Record.new.save
# before save / saving / after save

Enumerable ミックスイン

Enumerable をインクルードして #each を定義すると、map、select、reduce、sort、min、max など40以上のメソッドが得られます—これが Ruby のイテレータプロトコルです。Comparable をインクルードして <=> を定義すると、<、>、==、between?、clamp、sort サポートが得られます。これらのミックスインが Ruby のコレクションが強力な理由です。コレクションを表す、または自然な順序付けを持つ任意のクラスはこれらをインクルードすべきです。継承よりコンポジションです。

ruby
# Include Enumerable + define #each = 50+ methods free
class Playlist
  include Enumerable

  def initialize(songs)
    @songs = songs
  end

  def each
    @songs.each { |song| yield song }
  end
end

playlist = Playlist.new(["Song A", "Song B", "Song C"])
playlist.map { |s| s.upcase }   # ["SONG A", "SONG B", "SONG C"]
playlist.select { |s| s.include?("A") }  # ["Song A"]
playlist.reduce(:+)            # "Song ASong BSong C"
playlist.sort                  # ["Song A", "Song B", "Song C"]
playlist.first(2)              # ["Song A", "Song B"]
playlist.include?("Song B")    # true
playlist.count                 # 3

# Comparable: define <=> for natural ordering
class Temperature
  include Comparable
  attr_reader :celsius

  def initialize(c) @celsius = c end
  def <=>(other) celsius <=> other.celsius end
end

t1 = Temperature.new(20)
t2 = Temperature.new(30)
t1 < t2   # true (uses <=>)
t1 == t2  # false

特異メソッドとクラスメソッド

特異メソッドは1つの特定のオブジェクトに属します。クラスメソッドはクラスオブジェクト上の特異メソッドに過ぎません。'class << self' は特異クラス(アイゲンクラス)を開き、複数のクラスメソッドをクリーンに定義します。シングルトンパターンはクラス変数を使用して1つのインスタンスを保持します。特異クラスの理解は Ruby のオブジェクトモデルの鍵です—すべてのオブジェクトは独自のメソッドを保持する特異クラスを持ちます。これによりオブジェクトごとのカスタマイズとメタプログラミングが可能になります。

ruby
# Singleton method: defined on one object only
str = "hello"
def str.shout
  upcase + "!"
end
str.shout  # "HELLO!"
# "world".shout  # NoMethodError (only str has it)

# Class methods are singleton methods on the class
class Calculator
  def self.add(a, b)  # singleton method on Calculator
    a + b
  end
end
Calculator.add(1, 2)  # 3

# Singleton class (eigenclass): where singleton methods live
class Calculator
  class << self  # opens the singleton class
    def multiply(a, b) a * b end
    def divide(a, b) a / b end
  end
end
Calculator.multiply(2, 3)  # 6

# Singleton pattern
class Logger
  @instance = Logger.new
  class << self
    attr_reader :instance
  end
end
Logger.instance.equal?(Logger.instance)  # true

Refinements(スコープ付きモンキーパッチ)

Refinements(Ruby 2.1+)はスコープ付きモンキーパッチを可能にします—既存のクラスにメソッドを追加しますが、refinement を明示的に 'using' した場所のみ有効です。これはグローバルモンキーパッチ(他のコードを壊す可能性がある)より安全です。Refinements はスコープごと(ファイル、クラス、メソッド)にアクティブ化されます。グローバル名前空間を汚染せずに便利なメソッドを追加するのに有用です。パフォーマンスと一部のスコープの癖により普及度は低いですが、コアクラスを拡張する「正しい」方法です。

ruby
# Refinement: limited monkey patching
module StringRefinements
  refine String do
    def shout
      upcase + "!!!"
    end

    def palindrome?
      downcase == downcase.reverse
    end
  end
end

# Without using: shout is undefined
# "hello".shout  # NoMethodError

# With using: refinement is active in this scope
using StringRefinements
"hello".shout        # "HELLO!!!"
"racecar".palindrome?  # true

# Scoped to file or module
class App
  using StringRefinements
  def greet
    "hi".shout  # works here
  end
end
# "hi".shout  # NoMethodError (outside App)

# Safer than global monkey patching
# Refinements only affect code that opts in
19

ブロック・Proc・ラムダの深掘り

ブロックの基礎

ブロックは { } または do...end を使用してメソッドに渡される匿名クロージャです。yield がメソッド内からブロックを呼び出します。ブロックは |var| でパラメータを取れます。block_given? でブロックが渡されたかチェックできます。ブロックは Ruby のイテレータパターンと DSL の基盤です。外側のスコープから変数をキャプチャします(クロージャ)。

ruby
# Block: anonymous chunk of code passed to a method
[1, 2, 3].each { |n| puts n }      # Single-line
[1, 2, 3].each do |n|              # Multi-line
  puts n
end

# yield invokes the block
def greet
  print "Hello, "
  yield          # Calls the block
  puts "!"
end

greet { print "World" }   # Hello, World!

# yield with arguments
def calculate(a, b)
  yield(a + b)
  yield(a * b)
end

calculate(3, 4) { |result| puts "Result: #{result}" }

Proc とラムダ

Proc とラムダはブロックをラップするオブジェクトで、変数に格納して渡すことができます。Proc は緩い引数チェック(余分な引数は無視)を持ち、外側のメソッドから return します。ラムダは厳密な引数チェックを持ち、自身からのみ return します。柔軟性のために Proc を、予測可能な振る舞いの匿名関数にはラムダを使用してください。->() {} 構文(stabby lambda)はワンライナーに簡潔です。

ruby
# Proc: object wrapping a block
my_proc = Proc.new { |x| puts x * 2 }
my_proc.call(5)         # 10
my_proc.(5)             # 10 (shorthand)
my_proc[5]              # 10 (another shorthand)

# Lambda: stricter Proc
my_lambda = lambda { |x| puts x * 2 }
my_lambda = ->(x) { puts x * 2 }   # Stabby lambda

# Difference 1: argument checking
my_proc.call(1, 2, 3)    # OK (ignores extra args)
my_lambda.call(1, 2, 3)  # ArgumentError (wrong number of args)

# Difference 2: return behavior
def proc_test
  p = Proc.new { return 1 }
  p.call
  return 2  # Never reached
end

def lambda_test
  l = lambda { return 1 }
  l.call    # Returns 1 to the lambda, not the method
  return 2  # Reached
end

メソッドオブジェクト

method() メソッドは既存のメソッドをラップした Method オブジェクトを返します。& 演算子は Method または Proc をブロックに変換します(逆も同様)。Symbol#to_proc は :upcase を { |x| x.upcase } に変換し、簡潔な &:symbol 構文を可能にします。これは短いブロック操作の慣用的な Ruby です。Method オブジェクトはレシーバにバインドされているため、渡されても self を記憶します。

ruby
# Convert method to Proc with method()
class Calculator
  def add(a, b) a + b end
  def multiply(a, b) a * b end
end

calc = Calculator.new
add_proc = calc.method(:add)
puts add_proc.call(2, 3)   # 5

# Pass method as block with & operator
def apply_op(a, b, op)
  op.call(a, b)
end

puts apply_op(2, 3, calc.method(:add))       # 5
puts apply_op(2, 3, calc.method(:multiply))  # 6

# Symbol#to_proc
words = ["hello", "world"]
upcased = words.map(&:upcase)   # ["HELLO", "WORLD"]
# Equivalent to: words.map { |w| w.upcase }

# Common with &:map(&:to_i), select(&:positive?), sort_by(&:length)

クロージャと変数

クロージャは値ではなく参照で変数をキャプチャします。複数のクロージャが状態を共有できます(カウンターの例)。ブロックローカル変数(パラメータの ; の後に宣言)は外側の変数を変更せずにシャドウします。これによりアキュムレータ、ジェネレータ、メモ化などの関数型パターンが可能になります。注意:参照を保持するクロージャは解放されないとメモリリークを引き起こす可能性があります。プライベート状態のカプセル化にこのパターンを使用してください。

ruby
# Blocks/Procs/Lambdas capture variables (closures)
counter = 0
increment = lambda { counter += 1 }

increment.call
increment.call
puts counter   # 2

# Multiple closures sharing state
def make_counters
  count = 0
  [
    lambda { count += 1 },
    lambda { count },
    lambda { count = 0 }
  ]
end

inc, get, reset = make_counters
inc.call; inc.call
puts get.call   # 2
reset.call
puts get.call   # 0

# Block-local variables (shadow outer)
x = 10
[1, 2, 3].each do |y; x|   # x is block-local
  x = y * 2
end
puts x   # 10 (unchanged)

カスタムイテレータ

Enumerable をインクルードして each を実装すると、クラスにすべてのイテレータメソッド(map、select、reduce、sort など)が無料で得られます。ブロックを &block で渡して呼び出すか、yield を使用します。& 演算子はブロックを Proc に変換し、逆も同様です。これがカスタムコレクションをイテラブルにする慣用的な方法です。前方反復には each を実装し、双方向には reverse_each を追加してください。

ruby
class Tree
  include Enumerable

  def initialize(value, children = [])
    @value = value
    @children = children
  end

  def each(&block)
    block.call(@value)
    @children.each { |child| child.each(&block) }
  end
end

tree = Tree.new(1, [
  Tree.new(2, [Tree.new(4), Tree.new(5)]),
  Tree.new(3)
])

tree.each { |v| puts v }            # 1 2 4 5 3
puts tree.map { |v| v * 2 }.inspect # [2, 4, 8, 10, 6]
puts tree.select(&:even?).inspect   # [2, 4]
puts tree.reduce(:+)                # 15

# Including Enumerable gives you map, select, reduce, etc.
20

メタプログラミング

動的メソッド

method_missing は未定義メソッドへの呼び出しをインターセプトし、動的ディスパッチを可能にします。常に respond_to_missing? をオーバーライドして一致させてください。define_method はランタイムにメソッドを作成し、類似メソッド(ActiveRecord の find_by_* など)の生成に有用です。instance_variable_get/set が名前でインスタンス変数にアクセスします。メタプログラミングは控えめに使用してください—コードの理解とデバッグが難しくなります。可能な場合は明示的な定義を優先してください。

ruby
class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  # Method missing: catch undefined method calls
  def method_missing(name, *args, &block)
    if name.to_s =~ /^(.*)_with_prefix$/
      attr_name = $1
      "PREFIX_#{send(attr_name)}"
    else
      super
    end
  end

  def respond_to_missing?(name, include_private = false)
    name.to_s =~ /^(.*)_with_prefix$/ || super
  end
end

p = Person.new("Alice", 30)
puts p.name_with_prefix   # PREFIX_Alice

# Define methods dynamically
class Person
  [:home, :work, :mobile].each do |type|
    define_method("#{type}_phone") do
      instance_variable_get("@#{type}_phone")
    end

    define_method("#{type}_phone=") do |value|
      instance_variable_set("@#{type}_phone", value)
    end
  end
end

オープンクラスとモンキーパッチ

Ruby のクラスはオープンです:String のような組み込みを含む任意のクラスにメソッドを追加できます。これは強力ですが危険です(モンキーパッチが他のコードを壊す可能性があります)。Refinements(Ruby 2.1+)はスコープ付きモンキーパッチを提供します:モジュールを使用するファイル/クラス内でのみ適用されます。より安全なメタプログラミングのためにグローバルモンキーパッチより refinements を優先してください。パッチを明確に文書化し、コアの動作を変更しないでください。

ruby
# Reopen existing classes (monkey patching)
class String
  def shout
    upcase + "!"
  end

  def word_count
    split.size
  end
end

puts "hello world".shout       # HELLO WORLD!
puts "one two three".word_count # 3

# Refinements: scoped monkey patches
module ShoutRefinement
  refine String do
    def shout
      upcase + "!"
    end
  end
end

class MyClass
  using ShoutRefinement

  def greet(name)
    "hello #{name}".shout   # Works here
  end
end

# "test".shout  # NoMethodError (refinement not active)

フックとコールバック

Ruby はライフサイクルフックを提供します:inherited(サブクラス作成)、included(モジュールインクルード)、prepended(モジュール prepend)、method_added(メソッド定義)、method_removed、method_undefined。これらによりフレームワークがクラス変更に自動的に反応できます。ActiveRecord は属性追跡に、Rails はルーティングにこれらを使用します。クラスレベルのイベントにはクラスメソッド(self.inherited)として、メソッドイベントにはインスタンスメソッドとしてフックをオーバーライドしてください。

ruby
class Observable
  # Hook: called when class is subclassed
  def self.inherited(subclass)
    puts "#{subclass} inherits from #{self}"
  end

  # Hook: called when a module is included
  def self.included(base)
    puts "#{self} included in #{base}"
  end

  # Hook: called when a method is added
  def method_added(name)
    puts "Added method: #{name}"
  end

  # Hook: called when method is undefined
  def method_removed(name)
    puts "Removed method: #{name}"
  end
end

class Child < Observable
  def my_method; end
end
# Output:
# Child inherits from Observable
# Added method: my_method

# Other hooks: method_undefined, extended, prepended

eval と Binding

eval は文字列を Ruby コードとして実行しますが、入力が信頼できない場合は危険です(コードインジェクション)。binding は現在の実行コンテキスト(変数、self)を後の eval のためにキャプチャします。class_eval はクラスコンテキストでコードを実行します(メソッドを定義)。instance_eval は self をレシーバに変更します。DSL とコード生成に使用しますが、ユーザー入力での eval は避けてください。動的コードにはブロックと define_method を優先してください。eval が避けられない場合は入力を常にサニタイズしてください。

ruby
# eval: execute a string as Ruby code
result = eval("1 + 2 * 3")
puts result   # 7

# eval with binding (context)
def eval_in_context(code)
  x = 10
  binding   # Returns a Binding object capturing local variables
end

b = eval_in_context("binding")
puts eval("x * 2", b)   # 20

# class_eval: execute code in class context
String.class_eval do
  def palindrome?
    self == reverse
  end
end

puts "racecar".palindrome?   # true

# instance_eval: execute in instance context
"hello".instance_eval do
  puts length   # 5 (self is the string)
end

# NEVER eval user input (security risk)

リフレクションとイントロスペクション

Ruby は豊富なリフレクションを提供します:methods はすべてのメソッドをリストし、instance_variables はインスタンス変数をリストし、ancestors は継承チェーンを表示します。parameters はメソッドのパラメータ名と型を明らかにします。respond_to? はオブジェクトがメソッドに応答するかチェックします。デバッグ、シリアライズ(属性の検査)、汎用ツール(ORM など)の構築にリフレクションを使用してください。リフレクションの過剰使用は避けてください—型安全性をバイパスし、コードを追いにくくします。

ruby
class User
  attr_accessor :name, :email

  def initialize(name, email)
    @name = name
    @email = email
  end

  def save; end
  def valid?; true; end
end

u = User.new("Alice", "[email protected]")

# Inspect methods
puts u.methods - Object.methods    # [:name, :email, :save, :valid?, ...]
puts u.public_methods(false)       # Only this class's methods
puts User.instance_methods(false)  # [:save, :valid?, ...]

# Inspect variables
puts u.instance_variables.inspect   # [:@name, :@email]
puts u.instance_variable_get(:@name) # Alice

# Inspect class
puts User.ancestors.inspect
puts User.instance_method(:save).parameters

# Check if responds to method
puts u.respond_to?(:save)   # true
puts u.is_a?(User)          # true
21

テスト(RSpec/Minitest)

RSpec の基礎

RSpec は BDD スタイルのテストフレームワークです。describe が関連テストをグループ化し、context が条件ごとにグループ化し、it が単一テストを定義します。let が遅延メモ化変数を作成します。expect(...).to matcher がアサーション構文です。一般的なマッチャ:eq、be_valid、include、raise_error。subject + is_expected がボイラープレートを削減します。rspec(すべて)または rspec path/to/spec(特定)で実行します。テストデータにはフィクスチャの代わりにファクトリ(FactoryBot)を使用してください。

ruby
# spec/spec_helper.rb
require 'rspec'
require_relative '../lib/my_app'

# spec/models/user_spec.rb
require 'spec_helper'

RSpec.describe User do
  # Setup with let (lazy, memoized)
  let(:user) { User.new(name: 'Alice', email: '[email protected]') }

  describe '#name' do
    it 'returns the name' do
      expect(user.name).to eq('Alice')
    end
  end

  describe '#valid?' do
    context 'with valid attributes' do
      it 'is valid' do
        expect(user).to be_valid
      end
    end

    context 'without name' do
      let(:user) { User.new(email: '[email protected]') }
      it 'is invalid' do
        expect(user).not_to be_valid
        expect(user.errors[:name]).to include("can't be blank")
      end
    end
  end

  # Multiple examples
  describe '#age' do
    subject { user.age }
    it { is_expected.to be >= 0 }
  end
end

# Run: rspec spec/models/user_spec.rb
# Run all: rspec

RSpec モックとスタブ

スタブ(allow)がメソッドの戻り値を置き換え、モック(expect)がメソッドが呼ばれたことを検証します。double がテストダブル(フェイクオブジェクト)を作成します。and_return が戻り値を設定し、and_raise がエラーをシミュレートし、with が期待される引数を設定します。モックは控えめに使用してください—過剰なモックはテストを脆くします。実装ではなく振る舞いをテストしてください。可能な場合は実オブジェクトを優先し、外部サービス(API、メール、決済ゲートウェイ)にはモックを使用してください。

ruby
RSpec.describe PaymentProcessor do
  let(:gateway) { double('PaymentGateway') }
  let(:processor) { PaymentProcessor.new(gateway) }

  describe '#charge' do
    it 'charges the gateway' do
      # Stub: replace method return value
      allow(gateway).to receive(:charge).and_return(success: true)

      result = processor.charge(100)
      expect(result[:success]).to be true
    end

    it 'raises on gateway error' do
      allow(gateway).to receive(:charge)
        .and_raise(GatewayError, 'Network failure')

      expect { processor.charge(100) }
        .to raise_error(GatewayError, /Network/)
    end

    it 'verifies the gateway was called' do
      # Mock: expect method to be called
      expect(gateway).to receive(:charge).with(100)
      processor.charge(100)
    end

    it 'receives multiple calls' do
      allow(gateway).to receive(:charge).and_return(true, false, true)
      expect(gateway.charge(1)).to be true
      expect(gateway.charge(2)).to be false
    end
  end
end

Minitest

Minitest は Ruby のデフォルトテストライブラリです(Ruby に同梱)。Unit スタイル(assert/refute)と Spec スタイル(must_equal)の両方の構文をサポートします。setup が各テストの前に実行されます。アサーション:assert_equal、assert_nil、assert_raises、assert_includes。Minitest は RSpec より高速で依存関係が少ないです。モックには Minitest::Mock を、より多くの機能には Mocha gem を使用してください。Rails はデフォルトで Minitest を使用します(ActiveSupport::TestCase)。

ruby
# test/test_helper.rb
require 'minitest/autorun'
require_relative '../lib/my_app'

# test/models/user_test.rb
require 'test_helper'

class UserTest < Minitest::Test
  def setup
    @user = User.new(name: 'Alice', email: '[email protected]')
  end

  def test_name
    assert_equal 'Alice', @user.name
  end

  def test_valid_with_attributes
    assert @user.valid?
  end

  def test_invalid_without_name
    @user.name = nil
    refute @user.valid?
    assert_includes @user.errors[:name], "can't be blank"
  end

  def test_raises_on_error
    assert_raises(ArgumentError) { User.new! }
  end
end

# Spec-style Minitest
class UserSpec < Minitest::Spec
  let(:user) { User.new(name: 'Alice') }

  it 'has a name' do
    _(user.name).must_equal 'Alice'
  end
end

# Run: rake test
# Run specific: ruby -Itest test/models/user_test.rb

テストデータとファクトリ

FactoryBot がデフォルト値、バリエーション用のトレイト、関連を持つテストオブジェクトを生成します。create は DB に永続化し、build はしません。トレイトは合成可能です(factory :admin_with_posts, traits: [:admin, :with_posts])。ファクトリはフィクスチャより柔軟ですが遅いです。フィクスチャ(YAML)は高速ですが柔軟性に欠けます。ニーズに基づいて選択してください:シンプルなデータにはフィクスチャ、複雑なリレーションにはファクトリ。トレイトが多すぎるファクトリは避けてください—焦点を絞ってください。

ruby
# FactoryBot (gem 'factory_bot')
# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { 'Alice' }
    email { '[email protected]' }
    age { 30 }

    trait :admin do
      role { 'admin' }
    end

    trait :with_posts do
      after(:create) do |user|
        create_list(:post, 3, user: user)
      end
    end

    factory :admin_user, traits: [:admin]
    factory :user_with_posts, traits: [:with_posts]
  end
end

# Usage in specs
let(:user) { create(:user) }            # Saved to DB
let(:admin) { create(:admin_user) }
let(:user_with_posts) { create(:user_with_posts) }
let(:unsaved_user) { build(:user) }     # Not saved

# Fixtures (alternative)
# test/fixtures/users.yml
# alice:
#   name: Alice
#   email: [email protected]

# In tests: users(:alice)

インテグレーションとシステムテスト

リクエストスペックは HTTP 経由でフルスタック(ルーティング、コントローラ、モデル)をテストします。システムスペック(Capybara)は実ブラウザを駆動し、JavaScript インタラクションをテストします。API エンドポイントにはリクエストスペックを、ユーザーフローにはシステムスペックを使用してください。have_http_status がレスポンスコードをチェックします。visit/fill_in/click_button がユーザーアクションをシミュレートします。システムテストは遅いですがインテグレーションバグを捕捉します。JavaScript 駆動ページには js: true でシステムテストを実行してください。

ruby
# spec/requests/users_spec.rb (integration)
require 'spec_helper'

RSpec.describe 'Users API', type: :request do
  describe 'GET /users' do
    before { create_list(:user, 3) }

    it 'returns all users' do
      get '/users', as: :json
      expect(response).to have_http_status(200)
      expect(JSON.parse(response.body).size).to eq(3)
    end
  end

  describe 'POST /users' do
    it 'creates a user' do
      post '/users', params: { user: { name: 'Bob', email: '[email protected]' } }
      expect(response).to have_http_status(201)
      expect(User.last.name).to eq('Bob')
    end
  end
end

# System tests (browser automation)
# spec/system/login_spec.rb
require 'spec_helper'

RSpec.describe 'Login', type: :system do
  it 'logs in a user' do
    user = create(:user, password: 'password123')
    visit login_path
    fill_in 'Email', with: user.email
    fill_in 'Password', with: 'password123'
    click_button 'Log In'
    expect(page).to have_content('Welcome')
  end
end
22

スレッドとファイバ

スレッドの基礎

Ruby(MRI)のスレッドは VM がスケジュールするグリーンスレッドです—GIL(グローバルインタプリタロック)のため真に並列には実行されません。ただし、I/O 並行性(ネットワーク、ファイル操作)には有用です。join がスレッドの完了を待ちます。value が戻り値を取得します。CPU バウンドな並列処理には複数プロセス(fork、Sidekiq)または JRuby(GIL なし)を使用してください。メインスレッド終了時に殺されるのを避けるため常にスレッドを join してください。

ruby
# Create and run threads
threads = (1..3).map do |i|
  Thread.new(i) do |n|
    puts "Thread #{n} started"
    sleep(1)
    puts "Thread #{n} finished"
  end
end

# Wait for all threads
threads.each(&:join)
puts "All done"

# Thread with return value
thread = Thread.new { 1 + 2 }
result = thread.value   # 3 (waits for completion)

# Thread status
thread = Thread.new { sleep(1) }
puts thread.status   # 'run', 'sleep', false (finished), nil (error)
thread.join

# Current thread
Thread.current
Thread.list   # All threads
Thread.main   # Main thread

スレッド同期

Mutex.synchronize は一度に1つのスレッドのみがブロックを実行することを保証し、競合状態を防ぎます。Queue はスレッドセーフな FIFO です—プロデューサが push、コンシューマが pop(空ならブロック)。ConditionVariable がスレッドを調整します:wait がミューテックスを解放してスリープし、signal/broadcast が待機中のスレッドを起こします。共有される可変状態には常に同期を使用してください。デッドロックはスレッドが互いに待機すると発生します—一貫した順序でロックを取得してください。

ruby
# Mutex: mutual exclusion
counter = 0
mutex = Mutex.new

threads = 10.times.map do
  Thread.new do
    1000.times do
      mutex.synchronize { counter += 1 }
    end
  end
end
threads.each(&:join)
puts counter   # 10000 (without mutex, would be less)

# Queue: thread-safe FIFO
require 'thread'
queue = Queue.new

producer = Thread.new do
  5.times { |i| queue << "item #{i}" }
  queue << :done
end

consumer = Thread.new do
  loop do
    item = queue.pop
    break if item == :done
    puts "Processed: #{item}"
  end
end

[producer, consumer].each(&:join)

# ConditionVariable: signal between threads
mutex = Mutex.new
cv = ConditionVariable.new

ファイバ

ファイバは協調的軽量スレッドです:プリエンプトされるのではなく手動で制御を譲ります。resume がファイバを開始/再開し、Fiber.yield が一時停止して値を返します。ファイバはジェネレータ、遅延評価、状態マシンの解析に有用です。スレッドと異なり一度に1つのファイバのみが実行されるため、同期は不要です。ファイバはスレッドより安価ですが複数コアを使用できません。I/O マルチプレクシング(EventMachine、Async)に使用してください。

ruby
# Fiber: cooperative concurrency (manual scheduling)
fiber = Fiber.new do
  puts "Fiber started"
  Fiber.yield "first yield"
  puts "Fiber resumed"
  Fiber.yield "second yield"
  puts "Fiber ending"
  "fiber done"
end

puts fiber.resume   # "Fiber started", returns "first yield"
puts fiber.resume   # "Fiber resumed", returns "second yield"
puts fiber.resume   # "Fiber ending", returns "fiber done"

# Generator pattern with Fiber
def fibonacci
  Fiber.new do
    a, b = 0, 1
    loop do
      Fiber.yield a
      a, b = b, a + b
    end
  end
end

fib = fibonacci
10.times { print fib.resume, " " }
# 0 1 1 2 3 5 8 13 21 34

Async と並行 Ruby

async gem は内部でファイバを使用したモダンな非同期 I/O を提供し、高並行性のネットワークコードを可能にします。concurrent-ruby はスレッドセーフな抽象化を提供します:Future(非同期結果)、Promise(チェーン可能)、スレッドプール、アトミック変数。I/O バウンドな作業(HTTP、データベース)には async を、CPU バウンドな作業にはスレッドプールを使用してください。ハングを避けるため常にプールをシャットダウンしてください。これらの gem は MRI の GIL を回避して実用的な並行性を実現します。

ruby
# Async gem for modern async I/O
# gem install async
require 'async'
require 'async/http/internet'

Async do
  internet = Async::HTTP::Internet.new

  # Run requests concurrently
  tasks = 3.times.map do |i|
    Async do
      response = internet.get("https://httpbin.org/delay/#{i}")
      puts "Request #{i}: #{response.status}"
    end
  end

  tasks.each(&:wait)
end

# Concurrent Ruby (gem 'concurrent-ruby')
require 'concurrent'

# Future: async computation
future = Concurrent::Future.execute { sleep(1); 42 }
puts future.value   # 42 (blocks until ready)

# Promise chain
Concurrent::Promise.execute { 1 }
  .then { |v| v + 1 }
  .then { |v| v * 2 }
  .then { |v| puts v }   # 4

# Thread pool
pool = Concurrent::FixedThreadPool.new(4)
10.times do |i|
  pool.post { puts "Task #{i} on #{Thread.current.object_id}" }
end
pool.shutdown

Ractor(Ruby 3.0+)

Ractor(Ruby 3.0+)は GIL を回避して真の並列処理を提供します。各 Ractor は独自のヒープを持つためデータ競合がありません。通信はメッセージ(send/receive、take)経由です。送信されるオブジェクトはコピー(または Ractor.move で移動)され、分離が維持されます。フリーズされたオブジェクトは共有可能です。Ractor は並列 Ruby の未来ですが制限があります:ほとんどの gem はまだ Ractor セーフではありません。分離が許容される CPU バウンドな並列計算に使用してください。

ruby
# Ractor: true parallelism (no GIL)
# Ruby 3.0+ feature for thread-safe parallel execution

ractor = Ractor.new do
  value = receive    # Receive from main
  value * 2
end

ractor.send(21)
puts ractor.take     # 42

# Parallel computation
ractors = (1..4).map do |i|
  Ractor.new(i) do |n|
    sleep(1)
    n * n
  end
end

results = Ractor.select(*ractors)
# Or: ractors.map(&:take)  # [1, 4, 9, 16]

# Ractor restrictions:
# - Cannot share mutable objects
# - Communication only via messages
# - Copy or move semantics for sending

# Shared frozen objects are OK
SHARED = Ractor.new { [1, 2, 3].freeze }
# Multiple ractors can read SHARED safely
23

エンコーディングと I/O

文字列エンコーディング

すべての Ruby 文字列はエンコーディングを持ちます(デフォルト UTF-8)。bytesize はバイト数、length は文字数です(マルチバイトエンコーディングでは異なります)。force_encoding はバイトを変換せずにエンコーディングタグを変更します(実際のエンコーディングが分かっている場合に使用)。encode はエンコーディング間を変換します。外部データを処理する前に常に valid_encoding? でチェックしてください。マジックコメント # encoding: utf-8 でソースファイルエンコーディングを設定します(Ruby 2.0+ では UTF-8 がデフォルトですが)。

ruby
# Ruby strings have encodings
s = "hello"
puts s.encoding              # <Encoding:UTF-8>
puts s.bytesize              # 5
puts s.length               # 5

# Multibyte characters
japanese = "こんにちは"
puts japanese.encoding       # UTF-8
puts japanese.bytesize       # 15 (3 bytes per char)
puts japanese.length         # 5 (5 characters)

# Force encoding (interpret bytes differently)
bytes = "caf\u00e9".force_encoding('ASCII-8BIT')
puts bytes.encoding          # ASCII-8BIT

# Encode (convert to different encoding)
utf8 = "café"
latin1 = utf8.encode('ISO-8859-1')
puts latin1.encoding         # ISO-8859-1

# Invalid bytes
bad = "\xFF\xFE".force_encoding('UTF-8')
puts bad.valid_encoding?     # false
fixed = bad.encode('UTF-8', invalid: :replace, replace: '?')

ファイル I/O

File.read はファイル全体をメモリに読み込みます、File.foreach は行ごとに読み込みます(大きなファイルにメモリ効率的)。File.write は上書きし、mode 'a' は追加、'r+' は読み書きします。バイナリモード('rb'、'wb')はエンコーディング変換を防ぎます。ファイルが確実に閉じられるよう常にブロック形式(File.open)を使用してください。FileUtils がより高レベルな操作(cp、mv、mkdir_p)を提供します。読み込み前に File.exist? で存在をチェックしてください。

ruby
# Read entire file
content = File.read('data.txt')

# Read line by line (memory efficient)
File.foreach('large.log') do |line|
  puts line.chomp
end

# Write to file
File.write('output.txt', 'Hello, World!')

# Append
File.open('log.txt', 'a') do |f|
  f.puts "New log entry"
end

# Read/Write modes
File.open('file.txt', 'r+') do |f|
  content = f.read
  f.rewind
  f.write("Updated: " + content)
end

# Binary mode
File.open('image.png', 'rb') do |f|
  bytes = f.read
  puts bytes.bytesize
end

# File operations
File.exist?('file.txt')
File.size('file.txt')
File.mtime('file.txt')
File.delete('file.txt')
File.rename('old.txt', 'new.txt')
FileUtils.cp('a.txt', 'b.txt')   # require 'fileutils'

StringIO と Tempfile

StringIO は文字列を IO ライクなインターフェースでラップし、ディスクに触れずにファイル操作をテストするのに有用です。Tempfile はブロック終了時(Tempfile.create)またはリンク解除時(Tempfile.new)に自動削除される一時ファイルを作成します。メモリに収まらない大きなデータや外部プログラムにパス経由で渡す場合に Tempfile を使用してください。ensure ブロックでクリーンアップを常に保証してください。StringIO は I/O コードのユニットテストに最適です。

ruby
require 'stringio'
require 'tempfile'

# StringIO: in-memory file-like object
io = StringIO.new
io.puts "Hello"
io.puts "World"
io.rewind
puts io.read   # "Hello\nWorld\n"

# Use like a file
io = StringIO.new("line1\nline2\nline3")
io.each_line { |line| puts line.chomp }

# Tempfile: auto-deleted temporary file
Tempfile.create('myapp') do |f|
  f.write('temporary data')
  f.rewind
  puts f.read
end   # File automatically deleted

# Tempfile with explicit cleanup
temp = Tempfile.new('myapp')
begin
  temp.write('data')
  temp.rewind
  # Use temp.path to pass to external programs
ensure
  temp.close
  temp.unlink
end

ネットワーク I/O

TCPSocket/TCPServer は低レベル TCP アクセスを提供します。Net::HTTP は標準 HTTP クライアント(組み込み)です。複雑な HTTP ニーズ(セッション、Cookie、リトライ)には httparty または faraday gem を使用してください。HTTPS には常に use_ssl = true を設定してください。高性能 HTTP の場合は async-http または typhoeus を検討してください。URI が URL を安全にパースします。堅牢なネットワークコードのために Net::ReadTimeout と Errno::ECONNREFUSED を処理してください。

ruby
require 'socket'
require 'net/http'
require 'uri'

# TCP Client
TCPSocket.open('example.com', 80) do |socket|
  socket.write("GET / HTTP/1.0\r\n\r\n")
  puts socket.read
end

# TCP Server
server = TCPServer.new(2000)
loop do
  client = server.accept
  client.puts "Hello from server"
  client.close
end

# HTTP Client
uri = URI('https://api.example.com/data')
response = Net::HTTP.get_response(uri)
puts response.code          # 200
puts response.body

# HTTP POST
response = Net::HTTP.post_form(uri, key: 'value')

# HTTP with custom headers
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer token'
response = http.request(request)

STDIO とパイプライン

$stdout、$stderr、$stdin はグローバル IO オブジェクトです(オリジナルには定数 STDOUT/STDERR/STDIN を使用)。グローバルを再割り当てしてリダイレクトします。StringIO がテスト用に出力をキャプチャします。Open3.popen3 がサブプロセスの stdin/stdout/stderr を完全に制御します。Open3.pipeline がシェルパイプのようにコマンドをチェーンします。Capture3 が stdout、stderr、status を返します。サブプロセスに EOF をシグナルするため常に stdin を閉じてください。終了ステータスを取得するには wait.value を使用してください。

ruby
# Standard IO
$stdout.puts "To stdout"
$stderr.puts "To stderr"
input = $stdin.gets   # Read from stdin

# Redirect IO
$stdout = File.open('log.txt', 'w')
puts "This goes to file"
$stdout = $stdout   # Restore (or use STDOUT constant)

# Capture output
captured = StringIO.new
original = $stdout
$stdout = captured
puts "Captured"
$stdout = original
puts captured.string   # "Captured\n"

# Open3: run commands with full IO control
require 'open3'

Open3.popen3('grep hello') do |stdin, stdout, stderr, wait|
  stdin.puts "hello world"
  stdin.puts "goodbye"
  stdin.close
  puts stdout.read      # "hello world"
  puts wait.value.exitstatus
end

# Pipeline
output = Open3.pipeline('ls', 'grep .rb', 'wc -l')
24

パターンマッチング(3.0+)

基本パターンマッチング

パターンマッチング(in キーワード)は配列とハッシュを分解し、変数をバインドします。=> が一致した値を変数にバインドします。*rest が残りの配列要素をキャプチャします。ハッシュマッチングは部分一致です:余分なキーは無視されます。| が複数パターンに一致します。case/in が主要な形式で、ワンライナー形式は expression in pattern です。パターンマッチングは構造化データ(JSON、AST)の解析や複雑な if-else チェーンの置き換えに特に強力です。

ruby
# in: pattern matching (Ruby 2.7+, stable in 3.0)
case [1, 2, 3]
in [1, *rest]
  puts "Starts with 1, rest: #{rest}"
end

case {name: 'Alice', age: 30}
in {name: String => name, age: Integer => age}
  puts "#{name} is #{age}"
end

# Multiple patterns with |
case status
in :success | :ok
  puts "Good"
else
  puts "Other"
end

# Array patterns
case [1, 2, 3, 4]
in [_, _, *rest]
  puts "Rest: #{rest}"   # [3, 4]
end

# Hash patterns (partial match by default)
case {a: 1, b: 2, c: 3}
in {a: Integer}
  puts "Has a"   # Matches (ignores b, c)
end

変数バインディングとガード

=> が一致した値を変数にバインドします。ピン演算子(^var)は変数の現在の値に対してマッチします(バインドではなく)。ガード(if/unless)が条件を追加します。配列パターンは任意の位置の * スプラットをサポートします。Find パターン [*, target, *] は配列内の要素を検索します。パターンマッチングは複雑なデータ抽出に宣言的で簡潔です。パターンでバインドされた変数は case 文の後に利用可能です。

ruby
user = {name: 'Alice', age: 30, role: :admin}

case user
in {name: String => name, age: Integer => age, role: :admin} if age > 18
  puts "#{name} is an adult admin"
end

# Pin operator (^): match against variable value
expected = 'Alice'
case user
in {name: ^expected}
  puts "Matched expected name"
end

# Array destructuring with binding
case [1, 2, 3]
in [first, *middle, last]
  puts "First: #{first}, Middle: #{middle}, Last: #{last}"
end

# Find pattern (search within array)
case [1, 2, 3, 4, 5]
in [*, 3 => three, *]
  puts "Found 3: #{three}"
end

クラスと型パターン

クラスパターンは型でマッチします。カスタムクラスは deconstruct(配列パターン)と deconstruct_keys(ハッシュパターン)を実装して分解をサポートします。パターン Class[args] は deconstruct を使用し、Class(key:) は deconstruct_keys を使用します。多くの組み込みクラス(Time、Date、MatchData)が分解をサポートします。これによりドメインオブジェクト上の表現力豊かなマッチングが可能になります。クラスをパターンマッチ可能にするにはこれらのメソッドを実装してください。

ruby
# Match by class
case value
in Integer
  puts "Integer"
in String
  puts "String"
in Array
  puts "Array"
end

# Class with destructuring
class Point
  attr_reader :x, :y
  def initialize(x, y) @x, @y = x, y end
  def deconstruct = [@x, @y]              # Array pattern
  def deconstruct_keys(keys) = {x: @x, y: @y}  # Hash pattern
end

p = Point.new(1, 2)
case p
in Point[x, y]
  puts "Point at (#{x}, #{y})"
end

case p
in Point(x:, y:)
  puts "X: #{x}, Y: #{y}"
end

# Built-in classes support destructuring
case Time.now
in Time(hour: h) if h < 12
  puts "Morning"
end

ワンラインパターンマッチング

ワンライナー形式(expr in pattern)は NoMatchingPatternError を発生させる代わりに true/false を返します。一致成功時に変数をバインドします。ガード句と条件付き抽出に有用です。右向き形式(pattern => var)はマッチ全体をバインドします。ワンライナーマッチングはシンプルなケースに簡潔です、複雑な多分岐ロジックには case/in を使用してください。失敗したマッチでバインドされた変数は nil のままです。

ruby
# One-line form (Ruby 3.0+)
{status: :ok, data: 42} in {status: :ok, data: Integer => data}
puts data   # 42

# Returns true/false (does not raise)
[1, 2, 3] in [Integer, Integer, Integer]   # true
[1, 2, 3] in [String, *, *]                # false

# Useful for conditionals
if user in {role: :admin}
  puts "Admin access"
end

# Guard clauses
def process(data)
  return unless data in {type: String, value: Integer}
  # ...
end

# Extracting from JSON
json = JSON.parse('{"user":{"name":"Alice","age":30}}')
json in {"user" => {"name" => String => name, "age" => Integer => age}}
puts "#{name}, #{age}"   # Alice, 30

実践的なユースケース

パターンマッチングは以下に優れています:構造化データ(JSON、XML)の解析、状態マシン、AST トラバーサル、データ形状によるディスパッチ。冗長な if-else チェーンを宣言的パターンで置き換えます。else 句が予期しないフォーマットを処理します。複雑な条件にはガードと組み合わせてください。パターンマッチングは複雑なデータ処理のコードをより読みやすく保守しやすくします。これは Ruby 3 のクリーンで表現力豊かなコードのための最も強力な機能の1つです。

ruby
# 1. Parse JSON API responses
def handle_response(response)
  case JSON.parse(response)
  in {"status" => "success", "data" => Array => items}
    items.each { |item| process(item) }
  in {"status" => "error", "message" => String => msg}
    raise "API error: #{msg}"
  in {"status" => "error", "code" => Integer => code} if code >= 500
    retry_request
  else
    raise "Unknown response format"
  end
end

# 2. State machines
case state
in :idle, event: :start
  transition_to :running
in :running, event: :pause
  transition_to :paused
in :paused, event: :resume
  transition_to :running
in :running | :paused, event: :stop
  transition_to :idle
end

# 3. AST traversal
def evaluate(node)
  case node
  in {type: 'number', value: n}
    n
  in {type: 'add', left:, right:}
    evaluate(left) + evaluate(right)
  in {type: 'mul', left:, right:}
    evaluate(left) * evaluate(right)
  end
end
25

メタプログラミング

send と define_method

define_method はメソッドを動的に作成します。send は名前でメソッドを呼び出します(private も含む)。public_send は可視性を尊重します。DSL とボイラープレート削減に有用です。セキュリティ問題を避けるためユーザー入力に注意してください。メタプログラミングは強力ですが、コードを理解しにくくする可能性があります。

ruby
class Foo
  define_method(:greet) do |name|
    "Hello, #{name}"
  end
end
Foo.new.greet("Alice")  # "Hello, Alice"
# Dynamic method call
Foo.new.send(:greet, "Bob")  # "Hello, Bob"
Foo.new.public_send(:greet, "Bob")  # Respects visibility

method_missing

method_missing は未定義メソッドへの呼び出しをインターセプトします。動的ディスパッチと DSL に有用です。常に respond_to_missing? をオーバーライドして一致させてください。遅く、バグを隠す可能性があります。メソッドのセットが既知の場合は define_method を優先してください。ActiveRecord は属性アクセサにこれを使用しています。

ruby
class DynamicHash
  def method_missing(name, *args)
    if name.to_s.end_with?('=')
      self[name.to_s.chomp('=')] = args.first
    else
      self[name.to_s]
    end
  end
  def respond_to_missing?(name, include_private = false)
    true
  end
end

eval

eval は文字列を Ruby コードとして実行します。非常に強力ですが危険です。信頼できない入力を eval しないでください(コードインジェクション)。特定のコンテキストには binding.eval を使用します。安全な評価には Ripper のようなパーサーやサンドボックスを使用してください。ほとんどのユースケースにはより安全な代替手段があります。

ruby
# Execute a string as Ruby code
result = eval("1 + 2 * 3")  # 7
# Dynamic method definition
eval("def dynamic_method; 42; end")
dynamic_method  # 42
# WARNING: never eval untrusted input!

オープンクラス

Ruby のクラスはオープンです:組み込みを含む任意のクラスにメソッドを追加できます。これはモンキーパッチと呼ばれます。クイックフィックスに強力ですが、競合や混乱を引き起こす可能性があります。スコープ付き変更には refinements を使用してください。変更を明確に文書化してください。モンキーパッチよりコンポジションを優先してください。

ruby
class String
  def shout
    upcase + "!"
  end
end
"hello".shout  # "HELLO!"
# Monkey patching: modifying existing classes
# Use sparingly, can cause conflicts

クラスマクロ

クラスマクロは他のメソッドを定義するクラスメソッドです。has_many、belongs_to、attr_accessor が例です。内部で define_method を使用します。これが Rails が動的メソッドを作成する方法です。RSpec や Sinatra のような DSL がこのパターンを使用します。コードを宣言的で読みやすくします。

ruby
class ActiveRecord::Base
  def self.has_many(name)
    define_method(name) { [] }
  end
end
class Post < ActiveRecord::Base
  has_many :comments
end
Post.new.comments  # []

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.