基礎
変数と型
Ruby は動的型付けです—変数に型宣言は不要です。型を検査するには .class を、型チェックには is_a? を使用してください。数値やブール値を含めすべてがオブジェクトです。
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つのコピーしか存在しません。アイデンティティがコンテンツより重要なハッシュキー、メソッド名、識別子に使用してください。繰り返し使用には文字列よりメモリ効率が良いです。
status = :active
puts status.class # Symbol
puts status.to_s # "active"
# Symbols are immutable, reusable strings
hash = { name: "Alice", status: :active }
puts hash[:status] # activeNil と真偽性
Ruby では nil と false のみが偽です—それ以外(0、''、[] を含む)はすべて真です。これは 0 が偽となる多くの言語とは異なります。デフォルトには || を、nil の具体的なチェックには nil? を使用してください。
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 な降格が必要な場合 は寛容な変換を使用してください。
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 式を埋め込めます。単一引用符文字列はリテラルです(\\ と \' を除き補間やエスケープなし)。補間やエスケープシーケンスが必要な場合は二重引用符を使用してください。
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文字列
一般的な文字列メソッド
Ruby の文字列には豊富なメソッドがあります。ほとんどは新しい文字列を返します(Ruby では文字列は可変です)。インプレース変更には ! バリアント(upcase!、gsub!)を使用してください—これらはレシーバを変更し、変更がない場合は nil を返します。
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 を返すことがあります。コピーの作成を避けたい場合に使用しますが、副作用に注意してください。
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 テンプレート、長いメッセージに有用です。
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桁です。表形式出力と固定幅フォーマットに有用です。
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 を優先してください。
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データ構造
配列
配列は順序付き、ゼロインデックスで、混合型を保持できます。<< と push は末尾に追加します。メンバーシップには include? を、合計には sum を使用してください。配列は可変です—不変にするには freeze を使用してください。
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 を使用してください。
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 型で使用できます。
(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 を使用してください。これらは表現力豊かなデータ変換パイプラインを可能にします。
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 が必要です。
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)制御フロー
If / Elsif / Unless
if/elsif/else は標準的な分岐です。unless は if の逆です(条件が false のときに実行)。修飾子形式(statement if condition)は単一行ガードの慣用的な方法です—単純なケースの可読性を向上させます。
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 >= 60Case(When)
case/when はマッチングに === を使用し、範囲、クラス、正規表現を可能にします。複数の値はカンマで区切ります。then は単一行本体を許可します。対象がない場合、case はよりクリーンな if/elsif チェーンとして機能します。
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"
endWhile / Until / Loop
while は true の間実行し、until は true になるまで実行します(while false)。loop は無限です—break で抜けます。コレクションには while よりイ テレータ(each、map)を優先してください—より慣用的でエラーが少ないです。
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 の反復の基盤です。
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 3Break / Next / Redo
next は次の反復にスキップし(continue のようなもの)、break はループを早期終了します。break はブロックから値を返せます。redo は条件を再チェックせずに現在の反復を再開します—滅多に使用されません。
[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メソッドとブロック
メソッド定義
メソッドは def/end を使用します。キーワード引数(key:)はパラメータが多い場合の可読性を向上させます。*args は余分な位置引数を配列に集め、**kwargs はキーワード引数をハッシュに集めます。デフォルト値は = を使用します。
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 を使用してメソッドを柔軟にしてください—呼び出し側が振る舞いを提供します。
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 } # 10Proc と Lambda
Proc と lambda は変数に格納された再利用可能なブロックです。主な違い:lambda は引数の数をチェックし(不一致で発生)、自身からのみ return します。proc は寛容で外側のメソッドから return します。厳密な振る舞いには lambda を優先してください。
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)。単純な変換にクリーナーで読みやすいです。
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 を使用してください。複数の値は配列として返され、分解できます。これによりコードが簡潔になります。
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クラスとオブジェクト指向
クラスとインスタンス変数
@var = インスタンス変数(オブジェクトごと)、@@var = クラス変数(共有)。attr_accessor が getter+setter を生成し、attr_reader は getter のみ、attr_writer は setter のみ。self.method がクラスメソッドを定義します。
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 は単一継承 + ミックスインを使用します。
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 などが得られます。
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 を使用してください。
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 methodSelf とクラスメソッド
self は現在のオブジェクトを指します。クラス本体内では self はクラスです—def self.method がクラスメソッドを定義します。クラスメソッドはクラスで呼び出され(Counter.total)、インスタンスメソッドはインスタンスで呼び出されます。alias_method がメソッドのエイリアスを作成します。
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エラー処理
Begin / Rescue / Ensure
begin/rescue は Ruby の try/catch です。対象を絞った処理のために特定の例外クラスを rescue してください。=> e が例外オブジェクトをキャプチャします。ensure は常に実行されます—クリーンアップ(ファイルのクローズ、ロック の解放)に使用してください。
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/failureRaise とカスタム例外
raise は例外をスローします(引数なしの raise は再スロー)。カスタム例外は StandardError(またはより具体的なクラス)を継承します。Error サフィックスで名前を付けてください。特定の失敗モードを処理するためにクラスで rescue してください。
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}"
endRetry
retry は begin ブロックを最初から再開します。一時的な失敗(ネットワーク、レート制限)にカウンタとともに使用し、無限ループを避けてください。制限がないと retry はプログラムをハングさせる可能性があります—常にガードしてください。
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 topRescue 修飾子
修飾子としての rescue はフォールバック値を提供する簡潔な方法です。StandardError をキャッチし右側を返します。単純なケースに使用してください—複雑なロジックにはエラーを隠すため避けてください。解析やオプション操作に最適です。
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 を使用してください。
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ファイル I/O
ファイルの読み書き
File.write/File.read はシンプルなワンショットメソッドです 。ブロック付き File.open はファイルを自動クローズします。File.foreach はファイル全体を読み込まず1行ずつ読みます—大きなファイルにメモリ効率が良いです。chomp が末尾の改行を削除します。
# 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 を呼ぶ必要があります。
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 がファイルシステムを変更します。エラーを避けるため操作前に常に存在を確認してください。
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 がファイルパターンにマッチします—* は任意に、** は再帰的にマッチします。ファイル発見とバッチ処理に有用です。
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 } # recursiveCSV と JSON
CSV と JSON は標準ライブラリにあります。CSV.foreach は行をストリーミングします(メモリ効率)。JSON.parse は文字列キーを持つハッシュ/配列を返します。to_json が任意のオブジェクトをシリア ライズします。これらはデータ交換に不可欠です。
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"] # AliceDate/Time と正規表現
Time と Date
Time は瞬間を表します(タイムゾーン付き)。Date はカレンダー日付を表します(時間なし)。Time の算術は秒を使用します。strftime は %Y(年)、%m(月)、%d(日)、%H:%M(時間)でフォーマットします。ISO8601 解析には 'time' を require してください。
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 # tomorrowDate の解析と算術
Date の算術は自然に機能します—整数を加えると日を加えます。next_month/prev_month が月の境界を処理します。Date - Date は Rational(日数)を返します。upto/downto が日付範囲を反復します。カレンダーロジックには Date を、タイムスタンプには Time を使用してください。
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/ を使用します。
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)はキャプチャグループを参照します。テキスト処理に強力です。
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 のような)に有用な代替デリミタです。
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"メタプログラミングと並行性
動的メソッド定義
define_method は実行時にメソッドを作成します。複数の類似メソッドの生成や DSL 構築に使用してください。これはメタプログラミング—コードを書くコードです。強力ですが慎重に使用してください;コードを理解しにくくする可能性があります。
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 methodsmethod_missing
method_missing は未定義メソッドへの呼び出しをインターセプトします。柔軟な API やプロキシの構築に使用してください。リフレクションが機能するよう respond_to_missing? も常にオーバーライドしてください。控えめに使用してください—バグを隠し静的解析を混乱させる可能性があります。
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 以降)または複数プロセスを使用してください。
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 で手動で一時停止と再開をします。スレッドとは異なり並行では動作しません。ジェネレータ、遅延評価、一時停止可能な計算に使用してください。スレッドよりオーバーヘッドが低いです。
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 preemptivelySend と Eval
send は名前で任意のメソッドを呼び出します(private を含む)。public_send は可視性を尊重します。eval は文字列を Ruby コードとして実行します—非常に強力ですが信頼できない入力には危険です(コードインジェクション)。動的ディスパッチには send を使用し、本番では eval を避けてください。
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!ブロック、Proc と Lambda
ブロックの基礎
ブロックは Ruby の最も一般的なクロージャです—{ } または do...end でメソッドに渡される匿名コード。yield がブロックを呼び出します。|n| がブロックパラメータを宣言します。block_given? がブロックが渡されたかチェックします。ブロックはオブジェクトではありません(変数に格納できません)—そのためには Proc/Lambda を使用してください。すべてのメソッドは暗黙的なブロックを受け取れるため、DSL が自然になります(Rails がこれを多用)。
# 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
endProc と Lambda
Proc と Lambda はどちらも呼び出し可能なオブジェクトです(ブロックをオブジェクトにしたもの)。Proc は寛容です:余分な引数は nil になり、欠落した引数は nil で、'return' は外側のメソッドから抜けます。Lambda は厳密です:引数の数をチェックし、'return' は lambda からのみ抜けます。メソッドのような振る舞いには lambda を、ブロックのような振る舞いには Proc を使用してください。->(stabby lambda)はモダンで簡潔な構文です。
# 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 オブジェクトはレシーバを保持するため、オブジェクトにバインドされます。
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 のブロックがコールバックとイテレータで強力な理由です。
# 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 の反復とコールバックパターンをエレガントにします。
# 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) }Enumerable とイテレータ
コア Enumerable メソッド
Enumerable は Ruby の最も強力なミックスインです—include して #each を定義すると50以上のメソッドが得られます。map が変換、select/reject がフィルタ、reduce/inject が集計、find が最初のマッチを返し、group_by/partition がクラスタリングします。 シンボルショートカット(reduce(:+))が慣用的です。これらのメソッドは配列、ハッシュ、範囲、ファイル—#each を持つ任意のもので機能します。Enumerable の習得は慣用的な 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 は要素ごとのオーバーヘッドがあるため、小さなコレクションでは遅くなります。
# 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 を返します。
# 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 して <=> を定義するとオブジェクトの自然な順序付けができます。
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 でのデータ処理の基盤です—クリーンで表現力豊かなデータ操作のためにこれらを習得してください。
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"]}Gems と Bundler
Gem の基礎
Gem は Ruby のパッケージ(ライブラリ)です。gem install がシステム gem を管理します。プロジェクトには Bundler と Gemfile でバージョンを固定し依存関係を管理してください。バージョン指定子:'~> 1.4'(悲観的、パッチを許可)、'>= 5.0'(楽観的)。グループ(:development、:test、:production)で環境ごとに必要な gem のみを読み込めます。require: false は Bundler が自動 require しないことを意味します(必要時に手動で require)。
# 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'
endBundler コマンド
Bundler はプロジェクトが指定された正確な gem バージョンを使用することを保証します。bundle install は Gemfile を読み取り Gemfile.lock を書き込みます(再現性のための正確なバージョン—これをコミットしてください!)。bundle exec が正しい gem バージョンでコマンドを実行します(競合を回避)。bundle update がバージョンを変更します(注意—壊れる可能性があります)。正しい gem が読み込まれることを保証するため rake/rspec/rails には常に bundle exec を使用してください。Gemfile.lock がデプロイを再現可能にします。
# 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 cleanGemspec(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/ にテスト。
# 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.gemRake タスク
Rake は Ruby の make です—タスクランナー。task :name do ... end でタスクを定義します。名前空間が関連タスクをグループ化します。ファイルタスクは依存関係を持ちます(ソース変更時に再ビルド)。sh がシェルコマンドを実行します。Rake はテスト、ビルド、デプロイ、データベースタスクに使用されます(Rails が多用)。rake task_name で実行します。default タスクは rake だけと入力したときに実行されます。rake greet[Alice] で引数を渡します。
# 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'
endRbenv と 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 バージョンを固定してく ださい。
# 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 -vRails の基礎
MVC 構造
Rails は MVC フレームワークです:モデル(ActiveRecord)がデータを扱い、コントローラ(ActionController)が HTTP リクエストを処理し、ビュー(ActionView)がレスポンスをレンダリングします。resources が7つの RESTful ルートを自動生成します。ルートが URL をコントローラアクションにマッピングします。設定より規約:モデルに User、コントローラに UsersController と名前付けすると、Rails がすべてを結線します。この構造がすべての Rails アプリの基盤です。
# 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
endActiveRecord モデル
ActiveRecord は Rails の ORM です—モデルがデータベーステーブルにマッピングされます。validates がデータ整合性を強制します。関連(has_many、belongs_to、has_one)がリレーションシップを定義します。コールバック(before_save、after_create)がライフサイクルにフックします。スコープは再利用可能なクエリ断片です。ActiveRecord は規約を使用:User モデル → users テーブル、created_at/updated_at カラム。これが Rails の心臓部です—効果的な Rails 開発のために習得してください。
# 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)が慣例です。
# 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/)やデコレータを使用してくだ さい。
<!-- 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 を直接編集しないでください—マイグレーションを使用してください。これによりデータベース変更が環境間で再現可能になります。
# 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, :integerRSpec テスト
基本構文(describe、it、expect)
RSpec は Ruby の主要なテストフレームワークです。describe が関連テストをグループ化し、it が単一テストを定義します。expect(...).to / not_to がアサーションを行います。let が遅延メモ化変数を定義します(初回アクセス時に一度だけ計算)。context は describe のエイリアスで、分岐(when...)に使用します。shoulda-matchers gem が一般的な Rails バリデーション/関連のワンライナー構文を提供します。テストは app/ 構造をミラーする spec/ に配置します。
# 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(メモリ内のみ)を使用します。
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 を使用してください。ファクトリをシンプルに保ってください—複雑なファクトリは複雑なモデルを示唆します。