第2章|今さら学ぶ「Rubyらしさ(Enumerable)」
📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ
この章でわかること
-
each/map/select/reject/findの使い分け - 「ベルトコンベア」のたとえで掴むEnumerableの考え方
- 破壊的メソッドと非破壊的メソッドの違い
- メソッドチェーンで処理をつなげるテクニック
- KnowledgeNoteのコードで見る「Rubyらしい書き方」
🏠 たとえ話で掴む「Enumerable」
第1章で、Arrayは「本棚」、Hashは「整理棚」と整理した。今回はその棚から中身を取り出して加工する方法、つまり 配列を効率よく処理する仕組み の話です。
ここでは 工場のベルトコンベア のイメージで考える。
ベルトコンベアの上を、製品(配列の要素)が1つずつ流れてくる。コンベアの途中には「検品係」や「加工係」がいて、流れてきた製品に対して決まった作業をする。
| ベルトコンベア | Rubyのメソッド | やること |
|---|---|---|
| 検品係が1つずつ確認する | each |
全要素に同じ処理を実行する |
| 加工係が製品を変形する | map |
全要素を変換して新しい配列を作る |
| 品質チェックで合格品だけ通す | select |
条件に合う要素だけ残す |
| 不良品だけ取り除く | reject |
条件に合う要素を除外する |
| 「これだ!」と1つだけ見つける | find |
条件に合う最初の1つを返す |
Enumerableとは何か — 技術的な定義
Enumerable(イニューメラブル) は、Rubyが提供する モジュール (メソッドの集合体)です。select、map、find などの便利なメソッドが50以上まとめられていて、ArrayやHashには最初から組み込まれている(include Enumerableされている)ため、特別な準備なしに使えます。
Enumerableのメソッドは内部的に each を土台にしている。つまり each さえ定義されたクラスにEnumerableをincludeすれば、select や map も自動的に使えるようになる。この設計がRubyの柔軟さを支えています(→ 第3章のモジュールで詳しく扱います)。
大事なのは、 eachだけで全部やろうとしないこと。目的に合ったメソッドを選べると、コードが格段に読みやすくなります。
🔁 each — 「1つずつ確認する」基本の繰り返し
each は最も基本的なメソッドです。配列の要素を1つずつ取り出して、ブロック(do ... end や { } で囲んだ処理)を実行する。ブロックについての詳細は(→ 第4章で詳しく扱います)。
tags = ["Ruby", "Rails", "SQL"]
# eachで1つずつ表示する
tags.each do |tag|
puts "タグ: #{tag}"
end
# => タグ: Ruby
# => タグ: Rails
# => タグ: SQL
each は「全員に同じ処理をする」ときに使う。ただし、 eachの中で新しい配列を作ろうとすると冗長になりがち です。第1章の最後に書いたコードがまさにそのパターンでした。
# eachで「公開済み記事のタイトル」を集める — やや冗長
published_titles = []
articles.each do |article|
if article[:status] == "published"
published_titles << article[:title]
end
end
これをもっとスッキリ書けるのが、次の select と map です。
🔍 select / reject — 「条件で絞り込む」
select — 合格品だけ通す
select は、条件に合う要素だけを残して新しい配列を返す。品質チェックの合格品だけをコンベアの先に流すイメージです。
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 偶数だけを選ぶ
evens = numbers.select { |n| n.even? }
puts evens # => [2, 4, 6, 8, 10]
ブロックの中の条件が true になった要素だけが残る。
reject — 不良品を取り除く
reject は select の逆で、条件に合う要素を 除外 する。
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 偶数を除外する(= 奇数だけ残す)
odds = numbers.reject { |n| n.even? }
puts odds # => [1, 3, 5, 7, 9]
「〜を除きたい」ときは reject、「〜だけ残したい」ときは select。
🔄 map — 「全部を変換する」
map は、配列の全要素を変換して新しい配列を作る。加工係が製品を1つずつ別の形に変えていくイメージです。
names = ["tanaka", "suzuki", "yamada"]
# 全員の名前を大文字に変換する
upper_names = names.map { |name| name.upcase }
puts upper_names # => ["TANAKA", "SUZUKI", "YAMADA"]
元の配列 names は変わらない。map は 新しい配列を返す のがポイントです。
each と map の決定的な違い
tags = ["Ruby", "Rails", "SQL"]
# each — 戻り値は元の配列そのまま(処理結果は返さない)
result_each = tags.each { |tag| tag.downcase }
puts result_each # => ["Ruby", "Rails", "SQL"](元のまま!)
# map — 戻り値は変換後の新しい配列
result_map = tags.map { |tag| tag.downcase }
puts result_map # => ["ruby", "rails", "sql"](変換された!)
each は「処理を実行するだけ」で結果の配列を返さない。「変換した結果を使いたい」なら map を使います。
🎯 find — 「最初の1つを見つける」
find は、条件に合う 最初の1つ だけを返す。見つかった瞬間に探索を終了するので、全要素を調べる必要がないときに効率的です。
articles = [
{ id: 1, title: "Ruby入門", status: "draft" },
{ id: 2, title: "Rails基礎", status: "published" },
{ id: 3, title: "SQL完全理解", status: "published" }
]
# 最初に見つかった公開済み記事を取得
first_published = articles.find { |a| a[:status] == "published" }
puts first_published[:title] # => "Rails基礎"
select との違いは、select が「全部集める(配列を返す)」のに対し、find は「最初の1つだけ(単体を返す)」という点です。
⛓️ メソッドチェーン — ベルトコンベアをつなげる
ここまでのメソッドは つなげて使える のが大きな強み。ベルトコンベアの先にさらにコンベアを接続するイメージです。
第1章で書いた冗長なコードを、メソッドチェーンで書き直してみる。
articles = [
{ title: "Ruby入門", status: "published" },
{ title: "Rails基礎", status: "draft" },
{ title: "SQL完全理解", status: "published" },
{ title: "Git入門", status: "draft" }
]
# before: eachで書いた場合(第1章の書き方)
published_titles = []
articles.each do |article|
if article[:status] == "published"
published_titles << article[:title]
end
end
# after: select + map で書いた場合(Rubyらしい書き方)
published_titles = articles
.select { |a| a[:status] == "published" } # 公開済みだけ残す
.map { |a| a[:title] } # タイトルだけ取り出す
puts published_titles # => ["Ruby入門", "SQL完全理解"]
6行が2行になった。しかも読み方が自然で、「公開済みを選んで(select)→ タイトルを取り出す(map)」と、日本語の順番どおりに読めます。
⚠️ 破壊的メソッドと非破壊的メソッド
Rubyには、 元のデータを変更するメソッド(破壊的) と 元のデータを変えずに新しいデータを返すメソッド(非破壊的) の2種類がある。
これを 原本とコピー にたとえると:
- 非破壊的メソッド = 原本はそのまま残して、 コピーに修正を加える
- 破壊的メソッド = 原本そのものに直接修正を加える (元に戻せない)
tags = ["Ruby", "Rails", "SQL"]
# 非破壊的メソッド — 元の配列は変わらない
sorted = tags.sort
puts sorted # => ["Rails", "Ruby", "SQL"](ソート済みのコピー)
puts tags # => ["Ruby", "Rails", "SQL"](元のまま!)
# 破壊的メソッド(!がつく)— 元の配列が変わる
tags.sort!
puts tags # => ["Rails", "Ruby", "SQL"](元が変わった!)
見分け方のルール
Rubyでは、破壊的メソッドの多くに !(ビックリマーク) がついている。
# 非破壊的(コピーを返す)
tags.map { |t| t.upcase } # tagsは変わらない
tags.select { |t| t.size > 3 } # tagsは変わらない
tags.sort # tagsは変わらない
# 破壊的(元を変える)
tags.map! { |t| t.upcase } # tags自体が変わる
tags.select! { |t| t.size > 3} # tags自体が変わる
tags.sort! # tags自体が変わる
⚠️ ただし、
<<(push)やdeleteのように、!がなくても破壊的なメソッドもある。「!がなければ安全」とは限りません。
基本的には 非破壊的メソッドを使うのが安全 です。元のデータを変えてしまうと、他の場所で使っているときに予期しないバグを生みます。
🛠️ KnowledgeNoteでの具体例
KnowledgeNoteの記事データを使った実践的な例です。
# 記事一覧から、条件に合うデータを抽出する
articles = [
{ title: "Ruby入門", status: "published", likes: 15, tags: ["Ruby"] },
{ title: "Rails基礎", status: "draft", likes: 0, tags: ["Rails"] },
{ title: "SQL完全理解", status: "published", likes: 42, tags: ["SQL", "DB"] },
{ title: "Git入門", status: "published", likes: 8, tags: ["Git"] },
{ title: "Docker入門", status: "draft", likes: 0, tags: ["Docker"] }
]
# 公開済み記事の中で、いいねが10以上のタイトルを取得
popular_titles = articles
.select { |a| a[:status] == "published" } # 公開済みだけ
.select { |a| a[:likes] >= 10 } # いいね10以上
.map { |a| a[:title] } # タイトルを取り出す
# => ["Ruby入門", "SQL完全理解"]
# 全記事のタグを1つの配列にまとめる(flattenで平坦化)
all_tags = articles
.map { |a| a[:tags] } # => [["Ruby"], ["Rails"], ["SQL", "DB"], ...]
.flatten # => ["Ruby", "Rails", "SQL", "DB", "Git", "Docker"]
.uniq # => 重複を除去
# => ["Ruby", "Rails", "SQL", "DB", "Git", "Docker"]
Railsでは、このような操作をActiveRecordの スコープ (→ 第16章)やクエリメソッドで行うが、考え方は同じ。「絞り込んで → 変換して → 取り出す」という流れは、Ruby全体を通して使える基本パターンです。
📊 使い分け早見表
| やりたいこと | メソッド | 戻り値 |
|---|---|---|
| 全要素に処理を実行したい | each |
元の配列 |
| 全要素を変換したい | map |
新しい配列 |
| 条件に合うものだけ残したい | select |
新しい配列 |
| 条件に合うものを除外したい | reject |
新しい配列 |
| 条件に合う最初の1つを見つけたい | find |
1つの要素(or nil) |
| 全要素を1つの値にまとめたい | reduce |
1つの値 |
| 条件を満たすか全体で判定したい |
any? / all? / none?
|
true / false |
💼 面接で聞かれたら?
Q:each と map の違いを説明してください。
「
eachは配列の全要素に処理を実行しますが、戻り値は元の配列そのままです。一方mapは全要素を変換した新しい配列を返します。変換結果を使いたいときはmap、単に処理を実行するだけならeachを使い分けます。」深掘りされたら:
- 「破壊的メソッドとは?」→ 元のオブジェクトを直接変更するメソッド。
sort!やmap!のように末尾に!がつくものが多いが、<<やdeleteのように!がなくても破壊的なものもある。- 「select と find の違いは?」→
selectは条件に合う全要素を配列で返す。findは条件に合う最初の1つだけを返す。
🔗 もっと深く知りたい人へ(1次情報リンク)
- Ruby公式リファレンス:Enumerable — select / map / find など全メソッドの一覧
- Ruby公式リファレンス:Array — 配列固有のメソッド(flatten, uniq, sort等)
まとめ
- ✅ Enumerableは「ベルトコンベア」。配列の要素を1つずつ処理するメソッド群をまとめたモジュール
- ✅
eachは処理の実行、mapは変換、selectは絞り込み、findは1つ探す - ✅ メソッドチェーンで処理をつなげると、Rubyらしく簡潔に書ける
- ✅ 破壊的メソッド(
!付き)は元のデータを変える。基本は非破壊的メソッドを使う - ✅ 「eachだけで全部やる」のではなく、目的に合ったメソッドを選ぶのがRubyらしさ
📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに