Ruby メソッドチェーン実践:select・map・inject の活用パターン
Ruby の Enumerable メソッドはチェーンでつなぐことで、複雑な処理を宣言的かつ簡潔に書けます。実際のコードでよく登場するパターンをまとめます。
カートの合計金額を計算する
在庫あり・数量1以上の商品だけ対象にして合計金額を出す例です。
cart = [
{ name: 'apple', price: 100, qty: 2, in_stock: true },
{ name: 'banana', price: 80, qty: 0, in_stock: false }, # 数量0
{ name: 'cherry', price: 200, qty: 1, in_stock: true },
{ name: 'grape', price: 300, qty: 1, in_stock: false }, # 在庫なし
]
total = cart
.select { |item| item[:in_stock] && item[:qty] > 0 } # 有効な商品だけ絞り込む
.sum { |item| item[:price] * item[:qty] } # 小計を合計
# => 400(apple: 200 + cherry: 200)
Ruby では map → sum を sum のブロックにまとめられるため、JavaScript より簡潔になります。
タグの出現ランキングを作る
記事に付いたタグを集計して、出現回数順のランキングを作る例です。
articles = [
{ title: '記事A', tags: %w[ruby rails rspec] },
{ title: '記事B', tags: %w[ruby gem] },
{ title: '記事C', tags: %w[rails rspec] },
{ title: '記事D', tags: %w[ruby rails] },
]
top3 = articles
.flat_map { |a| a[:tags] } # タグを1つの配列に展開
.tally # { "ruby"=>3, "rails"=>3, "rspec"=>2, "gem"=>1 }
.sort_by { |_, count| -count } # 出現回数の降順
.first(3) # 上位3件
.map { |tag, count| { tag:, count: } } # 整形(Ruby 3.1+ のショートハンド)
# => [{ tag: "ruby", count: 3 }, { tag: "rails", count: 3 }, { tag: "rspec", count: 2 }]
tally は Ruby 2.7 以降で使えます。
アクティブユーザーのスコアランキングを作る
有効なユーザーだけを対象にスコア降順で上位3件を取り出し、順位付きで整形する例です。
users = [
{ name: 'Alice', score: 80, active: true },
{ name: 'Bob', score: 95, active: false }, # 無効
{ name: 'Carol', score: 72, active: true },
{ name: 'Dave', score: 88, active: true },
{ name: 'Eve', score: 91, active: true },
]
ranking = users
.select { |u| u[:active] } # 有効なユーザーだけ
.sort_by { |u| -u[:score] } # スコア降順
.first(3) # 上位3件
.map.with_index(1) do |u, rank| # 順位付きで整形
{ rank:, name: u[:name], score: u[:score] }
end
# => [
# { rank: 1, name: "Eve", score: 91 },
# { rank: 2, name: "Dave", score: 88 },
# { rank: 3, name: "Alice", score: 80 },
# ]
ユーザー別の売上合計を出す
注文データをユーザーごとにまとめて、売上の多い順に並べる例です。
orders = [
{ user: 'Alice', amount: 1000 },
{ user: 'Bob', amount: 500 },
{ user: 'Alice', amount: 2000 },
{ user: 'Carol', amount: 1500 },
{ user: 'Bob', amount: 800 },
]
ranking = orders
.group_by { |o| o[:user] } # ユーザーごとにグループ化
.transform_values { |os| os.sum { |o| o[:amount] } } # 各ユーザーの合計を計算
.sort_by { |_, total| -total } # 売上降順
.map { |user, total| { user:, total: } } # 整形
# => [
# { user: "Alice", total: 3000 },
# { user: "Carol", total: 1500 },
# { user: "Bob", total: 1300 },
# ]
group_by → transform_values → sort_by は集計系で非常によく使うパターンです。
カテゴリ別の最高スコア商品を取り出す
カテゴリごとに最も評価の高い商品を1件ずつ取り出す例です。
products = [
{ name: 'MacBook', category: 'pc', score: 90 },
{ name: 'ThinkPad', category: 'pc', score: 85 },
{ name: 'iPhone', category: 'phone', score: 95 },
{ name: 'Pixel', category: 'phone', score: 88 },
{ name: 'iPad', category: 'tablet', score: 82 },
]
best_per_category = products
.group_by { |p| p[:category] } # カテゴリごとにグループ化
.transform_values { |ps| ps.max_by { |p| p[:score] } } # 各カテゴリの最高スコア商品
.values # ハッシュの値だけ取り出す
.sort_by { |p| -p[:score] } # スコア降順
# => [
# { name: "iPhone", category: "phone", score: 95 },
# { name: "MacBook", category: "pc", score: 90 },
# { name: "iPad", category: "tablet", score: 82 },
# ]
チェーンを書くときのポイント
# ❌ 読みにくい result = data.select{|x|x[:active]}.map{|x|x[:value]*2}.inject(:+) # ✅ 読みやすい:処理ごとに改行する result = data .select { |x| x[:active] } # 有効なものだけ .map { |x| x[:value] * 2 } # 2倍に変換 .sum # 合計
select→map→sum/injectの順が基本(絞り込んでから変換・集計)mapしてからselectすると不要な変換が走るため、selectを先にgroup_by→transform_valuesはハッシュ集計の定番セット
まとめ
| パターン | チェーン |
|---|---|
| 有効なものを集計する | select → sum |
| 有効なものを変換して集計する | select → map → inject |
| タグ・ラベルを集計する | flat_map → tally → sort_by |
| ランキングを作る | select → sort_by → first |
| グループ別に集計する | group_by → transform_values → sort_by |
| カテゴリ別の最良を取る | group_by → transform_values { max_by } |
各メソッドの詳細は以下を参照してください。 - 「Ruby 配列の絞り込み:select・reject・find の使い方」 - 「Ruby 配列の変換・集計:map・inject・flat_map の使い方」 - 「Ruby ハッシュの繰り返し:each・map・select の使い方」