【保存版】Ruby Enumの完全ガイド:20個の実践的な使用例と最適化テクニック

目次

目次へ

Enumとは?初心者でもわかるRubyの基礎概念

EnumはRubyで配列処理を簡単にする魔法のような機能

RubyのEnumerableモジュールは、コレクション(配列やハッシュなど)を扱うための強力なメソッド群を提供します。このモジュールを理解することは、Rubyでの効率的なプログラミングの鍵となります。

Enumerable モジュールの特徴

  • 繰り返し処理の抽象化: 配列やハッシュなどのコレクションに対する繰り返し処理を、シンプルで読みやすい形で書けます
  • メソッドチェーンの実現: 複数の処理を連結して書くことができ、データの加工を段階的に行えます
  • 遅延評価: 必要になるまで実際の処理を遅らせることができ、メモリ効率が良い

以下は基本的な使用例です:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 配列の各要素を2倍にする
numbers = [1, 2, 3, 4, 5]
doubled = numbers.map { |n| n * 2 }
# => [2, 4, 6, 8, 10]
# 偶数のみを抽出する
evens = numbers.select { |n| n.even? }
# => [2, 4]
# 要素の合計を計算する
sum = numbers.reduce(0) { |acc, n| acc + n }
# => 15
# 配列の各要素を2倍にする numbers = [1, 2, 3, 4, 5] doubled = numbers.map { |n| n * 2 } # => [2, 4, 6, 8, 10] # 偶数のみを抽出する evens = numbers.select { |n| n.even? } # => [2, 4] # 要素の合計を計算する sum = numbers.reduce(0) { |acc, n| acc + n } # => 15
# 配列の各要素を2倍にする
numbers = [1, 2, 3, 4, 5]
doubled = numbers.map { |n| n * 2 }
# => [2, 4, 6, 8, 10]

# 偶数のみを抽出する
evens = numbers.select { |n| n.even? }
# => [2, 4]

# 要素の合計を計算する
sum = numbers.reduce(0) { |acc, n| acc + n }
# => 15

他言語のイテレータとの違いから理解するEnum

RubyのEnumerableは、他のプログラミング言語のイテレータと比較して、いくつかの特徴的な違いがあります:

特徴Ruby(Enumerable)他言語の一般的なイテレータ
メソッドチェーン自然な形で記述可能言語によっては複雑な構文が必要
ブロック構文読みやすい{ }do...end多くの場合、ラムダ式や無名関数を使用
遅延評価lazy を使用して実現可能言語によって異なる実装が必要
メソッドの豊富さ50以上の組み込みメソッド基本的な操作のみを提供することが多い

具体的な例を見てみましょう:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Rubyでの実装
result = [1, 2, 3, 4, 5]
.map { |n| n * 2 } # 各要素を2倍
.select { |n| n > 5 } # 5より大きい要素を選択
.reduce(0, :+) # 合計を計算
# => 24
# 同様の処理を従来のループで書いた場合
numbers = [1, 2, 3, 4, 5]
doubled = []
numbers.each { |n| doubled << n * 2 } # 2倍にする
filtered = []
doubled.each { |n| filtered << n if n > 5 } # フィルタリング
sum = 0
filtered.each { |n| sum += n } # 合計を計算
# => 24
# Rubyでの実装 result = [1, 2, 3, 4, 5] .map { |n| n * 2 } # 各要素を2倍 .select { |n| n > 5 } # 5より大きい要素を選択 .reduce(0, :+) # 合計を計算 # => 24 # 同様の処理を従来のループで書いた場合 numbers = [1, 2, 3, 4, 5] doubled = [] numbers.each { |n| doubled << n * 2 } # 2倍にする filtered = [] doubled.each { |n| filtered << n if n > 5 } # フィルタリング sum = 0 filtered.each { |n| sum += n } # 合計を計算 # => 24
# Rubyでの実装
result = [1, 2, 3, 4, 5]
  .map { |n| n * 2 }     # 各要素を2倍
  .select { |n| n > 5 }  # 5より大きい要素を選択
  .reduce(0, :+)         # 合計を計算
# => 24

# 同様の処理を従来のループで書いた場合
numbers = [1, 2, 3, 4, 5]
doubled = []
numbers.each { |n| doubled << n * 2 }  # 2倍にする
filtered = []
doubled.each { |n| filtered << n if n > 5 }  # フィルタリング
sum = 0
filtered.each { |n| sum += n }  # 合計を計算
# => 24

このように、Enumを使用することで:

  1. コードがより宣言的になり、「何をするか」が明確
  2. 一時変数が減り、コードがクリーンに
  3. バグの可能性が減少
  4. 可読性が大幅に向上

以上がEnumの基本的な概念です。これらの基礎を理解することで、より複雑な処理も効率的に実装できるようになります。

Enumの基本メソッド完全マスター

map/collectで配列を自由に変換する

map(別名collect)は、配列の各要素を変換して新しい配列を作成する最も基本的なEnumメソッドです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 基本的な使い方
numbers = [1, 2, 3, 4, 5]
squared = numbers.map { |n| n ** 2 }
# => [1, 4, 9, 16, 25]
# 複数の条件での変換
users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
]
names = users.map { |user| user[:name] }
# => ["Alice", "Bob"]
# メソッド参照を使用した簡潔な記法
words = ["hello", "world"]
upper_words = words.map(&:upcase)
# => ["HELLO", "WORLD"]
# 基本的な使い方 numbers = [1, 2, 3, 4, 5] squared = numbers.map { |n| n ** 2 } # => [1, 4, 9, 16, 25] # 複数の条件での変換 users = [ { name: "Alice", age: 25 }, { name: "Bob", age: 30 } ] names = users.map { |user| user[:name] } # => ["Alice", "Bob"] # メソッド参照を使用した簡潔な記法 words = ["hello", "world"] upper_words = words.map(&:upcase) # => ["HELLO", "WORLD"]
# 基本的な使い方
numbers = [1, 2, 3, 4, 5]
squared = numbers.map { |n| n ** 2 }
# => [1, 4, 9, 16, 25]

# 複数の条件での変換
users = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 30 }
]
names = users.map { |user| user[:name] }
# => ["Alice", "Bob"]

# メソッド参照を使用した簡潔な記法
words = ["hello", "world"]
upper_words = words.map(&:upcase)
# => ["HELLO", "WORLD"]

実践的なユースケース:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# APIレスポンスの整形
api_response = [
{ "user_id" => 1, "data" => { "name" => "Alice" }},
{ "user_id" => 2, "data" => { "name" => "Bob" }}
]
formatted = api_response.map { |r|
{
id: r["user_id"],
name: r["data"]["name"]
}
}
# => [{:id=>1, :name=>"Alice"}, {:id=>2, :name=>"Bob"}]
# APIレスポンスの整形 api_response = [ { "user_id" => 1, "data" => { "name" => "Alice" }}, { "user_id" => 2, "data" => { "name" => "Bob" }} ] formatted = api_response.map { |r| { id: r["user_id"], name: r["data"]["name"] } } # => [{:id=>1, :name=>"Alice"}, {:id=>2, :name=>"Bob"}]
# APIレスポンスの整形
api_response = [
  { "user_id" => 1, "data" => { "name" => "Alice" }},
  { "user_id" => 2, "data" => { "name" => "Bob" }}
]
formatted = api_response.map { |r| 
  {
    id: r["user_id"],
    name: r["data"]["name"]
  }
}
# => [{:id=>1, :name=>"Alice"}, {:id=>2, :name=>"Bob"}]

select/reject条件に合う要素を抽出する

selectrejectは、条件に基づいて要素をフィルタリングするメソッドです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
numbers = [1, 2, 3, 4, 5, 6]
# selectは条件に合う要素を抽出
evens = numbers.select { |n| n.even? }
# => [2, 4, 6]
# rejectは条件に合う要素を除外
non_evens = numbers.reject { |n| n.even? }
# => [1, 3, 5]
# 複雑な条件での使用例
users = [
{ name: "Alice", age: 25, active: true },
{ name: "Bob", age: 30, active: false },
{ name: "Charlie", age: 20, active: true }
]
active_adult_users = users.select { |user|
user[:age] >= 20 && user[:active]
}
# => [{:name=>"Alice", :age=>25, :active=>true}]
numbers = [1, 2, 3, 4, 5, 6] # selectは条件に合う要素を抽出 evens = numbers.select { |n| n.even? } # => [2, 4, 6] # rejectは条件に合う要素を除外 non_evens = numbers.reject { |n| n.even? } # => [1, 3, 5] # 複雑な条件での使用例 users = [ { name: "Alice", age: 25, active: true }, { name: "Bob", age: 30, active: false }, { name: "Charlie", age: 20, active: true } ] active_adult_users = users.select { |user| user[:age] >= 20 && user[:active] } # => [{:name=>"Alice", :age=>25, :active=>true}]
numbers = [1, 2, 3, 4, 5, 6]

# selectは条件に合う要素を抽出
evens = numbers.select { |n| n.even? }
# => [2, 4, 6]

# rejectは条件に合う要素を除外
non_evens = numbers.reject { |n| n.even? }
# => [1, 3, 5]

# 複雑な条件での使用例
users = [
  { name: "Alice", age: 25, active: true },
  { name: "Bob", age: 30, active: false },
  { name: "Charlie", age: 20, active: true }
]

active_adult_users = users.select { |user| 
  user[:age] >= 20 && user[:active]
}
# => [{:name=>"Alice", :age=>25, :active=>true}]

reduce/injectでコレクションを集計する

reduce(別名inject)は、配列の要素を集計して単一の値を得るメソッドです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
numbers = [1, 2, 3, 4, 5]
# 基本的な合計計算
sum = numbers.reduce(0) { |acc, n| acc + n }
# => 15
# 初期値を省略した場合(最初の要素が初期値になる)
product = numbers.reduce(:*)
# => 120
# より複雑な集計の例
words = ["ruby", "is", "awesome"]
word_lengths = words.reduce({}) { |hash, word|
hash[word] = word.length
hash
}
# => {"ruby"=>4, "is"=>2, "awesome"=>7}
numbers = [1, 2, 3, 4, 5] # 基本的な合計計算 sum = numbers.reduce(0) { |acc, n| acc + n } # => 15 # 初期値を省略した場合(最初の要素が初期値になる) product = numbers.reduce(:*) # => 120 # より複雑な集計の例 words = ["ruby", "is", "awesome"] word_lengths = words.reduce({}) { |hash, word| hash[word] = word.length hash } # => {"ruby"=>4, "is"=>2, "awesome"=>7}
numbers = [1, 2, 3, 4, 5]

# 基本的な合計計算
sum = numbers.reduce(0) { |acc, n| acc + n }
# => 15

# 初期値を省略した場合(最初の要素が初期値になる)
product = numbers.reduce(:*)
# => 120

# より複雑な集計の例
words = ["ruby", "is", "awesome"]
word_lengths = words.reduce({}) { |hash, word|
  hash[word] = word.length
  hash
}
# => {"ruby"=>4, "is"=>2, "awesome"=>7}

実践的な使用例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 売上データの集計
sales = [
{ product: "A", amount: 100 },
{ product: "B", amount: 200 },
{ product: "A", amount: 150 }
]
sales_by_product = sales.reduce(Hash.new(0)) { |hash, sale|
hash[sale[:product]] += sale[:amount]
hash
}
# => {"A"=>250, "B"=>200}
# 売上データの集計 sales = [ { product: "A", amount: 100 }, { product: "B", amount: 200 }, { product: "A", amount: 150 } ] sales_by_product = sales.reduce(Hash.new(0)) { |hash, sale| hash[sale[:product]] += sale[:amount] hash } # => {"A"=>250, "B"=>200}
# 売上データの集計
sales = [
  { product: "A", amount: 100 },
  { product: "B", amount: 200 },
  { product: "A", amount: 150 }
]

sales_by_product = sales.reduce(Hash.new(0)) { |hash, sale|
  hash[sale[:product]] += sale[:amount]
  hash
}
# => {"A"=>250, "B"=>200}

これらの基本メソッドを組み合わせることで、より複雑な処理も簡潔に書くことができます:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 商品データから、1000円以上の商品の税込価格を計算する
products = [
{ name: "商品A", price: 800 },
{ name: "商品B", price: 1200 },
{ name: "商品C", price: 1500 }
]
expensive_products_with_tax = products
.select { |p| p[:price] >= 1000 } # 1000円以上をフィルタリング
.map { |p|
{
name: p[:name],
price_with_tax: (p[:price] * 1.1).round
}
}
# => [{:name=>"商品B", :price_with_tax=>1320},
# {:name=>"商品C", :price_with_tax=>1650}]
# 商品データから、1000円以上の商品の税込価格を計算する products = [ { name: "商品A", price: 800 }, { name: "商品B", price: 1200 }, { name: "商品C", price: 1500 } ] expensive_products_with_tax = products .select { |p| p[:price] >= 1000 } # 1000円以上をフィルタリング .map { |p| { name: p[:name], price_with_tax: (p[:price] * 1.1).round } } # => [{:name=>"商品B", :price_with_tax=>1320}, # {:name=>"商品C", :price_with_tax=>1650}]
# 商品データから、1000円以上の商品の税込価格を計算する
products = [
  { name: "商品A", price: 800 },
  { name: "商品B", price: 1200 },
  { name: "商品C", price: 1500 }
]

expensive_products_with_tax = products
  .select { |p| p[:price] >= 1000 }  # 1000円以上をフィルタリング
  .map { |p| 
    {
      name: p[:name],
      price_with_tax: (p[:price] * 1.1).round
    }
  }
# => [{:name=>"商品B", :price_with_tax=>1320}, 
#     {:name=>"商品C", :price_with_tax=>1650}]

これらのメソッドは、Rubyでの配列処理の基礎となる重要な要素です。適切に使用することで、コードの可読性と保守性を大きく向上させることができます。

知って得する!実践的なEnumの活用パターン

複数のEnumメソッドを組み合わせて処理を効率化する

実践的なプログラミングでは、複数のEnumメソッドを組み合わせることで、複雑な処理を簡潔に書くことができます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# ユーザーデータから条件に合う情報を抽出して加工する
users = [
{ id: 1, name: "Alice", age: 25, points: 100 },
{ id: 2, name: "Bob", age: 30, points: 150 },
{ id: 3, name: "Charlie", age: 20, points: 80 }
]
# 25歳以上のユーザーのポイントを20%アップして、名前とポイントの配列を作成
result = users
.select { |user| user[:age] >= 25 } # 25歳以上をフィルタリング
.map { |user|
{
name: user[:name],
updated_points: (user[:points] * 1.2).round
}
}
# => [{:name=>"Alice", :updated_points=>120},
# {:name=>"Bob", :updated_points=>180}]
# ユーザーデータから条件に合う情報を抽出して加工する users = [ { id: 1, name: "Alice", age: 25, points: 100 }, { id: 2, name: "Bob", age: 30, points: 150 }, { id: 3, name: "Charlie", age: 20, points: 80 } ] # 25歳以上のユーザーのポイントを20%アップして、名前とポイントの配列を作成 result = users .select { |user| user[:age] >= 25 } # 25歳以上をフィルタリング .map { |user| { name: user[:name], updated_points: (user[:points] * 1.2).round } } # => [{:name=>"Alice", :updated_points=>120}, # {:name=>"Bob", :updated_points=>180}]
# ユーザーデータから条件に合う情報を抽出して加工する
users = [
  { id: 1, name: "Alice", age: 25, points: 100 },
  { id: 2, name: "Bob", age: 30, points: 150 },
  { id: 3, name: "Charlie", age: 20, points: 80 }
]

# 25歳以上のユーザーのポイントを20%アップして、名前とポイントの配列を作成
result = users
  .select { |user| user[:age] >= 25 }  # 25歳以上をフィルタリング
  .map { |user| 
    {
      name: user[:name],
      updated_points: (user[:points] * 1.2).round
    }
  }
# => [{:name=>"Alice", :updated_points=>120}, 
#     {:name=>"Bob", :updated_points=>180}]

ブロックとEnumを組み合わせた高度なテクニック

ブロックとEnumを組み合わせることで、より柔軟な処理が可能になります:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# カスタムソート条件を実装する
class Product
attr_reader :name, :price, :stock
def initialize(name, price, stock)
@name = name
@price = price
@stock = stock
end
end
products = [
Product.new("A", 1000, 5),
Product.new("B", 800, 2),
Product.new("C", 1200, 8)
]
# 在庫が3個以上ある商品を価格の安い順にソート
sorted_products = products
.select { |p| p.stock >= 3 }
.sort_by { |p| p.price }
.map { |p| "#{p.name}: #{p.price}円 (在庫: #{p.stock}個)" }
# => ["A: 1000円 (在庫: 5個)", "C: 1200円 (在庫: 8個)"]
# カスタムソート条件を実装する class Product attr_reader :name, :price, :stock def initialize(name, price, stock) @name = name @price = price @stock = stock end end products = [ Product.new("A", 1000, 5), Product.new("B", 800, 2), Product.new("C", 1200, 8) ] # 在庫が3個以上ある商品を価格の安い順にソート sorted_products = products .select { |p| p.stock >= 3 } .sort_by { |p| p.price } .map { |p| "#{p.name}: #{p.price}円 (在庫: #{p.stock}個)" } # => ["A: 1000円 (在庫: 5個)", "C: 1200円 (在庫: 8個)"]
# カスタムソート条件を実装する
class Product
  attr_reader :name, :price, :stock

  def initialize(name, price, stock)
    @name = name
    @price = price
    @stock = stock
  end
end

products = [
  Product.new("A", 1000, 5),
  Product.new("B", 800, 2),
  Product.new("C", 1200, 8)
]

# 在庫が3個以上ある商品を価格の安い順にソート
sorted_products = products
  .select { |p| p.stock >= 3 }
  .sort_by { |p| p.price }
  .map { |p| "#{p.name}: #{p.price}円 (在庫: #{p.stock}個)" }
# => ["A: 1000円 (在庫: 5個)", "C: 1200円 (在庫: 8個)"]

実務でよく使うEnumのイディオム集

実務でよく遭遇する処理パターンをEnumで効率的に実装する方法を紹介します:

  1. グループ化と集計の組み合わせ
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 注文データを月ごとに集計する
orders = [
{ date: "2024-01-15", amount: 1000 },
{ date: "2024-01-20", amount: 2000 },
{ date: "2024-02-10", amount: 1500 }
]
monthly_totals = orders.group_by { |order|
Date.parse(order[:date]).strftime("%Y-%m")
}.transform_values { |orders|
orders.sum { |order| order[:amount] }
}
# => {"2024-01"=>3000, "2024-02"=>1500}
# 注文データを月ごとに集計する orders = [ { date: "2024-01-15", amount: 1000 }, { date: "2024-01-20", amount: 2000 }, { date: "2024-02-10", amount: 1500 } ] monthly_totals = orders.group_by { |order| Date.parse(order[:date]).strftime("%Y-%m") }.transform_values { |orders| orders.sum { |order| order[:amount] } } # => {"2024-01"=>3000, "2024-02"=>1500}
# 注文データを月ごとに集計する
orders = [
  { date: "2024-01-15", amount: 1000 },
  { date: "2024-01-20", amount: 2000 },
  { date: "2024-02-10", amount: 1500 }
]

monthly_totals = orders.group_by { |order| 
  Date.parse(order[:date]).strftime("%Y-%m")
}.transform_values { |orders| 
  orders.sum { |order| order[:amount] }
}
# => {"2024-01"=>3000, "2024-02"=>1500}
  1. 条件付きマッピング
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# ステータスに応じて異なる処理を適用する
tasks = [
{ id: 1, status: "pending", name: "Task 1" },
{ id: 2, status: "completed", name: "Task 2" },
{ id: 3, status: "pending", name: "Task 3" }
]
processed_tasks = tasks.map { |task|
case task[:status]
when "pending"
{ id: task[:id], message: "要対応: #{task[:name]}" }
when "completed"
{ id: task[:id], message: "完了済: #{task[:name]}" }
end
}
# => [{:id=>1, :message=>"要対応: Task 1"},
# {:id=>2, :message=>"完了済: Task 2"},
# {:id=>3, :message=>"要対応: Task 3"}]
# ステータスに応じて異なる処理を適用する tasks = [ { id: 1, status: "pending", name: "Task 1" }, { id: 2, status: "completed", name: "Task 2" }, { id: 3, status: "pending", name: "Task 3" } ] processed_tasks = tasks.map { |task| case task[:status] when "pending" { id: task[:id], message: "要対応: #{task[:name]}" } when "completed" { id: task[:id], message: "完了済: #{task[:name]}" } end } # => [{:id=>1, :message=>"要対応: Task 1"}, # {:id=>2, :message=>"完了済: Task 2"}, # {:id=>3, :message=>"要対応: Task 3"}]
# ステータスに応じて異なる処理を適用する
tasks = [
  { id: 1, status: "pending", name: "Task 1" },
  { id: 2, status: "completed", name: "Task 2" },
  { id: 3, status: "pending", name: "Task 3" }
]

processed_tasks = tasks.map { |task|
  case task[:status]
  when "pending"
    { id: task[:id], message: "要対応: #{task[:name]}" }
  when "completed"
    { id: task[:id], message: "完了済: #{task[:name]}" }
  end
}
# => [{:id=>1, :message=>"要対応: Task 1"},
#     {:id=>2, :message=>"完了済: Task 2"},
#     {:id=>3, :message=>"要対応: Task 3"}]
  1. 階層化されたデータの処理
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 階層構造のデータを平坦化して処理する
departments = [
{
name: "営業部",
teams: [
{ name: "国内営業", members: 5 },
{ name: "海外営業", members: 3 }
]
},
{
name: "開発部",
teams: [
{ name: "フロントエンド", members: 4 },
{ name: "バックエンド", members: 6 }
]
}
]
# 部署ごとの総メンバー数を計算
department_members = departments.map { |dept|
{
department: dept[:name],
total_members: dept[:teams].sum { |team| team[:members] }
}
}
# => [{:department=>"営業部", :total_members=>8},
# {:department=>"開発部", :total_members=>10}]
# 階層構造のデータを平坦化して処理する departments = [ { name: "営業部", teams: [ { name: "国内営業", members: 5 }, { name: "海外営業", members: 3 } ] }, { name: "開発部", teams: [ { name: "フロントエンド", members: 4 }, { name: "バックエンド", members: 6 } ] } ] # 部署ごとの総メンバー数を計算 department_members = departments.map { |dept| { department: dept[:name], total_members: dept[:teams].sum { |team| team[:members] } } } # => [{:department=>"営業部", :total_members=>8}, # {:department=>"開発部", :total_members=>10}]
# 階層構造のデータを平坦化して処理する
departments = [
  {
    name: "営業部",
    teams: [
      { name: "国内営業", members: 5 },
      { name: "海外営業", members: 3 }
    ]
  },
  {
    name: "開発部",
    teams: [
      { name: "フロントエンド", members: 4 },
      { name: "バックエンド", members: 6 }
    ]
  }
]

# 部署ごとの総メンバー数を計算
department_members = departments.map { |dept|
  {
    department: dept[:name],
    total_members: dept[:teams].sum { |team| team[:members] }
  }
}
# => [{:department=>"営業部", :total_members=>8},
#     {:department=>"開発部", :total_members=>10}]

これらのパターンを理解し、適切に組み合わせることで、複雑な業務要件も効率的に実装することができます。

Enumのパフォーマンス最適化ガイド

each vs map:適切なメソッドの選択

Enumメソッドの選択は、パフォーマンスに大きな影響を与えます。以下が主要なメソッドのパフォーマンス特性です:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 悪い例:新しい配列が必要ないのにmapを使用
numbers = (1..1000000).to_a
# メモリ効率が悪い実装
numbers.map { |n| puts n if n.even? } # 不要な配列を作成
# 良い例:副作用のみの処理にはeachを使用
numbers.each { |n| puts n if n.even? } # メモリ効率が良い
# 悪い例:新しい配列が必要ないのにmapを使用 numbers = (1..1000000).to_a # メモリ効率が悪い実装 numbers.map { |n| puts n if n.even? } # 不要な配列を作成 # 良い例:副作用のみの処理にはeachを使用 numbers.each { |n| puts n if n.even? } # メモリ効率が良い
# 悪い例:新しい配列が必要ないのにmapを使用
numbers = (1..1000000).to_a

# メモリ効率が悪い実装
numbers.map { |n| puts n if n.even? }  # 不要な配列を作成

# 良い例:副作用のみの処理にはeachを使用
numbers.each { |n| puts n if n.even? }  # メモリ効率が良い

メソッド選択のガイドライン:

メソッド使用すべき場合避けるべき場合
each副作用が目的の処理新しいコレクションが必要な場合
map変換後の配列が必要な場合結果を使用しない場合
select/reject条件でフィルタリングする場合全件必要な場合
reduce集計処理が必要な場合中間結果が必要な場合

メモリ使用量を重視したEnumの使い方

大規模なデータ処理時のメモリ使用量を最適化する方法を紹介します:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# メモリ効率の悪い実装
def process_large_data(numbers)
# 中間配列をたくさん生成
result = numbers
.map { |n| n * 2 }
.select { |n| n > 1000 }
.map { |n| n.to_s }
result
end
# メモリ効率の良い実装
def process_large_data_optimized(numbers)
# 一度の走査で処理を完結
numbers.each_with_object([]) do |n, result|
doubled = n * 2
result << doubled.to_s if doubled > 1000
end
end
# lazyを使用した実装
def process_large_data_lazy(numbers)
numbers
.lazy
.map { |n| n * 2 }
.select { |n| n > 1000 }
.map { |n| n.to_s }
.force
end
# メモリ効率の悪い実装 def process_large_data(numbers) # 中間配列をたくさん生成 result = numbers .map { |n| n * 2 } .select { |n| n > 1000 } .map { |n| n.to_s } result end # メモリ効率の良い実装 def process_large_data_optimized(numbers) # 一度の走査で処理を完結 numbers.each_with_object([]) do |n, result| doubled = n * 2 result << doubled.to_s if doubled > 1000 end end # lazyを使用した実装 def process_large_data_lazy(numbers) numbers .lazy .map { |n| n * 2 } .select { |n| n > 1000 } .map { |n| n.to_s } .force end
# メモリ効率の悪い実装
def process_large_data(numbers)
  # 中間配列をたくさん生成
  result = numbers
    .map { |n| n * 2 }
    .select { |n| n > 1000 }
    .map { |n| n.to_s }
  result
end

# メモリ効率の良い実装
def process_large_data_optimized(numbers)
  # 一度の走査で処理を完結
  numbers.each_with_object([]) do |n, result|
    doubled = n * 2
    result << doubled.to_s if doubled > 1000
  end
end

# lazyを使用した実装
def process_large_data_lazy(numbers)
  numbers
    .lazy
    .map { |n| n * 2 }
    .select { |n| n > 1000 }
    .map { |n| n.to_s }
    .force
end

lazy評価を使用する際の注意点:

  • 小さなデータセットでは通常の実装の方が高速
  • メモリ使用量と処理速度はトレードオフ
  • 無限シーケンスの処理に特に有効

大規模データ処理時のベストプラクティス

大規模データを効率的に処理するためのテクニックを紹介します:

  1. バッチ処理の活用
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# メモリ効率の良いバッチ処理
def process_in_batches(items, batch_size = 1000)
items.each_slice(batch_size).map do |batch|
batch.map { |item| process_item(item) }
end.flatten
end
def process_item(item)
# 重い処理
sleep(0.001)
item * 2
end
# 使用例
large_array = (1..10000).to_a
result = process_in_batches(large_array)
# メモリ効率の良いバッチ処理 def process_in_batches(items, batch_size = 1000) items.each_slice(batch_size).map do |batch| batch.map { |item| process_item(item) } end.flatten end def process_item(item) # 重い処理 sleep(0.001) item * 2 end # 使用例 large_array = (1..10000).to_a result = process_in_batches(large_array)
# メモリ効率の良いバッチ処理
def process_in_batches(items, batch_size = 1000)
  items.each_slice(batch_size).map do |batch|
    batch.map { |item| process_item(item) }
  end.flatten
end

def process_item(item)
  # 重い処理
  sleep(0.001)
  item * 2
end

# 使用例
large_array = (1..10000).to_a
result = process_in_batches(large_array)
  1. 早期リターンの活用
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 条件を満たす最初の要素を見つける(効率的)
def find_first_match(items)
items.each do |item|
return item if meets_criteria?(item)
end
nil
end
# 非効率な実装(全件処理する)
def find_first_match_inefficient(items)
items.select { |item| meets_criteria?(item) }.first
end
# 条件を満たす最初の要素を見つける(効率的) def find_first_match(items) items.each do |item| return item if meets_criteria?(item) end nil end # 非効率な実装(全件処理する) def find_first_match_inefficient(items) items.select { |item| meets_criteria?(item) }.first end
# 条件を満たす最初の要素を見つける(効率的)
def find_first_match(items)
  items.each do |item|
    return item if meets_criteria?(item)
  end
  nil
end

# 非効率な実装(全件処理する)
def find_first_match_inefficient(items)
  items.select { |item| meets_criteria?(item) }.first
end
  1. ベンチマークを活用した最適化
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
require 'benchmark'
def benchmark_operations
array = (1..100000).to_a
Benchmark.bm do |x|
x.report("map + select:") {
array.map { |n| n * 2 }.select { |n| n > 1000 }
}
x.report("each_with_object:") {
array.each_with_object([]) { |n, result|
doubled = n * 2
result << doubled if doubled > 1000
}
}
x.report("lazy evaluation:") {
array.lazy.map { |n| n * 2 }.select { |n| n > 1000 }.force
}
end
end
require 'benchmark' def benchmark_operations array = (1..100000).to_a Benchmark.bm do |x| x.report("map + select:") { array.map { |n| n * 2 }.select { |n| n > 1000 } } x.report("each_with_object:") { array.each_with_object([]) { |n, result| doubled = n * 2 result << doubled if doubled > 1000 } } x.report("lazy evaluation:") { array.lazy.map { |n| n * 2 }.select { |n| n > 1000 }.force } end end
require 'benchmark'

def benchmark_operations
  array = (1..100000).to_a

  Benchmark.bm do |x|
    x.report("map + select:") { 
      array.map { |n| n * 2 }.select { |n| n > 1000 }
    }

    x.report("each_with_object:") {
      array.each_with_object([]) { |n, result|
        doubled = n * 2
        result << doubled if doubled > 1000
      }
    }

    x.report("lazy evaluation:") {
      array.lazy.map { |n| n * 2 }.select { |n| n > 1000 }.force
    }
  end
end

パフォーマンス最適化のチェックリスト:

  1. データサイズの確認
  • 小規模(数千件以下):通常の実装で十分
  • 中規模(数万件):メモリ使用量に注意
  • 大規模(数十万件以上):バッチ処理やlazy評価を検討
  1. メモリ使用量の監視
  • GC.stat を使用してメモリ使用状況を確認
  • 必要に応じてプロファイリングツールを使用
  1. 処理の特性に応じた最適化
  • 全件処理が必要な場合:バッチ処理を検討
  • 条件に合う要素のみ必要:早期リターンを活用
  • メモリ制約が厳しい:lazy評価を使用

これらの最適化テクニックを適切に組み合わせることで、大規模なデータ処理でもパフォーマンスを維持することができます。

よくある間違いとトラブルシューティング

初心者がなりやすいEnumの落とし穴

  1. 破壊的メソッドと非破壊的メソッドの混同
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 良くある間違い
numbers = [1, 2, 3, 4, 5]
numbers.select! { |n| n.even? } # 破壊的メソッド
puts numbers.length # => 2(元の配列が変更されている)
# 推奨される方法
numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select { |n| n.even? } # 新しい配列を作成
puts numbers.length # => 5(元の配列は変更されていない)
puts even_numbers.length # => 2
# 良くある間違い numbers = [1, 2, 3, 4, 5] numbers.select! { |n| n.even? } # 破壊的メソッド puts numbers.length # => 2(元の配列が変更されている) # 推奨される方法 numbers = [1, 2, 3, 4, 5] even_numbers = numbers.select { |n| n.even? } # 新しい配列を作成 puts numbers.length # => 5(元の配列は変更されていない) puts even_numbers.length # => 2
# 良くある間違い
numbers = [1, 2, 3, 4, 5]
numbers.select! { |n| n.even? }  # 破壊的メソッド
puts numbers.length  # => 2(元の配列が変更されている)

# 推奨される方法
numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select { |n| n.even? }  # 新しい配列を作成
puts numbers.length  # => 5(元の配列は変更されていない)
puts even_numbers.length  # => 2
  1. ブロックの戻り値を意識していない
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 意図しない結果になる例
users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
]
# 間違った実装
result = users.map do |user|
if user[:age] > 25
user[:name].upcase
end
end
# => ["Alice", nil] # 条件に合わない場合にnilが返る
# 正しい実装
result = users.map do |user|
user[:age] > 25 ? user[:name].upcase : user[:name]
end
# => ["Alice", "BOB"]
# 意図しない結果になる例 users = [ { name: "Alice", age: 25 }, { name: "Bob", age: 30 } ] # 間違った実装 result = users.map do |user| if user[:age] > 25 user[:name].upcase end end # => ["Alice", nil] # 条件に合わない場合にnilが返る # 正しい実装 result = users.map do |user| user[:age] > 25 ? user[:name].upcase : user[:name] end # => ["Alice", "BOB"]
# 意図しない結果になる例
users = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 30 }
]

# 間違った実装
result = users.map do |user|
  if user[:age] > 25
    user[:name].upcase
  end
end
# => ["Alice", nil]  # 条件に合わない場合にnilが返る

# 正しい実装
result = users.map do |user|
  user[:age] > 25 ? user[:name].upcase : user[:name]
end
# => ["Alice", "BOB"]
  1. メソッドチェーンの順序による非効率
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
numbers = (1..1000).to_a
# 非効率な実装
result = numbers
.map { |n| n * 2 } # 1000個の要素を処理
.select { |n| n < 100 } # 不要な要素も変換している
# 効率的な実装
result = numbers
.select { |n| n < 50 } # 先にフィルタリング
.map { |n| n * 2 } # 必要な要素のみ変換
numbers = (1..1000).to_a # 非効率な実装 result = numbers .map { |n| n * 2 } # 1000個の要素を処理 .select { |n| n < 100 } # 不要な要素も変換している # 効率的な実装 result = numbers .select { |n| n < 50 } # 先にフィルタリング .map { |n| n * 2 } # 必要な要素のみ変換
numbers = (1..1000).to_a

# 非効率な実装
result = numbers
  .map { |n| n * 2 }     # 1000個の要素を処理
  .select { |n| n < 100 } # 不要な要素も変換している

# 効率的な実装
result = numbers
  .select { |n| n < 50 }  # 先にフィルタリング
  .map { |n| n * 2 }      # 必要な要素のみ変換

デバッグに使えるEnumのチェックポイント

  1. 中間結果の確認
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# デバッグ用のtapメソッドを活用
result = (1..5).to_a
.map { |n| n * 2 }
.tap { |arr| puts "After map: #{arr.inspect}" }
.select { |n| n > 5 }
.tap { |arr| puts "After select: #{arr.inspect}" }
.reduce(0, :+)
.tap { |sum| puts "Final sum: #{sum}" }
# 出力:
# After map: [2, 4, 6, 8, 10]
# After select: [6, 8, 10]
# Final sum: 24
# デバッグ用のtapメソッドを活用 result = (1..5).to_a .map { |n| n * 2 } .tap { |arr| puts "After map: #{arr.inspect}" } .select { |n| n > 5 } .tap { |arr| puts "After select: #{arr.inspect}" } .reduce(0, :+) .tap { |sum| puts "Final sum: #{sum}" } # 出力: # After map: [2, 4, 6, 8, 10] # After select: [6, 8, 10] # Final sum: 24
# デバッグ用のtapメソッドを活用
result = (1..5).to_a
  .map { |n| n * 2 }
  .tap { |arr| puts "After map: #{arr.inspect}" }
  .select { |n| n > 5 }
  .tap { |arr| puts "After select: #{arr.inspect}" }
  .reduce(0, :+)
  .tap { |sum| puts "Final sum: #{sum}" }

# 出力:
# After map: [2, 4, 6, 8, 10]
# After select: [6, 8, 10]
# Final sum: 24
  1. エラーの特定と対処

よくあるエラーとその対処法:

エラー原因対処法
NoMethodErrornilに対するメソッド呼び出し存在チェックを追加
undefined method `each’Enumerableでないオブジェクトの使用オブジェクトの型を確認
ArgumentErrorブロックパラメータの数が不正メソッドのドキュメントを確認
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# エラー発生例とその対処
users = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: nil },
{ name: "Charlie", email: "charlie@example.com" }
]
# エラーが発生する実装
begin
users.map { |user| user[:email].upcase }
rescue => e
puts "エラー発生: #{e.message}"
end
# 安全な実装
users.map { |user| user[:email]&.upcase || "NO EMAIL" }
# エラー発生例とその対処 users = [ { name: "Alice", email: "alice@example.com" }, { name: "Bob", email: nil }, { name: "Charlie", email: "charlie@example.com" } ] # エラーが発生する実装 begin users.map { |user| user[:email].upcase } rescue => e puts "エラー発生: #{e.message}" end # 安全な実装 users.map { |user| user[:email]&.upcase || "NO EMAIL" }
# エラー発生例とその対処
users = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: nil },
  { name: "Charlie", email: "charlie@example.com" }
]

# エラーが発生する実装
begin 
  users.map { |user| user[:email].upcase }
rescue => e
  puts "エラー発生: #{e.message}"
end

# 安全な実装
users.map { |user| user[:email]&.upcase || "NO EMAIL" }
  1. パフォーマンス問題のデバッグ
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
require 'benchmark'
def debug_performance
array = (1..10000).to_a
Benchmark.bm do |x|
x.report("Original:") do
array
.map { |n| n * 2 }
.select { |n| n > 1000 }
.map { |n| n.to_s }
end
x.report("Optimized:") do
array.each_with_object([]) do |n, result|
doubled = n * 2
result << doubled.to_s if doubled > 1000
end
end
end
end
# メモリ使用量のデバッグ
def monitor_memory
before = GC.stat[:heap_allocated_objects]
yield
after = GC.stat[:heap_allocated_objects]
puts "Objects allocated: #{after - before}"
end
# 使用例
monitor_memory do
result = (1..10000).to_a.map { |n| n * 2 }
end
require 'benchmark' def debug_performance array = (1..10000).to_a Benchmark.bm do |x| x.report("Original:") do array .map { |n| n * 2 } .select { |n| n > 1000 } .map { |n| n.to_s } end x.report("Optimized:") do array.each_with_object([]) do |n, result| doubled = n * 2 result << doubled.to_s if doubled > 1000 end end end end # メモリ使用量のデバッグ def monitor_memory before = GC.stat[:heap_allocated_objects] yield after = GC.stat[:heap_allocated_objects] puts "Objects allocated: #{after - before}" end # 使用例 monitor_memory do result = (1..10000).to_a.map { |n| n * 2 } end
require 'benchmark'

def debug_performance
  array = (1..10000).to_a

  Benchmark.bm do |x|
    x.report("Original:") do
      array
        .map { |n| n * 2 }
        .select { |n| n > 1000 }
        .map { |n| n.to_s }
    end

    x.report("Optimized:") do
      array.each_with_object([]) do |n, result|
        doubled = n * 2
        result << doubled.to_s if doubled > 1000
      end
    end
  end
end

# メモリ使用量のデバッグ
def monitor_memory
  before = GC.stat[:heap_allocated_objects]
  yield
  after = GC.stat[:heap_allocated_objects]
  puts "Objects allocated: #{after - before}"
end

# 使用例
monitor_memory do
  result = (1..10000).to_a.map { |n| n * 2 }
end

デバッグのベストプラクティス:

  1. 段階的な検証
  • 各処理ステップの結果を確認
  • 期待する型や値の検証
  • エッジケースのテスト
  1. エラー処理の実装
  • 適切な例外処理の追加
  • nilチェックの実装
  • デフォルト値の設定
  1. パフォーマンスモニタリング
  • 処理時間の計測
  • メモリ使用量の監視
  • ボトルネックの特定

これらの注意点とデバッグテクニックを意識することで、より信頼性の高いコードを書くことができます。

Enumを使った実装例:現場で使えるコードレシピ集

データ一括処理をEnumでエレガントに書く

実務でよく遭遇する一括処理のシナリオと、Enumを使った効率的な実装方法を紹介します。

  1. CSVデータの一括処理
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
require 'csv'
class UserDataProcessor
def self.process_csv(file_path)
CSV.read(file_path, headers: true)
.map { |row| row.to_h }
.each_with_object({valid: [], invalid: []}) do |user, result|
if valid_user?(user)
result[:valid] << normalize_user(user)
else
result[:invalid] << {
data: user,
errors: validate_user(user)
}
end
end
end
private
def self.valid_user?(user)
user['email'].to_s.include?('@') &&
user['age'].to_i.positive? &&
user['name'].to_s.length >= 2
end
def self.normalize_user(user)
{
name: user['name'].strip.capitalize,
email: user['email'].downcase,
age: user['age'].to_i
}
end
def self.validate_user(user)
errors = []
errors << 'Invalid email' unless user['email'].to_s.include?('@')
errors << 'Invalid age' unless user['age'].to_i.positive?
errors << 'Invalid name' unless user['name'].to_s.length >= 2
errors
end
end
# 使用例
# result = UserDataProcessor.process_csv('users.csv')
# puts "Valid users: #{result[:valid].length}"
# puts "Invalid users: #{result[:invalid].length}"
require 'csv' class UserDataProcessor def self.process_csv(file_path) CSV.read(file_path, headers: true) .map { |row| row.to_h } .each_with_object({valid: [], invalid: []}) do |user, result| if valid_user?(user) result[:valid] << normalize_user(user) else result[:invalid] << { data: user, errors: validate_user(user) } end end end private def self.valid_user?(user) user['email'].to_s.include?('@') && user['age'].to_i.positive? && user['name'].to_s.length >= 2 end def self.normalize_user(user) { name: user['name'].strip.capitalize, email: user['email'].downcase, age: user['age'].to_i } end def self.validate_user(user) errors = [] errors << 'Invalid email' unless user['email'].to_s.include?('@') errors << 'Invalid age' unless user['age'].to_i.positive? errors << 'Invalid name' unless user['name'].to_s.length >= 2 errors end end # 使用例 # result = UserDataProcessor.process_csv('users.csv') # puts "Valid users: #{result[:valid].length}" # puts "Invalid users: #{result[:invalid].length}"
require 'csv'

class UserDataProcessor
  def self.process_csv(file_path)
    CSV.read(file_path, headers: true)
      .map { |row| row.to_h }
      .each_with_object({valid: [], invalid: []}) do |user, result|
        if valid_user?(user)
          result[:valid] << normalize_user(user)
        else
          result[:invalid] << {
            data: user,
            errors: validate_user(user)
          }
        end
      end
  end

  private

  def self.valid_user?(user)
    user['email'].to_s.include?('@') &&
      user['age'].to_i.positive? &&
      user['name'].to_s.length >= 2
  end

  def self.normalize_user(user)
    {
      name: user['name'].strip.capitalize,
      email: user['email'].downcase,
      age: user['age'].to_i
    }
  end

  def self.validate_user(user)
    errors = []
    errors << 'Invalid email' unless user['email'].to_s.include?('@')
    errors << 'Invalid age' unless user['age'].to_i.positive?
    errors << 'Invalid name' unless user['name'].to_s.length >= 2
    errors
  end
end

# 使用例
# result = UserDataProcessor.process_csv('users.csv')
# puts "Valid users: #{result[:valid].length}"
# puts "Invalid users: #{result[:invalid].length}"

複雑な検索ロジックをEnumで整理する

検索フィルターの実装例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class ProductSearch
def initialize(products)
@products = products
end
def search(params)
results = @products
results = filter_by_price(results, params[:price_range]) if params[:price_range]
results = filter_by_category(results, params[:category]) if params[:category]
results = filter_by_keyword(results, params[:keyword]) if params[:keyword]
results = sort_results(results, params[:sort_by]) if params[:sort_by]
results
end
private
def filter_by_price(products, range)
min, max = range.split('-').map(&:to_i)
products.select { |p| p.price.between?(min, max) }
end
def filter_by_category(products, category)
products.select { |p| p.category == category }
end
def filter_by_keyword(products, keyword)
products.select { |p|
p.name.downcase.include?(keyword.downcase) ||
p.description.downcase.include?(keyword.downcase)
}
end
def sort_results(products, sort_by)
case sort_by
when 'price_asc'
products.sort_by(&:price)
when 'price_desc'
products.sort_by(&:price).reverse
when 'name'
products.sort_by(&:name)
else
products
end
end
end
# 使用例
# search = ProductSearch.new(Product.all)
# results = search.search({
# price_range: '1000-5000',
# category: 'electronics',
# keyword: 'wireless',
# sort_by: 'price_asc'
# })
class ProductSearch def initialize(products) @products = products end def search(params) results = @products results = filter_by_price(results, params[:price_range]) if params[:price_range] results = filter_by_category(results, params[:category]) if params[:category] results = filter_by_keyword(results, params[:keyword]) if params[:keyword] results = sort_results(results, params[:sort_by]) if params[:sort_by] results end private def filter_by_price(products, range) min, max = range.split('-').map(&:to_i) products.select { |p| p.price.between?(min, max) } end def filter_by_category(products, category) products.select { |p| p.category == category } end def filter_by_keyword(products, keyword) products.select { |p| p.name.downcase.include?(keyword.downcase) || p.description.downcase.include?(keyword.downcase) } end def sort_results(products, sort_by) case sort_by when 'price_asc' products.sort_by(&:price) when 'price_desc' products.sort_by(&:price).reverse when 'name' products.sort_by(&:name) else products end end end # 使用例 # search = ProductSearch.new(Product.all) # results = search.search({ # price_range: '1000-5000', # category: 'electronics', # keyword: 'wireless', # sort_by: 'price_asc' # })
class ProductSearch
  def initialize(products)
    @products = products
  end

  def search(params)
    results = @products

    results = filter_by_price(results, params[:price_range]) if params[:price_range]
    results = filter_by_category(results, params[:category]) if params[:category]
    results = filter_by_keyword(results, params[:keyword]) if params[:keyword]
    results = sort_results(results, params[:sort_by]) if params[:sort_by]

    results
  end

  private

  def filter_by_price(products, range)
    min, max = range.split('-').map(&:to_i)
    products.select { |p| p.price.between?(min, max) }
  end

  def filter_by_category(products, category)
    products.select { |p| p.category == category }
  end

  def filter_by_keyword(products, keyword)
    products.select { |p| 
      p.name.downcase.include?(keyword.downcase) ||
      p.description.downcase.include?(keyword.downcase)
    }
  end

  def sort_results(products, sort_by)
    case sort_by
    when 'price_asc'
      products.sort_by(&:price)
    when 'price_desc'
      products.sort_by(&:price).reverse
    when 'name'
      products.sort_by(&:name)
    else
      products
    end
  end
end

# 使用例
# search = ProductSearch.new(Product.all)
# results = search.search({
#   price_range: '1000-5000',
#   category: 'electronics',
#   keyword: 'wireless',
#   sort_by: 'price_asc'
# })

バッチ処理でのEnum活用テクニック

大規模なバッチ処理の実装例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class BatchProcessor
class << self
def process_in_batches(items, batch_size: 1000)
items
.each_slice(batch_size)
.with_index
.each_with_object({ success: [], failure: [] }) do |(batch, index), results|
begin
process_batch(batch, index, results)
rescue => e
log_error(e, batch, index)
end
end
end
private
def process_batch(batch, index, results)
puts "Processing batch #{index + 1}..."
batch.each do |item|
begin
processed_item = process_item(item)
results[:success] << processed_item
rescue => e
results[:failure] << {
item: item,
error: e.message
}
end
end
end
def process_item(item)
# 実際の処理をここに実装
sleep(0.01) # 処理時間のシミュレーション
{ id: item[:id], status: 'processed' }
end
def log_error(error, batch, index)
puts "Error in batch #{index + 1}: #{error.message}"
puts error.backtrace.take(5)
end
end
end
# 使用例:ログファイルの一括処理
class LogProcessor
def self.process_logs(log_files)
log_files
.flat_map { |file| read_log_file(file) }
.group_by { |log| log[:event_type] }
.transform_values do |logs|
{
count: logs.length,
earliest: logs.min_by { |log| log[:timestamp] },
latest: logs.max_by { |log| log[:timestamp] },
error_rate: calculate_error_rate(logs)
}
end
end
private
def self.read_log_file(file)
File.readlines(file)
.map(&:chomp)
.map { |line| parse_log_line(line) }
.compact
end
def self.parse_log_line(line)
timestamp, event_type, message = line.split('|').map(&:strip)
{
timestamp: Time.parse(timestamp),
event_type: event_type,
message: message,
is_error: message.include?('ERROR')
}
rescue
nil
end
def self.calculate_error_rate(logs)
error_count = logs.count { |log| log[:is_error] }
(error_count.to_f / logs.length * 100).round(2)
end
end
# 実践的なデータ変換の例
class DataTransformer
def self.transform_records(records)
records
.group_by { |r| r[:date].to_date }
.transform_values do |daily_records|
{
total_amount: calculate_total(daily_records),
average_amount: calculate_average(daily_records),
transaction_count: daily_records.length,
categories: summarize_categories(daily_records)
}
end
end
private
def self.calculate_total(records)
records.sum { |r| r[:amount] }
end
def self.calculate_average(records)
return 0 if records.empty?
records.sum { |r| r[:amount] } / records.length.to_f
end
def self.summarize_categories(records)
records
.group_by { |r| r[:category] }
.transform_values { |rs| rs.sum { |r| r[:amount] } }
end
end
class BatchProcessor class << self def process_in_batches(items, batch_size: 1000) items .each_slice(batch_size) .with_index .each_with_object({ success: [], failure: [] }) do |(batch, index), results| begin process_batch(batch, index, results) rescue => e log_error(e, batch, index) end end end private def process_batch(batch, index, results) puts "Processing batch #{index + 1}..." batch.each do |item| begin processed_item = process_item(item) results[:success] << processed_item rescue => e results[:failure] << { item: item, error: e.message } end end end def process_item(item) # 実際の処理をここに実装 sleep(0.01) # 処理時間のシミュレーション { id: item[:id], status: 'processed' } end def log_error(error, batch, index) puts "Error in batch #{index + 1}: #{error.message}" puts error.backtrace.take(5) end end end # 使用例:ログファイルの一括処理 class LogProcessor def self.process_logs(log_files) log_files .flat_map { |file| read_log_file(file) } .group_by { |log| log[:event_type] } .transform_values do |logs| { count: logs.length, earliest: logs.min_by { |log| log[:timestamp] }, latest: logs.max_by { |log| log[:timestamp] }, error_rate: calculate_error_rate(logs) } end end private def self.read_log_file(file) File.readlines(file) .map(&:chomp) .map { |line| parse_log_line(line) } .compact end def self.parse_log_line(line) timestamp, event_type, message = line.split('|').map(&:strip) { timestamp: Time.parse(timestamp), event_type: event_type, message: message, is_error: message.include?('ERROR') } rescue nil end def self.calculate_error_rate(logs) error_count = logs.count { |log| log[:is_error] } (error_count.to_f / logs.length * 100).round(2) end end # 実践的なデータ変換の例 class DataTransformer def self.transform_records(records) records .group_by { |r| r[:date].to_date } .transform_values do |daily_records| { total_amount: calculate_total(daily_records), average_amount: calculate_average(daily_records), transaction_count: daily_records.length, categories: summarize_categories(daily_records) } end end private def self.calculate_total(records) records.sum { |r| r[:amount] } end def self.calculate_average(records) return 0 if records.empty? records.sum { |r| r[:amount] } / records.length.to_f end def self.summarize_categories(records) records .group_by { |r| r[:category] } .transform_values { |rs| rs.sum { |r| r[:amount] } } end end
class BatchProcessor
  class << self
    def process_in_batches(items, batch_size: 1000)
      items
        .each_slice(batch_size)
        .with_index
        .each_with_object({ success: [], failure: [] }) do |(batch, index), results|
          begin
            process_batch(batch, index, results)
          rescue => e
            log_error(e, batch, index)
          end
        end
    end

    private

    def process_batch(batch, index, results)
      puts "Processing batch #{index + 1}..."

      batch.each do |item|
        begin
          processed_item = process_item(item)
          results[:success] << processed_item
        rescue => e
          results[:failure] << {
            item: item,
            error: e.message
          }
        end
      end
    end

    def process_item(item)
      # 実際の処理をここに実装
      sleep(0.01) # 処理時間のシミュレーション
      { id: item[:id], status: 'processed' }
    end

    def log_error(error, batch, index)
      puts "Error in batch #{index + 1}: #{error.message}"
      puts error.backtrace.take(5)
    end
  end
end

# 使用例:ログファイルの一括処理
class LogProcessor
  def self.process_logs(log_files)
    log_files
      .flat_map { |file| read_log_file(file) }
      .group_by { |log| log[:event_type] }
      .transform_values do |logs|
        {
          count: logs.length,
          earliest: logs.min_by { |log| log[:timestamp] },
          latest: logs.max_by { |log| log[:timestamp] },
          error_rate: calculate_error_rate(logs)
        }
      end
  end

  private

  def self.read_log_file(file)
    File.readlines(file)
      .map(&:chomp)
      .map { |line| parse_log_line(line) }
      .compact
  end

  def self.parse_log_line(line)
    timestamp, event_type, message = line.split('|').map(&:strip)
    {
      timestamp: Time.parse(timestamp),
      event_type: event_type,
      message: message,
      is_error: message.include?('ERROR')
    }
  rescue
    nil
  end

  def self.calculate_error_rate(logs)
    error_count = logs.count { |log| log[:is_error] }
    (error_count.to_f / logs.length * 100).round(2)
  end
end

# 実践的なデータ変換の例
class DataTransformer
  def self.transform_records(records)
    records
      .group_by { |r| r[:date].to_date }
      .transform_values do |daily_records|
        {
          total_amount: calculate_total(daily_records),
          average_amount: calculate_average(daily_records),
          transaction_count: daily_records.length,
          categories: summarize_categories(daily_records)
        }
      end
  end

  private

  def self.calculate_total(records)
    records.sum { |r| r[:amount] }
  end

  def self.calculate_average(records)
    return 0 if records.empty?
    records.sum { |r| r[:amount] } / records.length.to_f
  end

  def self.summarize_categories(records)
    records
      .group_by { |r| r[:category] }
      .transform_values { |rs| rs.sum { |r| r[:amount] } }
  end
end

これらの実装例は、実務で遭遇する典型的なシナリオに対する解決策を提供します。以下の点に注意して実装しています:

  1. エラー処理の適切な実装
  2. バッチサイズの調整による最適化
  3. 進捗状況のログ出力
  4. メモリ使用量の最適化
  5. 保守性を考慮したコード設計

これらのパターンを理解し、適切に応用することで、より効率的で保守性の高いコードを書くことができます。