みなさん、こんにちは。
前回は、データベースの構造変更を管理するマイグレーションを取り上げました。テーブルを用意してデータを保存したら、次は必要なデータを取り出す番です。今回は、Active Recordを使った検索について学びましょう。
記事の詳細画面では特定の記事を1件、一覧画面では公開中の記事を複数件取り出します。同じ検索でも、画面の目的によって必要なデータは異なります。試験対策では、find、find_by、where の使い分けと戻り値の違いを押さえることが大切です。実務では、それに加えて、検索する範囲や件数も考えます。メソッド名を覚えるだけでなく、「何を、どの条件で、何件取り出したいのか」をコードにできるようにしていきましょう。今回もRails 7.1を前提に説明します。
クエリとは何か
クエリとは、データベースに対する問い合わせです。Active Recordでは、Rubyのメソッドで検索条件を指定すると、その内容に応じたSQLが組み立てられます。検索結果の各レコードは、通常、モデルのインスタンスとして扱えます。以降は、記事を表す Article モデルと articles テーブルがあり、id、title、published、user_id、created_at の各カラムを持つ例で考えます。
1件探すならfindかfind_by
主キーであるidが分かっていて、その記事を取り出したい場合は find を使います。一方、タイトルなどの条件で1件探したい場合は find_by を使います。
article = Article.find(10)
article = Article.find_by(title: "Rails入門")find(10) はidが10の記事を返し、見つからなければ ActiveRecord::RecordNotFound という例外が発生します。記事の詳細画面のように、指定されたIDのデータが存在することを前提とした処理で使います。なお、find に複数のIDを渡す使い方もありますが、ここではIDを1つ指定する場合を扱います。
find_by は条件に一致するレコードを1件返し、見つからなければ nil を返します。そのため、見つからない場合も通常の処理として分岐したいときに使えます。ただし、タイトルが同じ記事が複数あっても、すべては返しません。並び順を指定していなければ、どの1件が返るかも保証されません。検索条件だけでなく、該当するデータがないときにどう扱うかまで考えて選びましょう。
一覧を絞り込むならwhere
条件に一致する記事を一覧として扱いたい場合は where を使います。次の例は、公開中の記事のうち、user_id が5の記事に絞り込むものです。
articles = Article.where(published: true, user_id: 5)ハッシュに複数の条件を指定すると、両方を満たすレコードが対象になります。ここで where が返すのは、配列ではなく ActiveRecord::Relation というオブジェクトです。検索条件を保持しており、さらに条件や並び順を付け加えられます。実際に検索した結果が1件でも、where の戻り値が1つの Article に変わるわけではありません。該当する記事がなければ、読み込んだ結果は空になります。
where で条件を組み立てただけでは、通常はまだレコードを読み込みません。each で順番に扱ったり、to_a で配列に変換したりする段階で検索を実行します。この仕組みにより、必要な条件をまとめてから問い合わせられます。なお、Railsコンソールでは結果の表示に伴って検索が実行されることがあります。
並び順と件数を決める
公開中の記事を新しい順に10件表示したい場合は、where に order と limit をつなげます。
articles = Article.where(published: true)
.order(created_at: :desc, id: :desc)
.limit(10)order は並び順を指定します。desc は降順、asc は昇順です。この例では作成日時が新しい記事を先にし、日時が同じならidが大きい記事を先にします。limit(10) は取得件数を最大10件に制限します。並び順を指定せずに limit だけを付けても、「新しい10件」にはなりません。
一覧画面に10件だけ必要なら、データベース側で絞り込みと件数制限を行います。全件をRubyの配列へ読み込んでから選び出すと、表示に使わないデータまで取り込むことになります。データが増えても必要な範囲を取得できるよう、画面の要件を検索条件に反映しましょう。
入力値を検索条件に使う
利用者が入力したタイトルで検索する場合は、ハッシュで値を渡せます。比較演算子を使いたい場合は、SQLの条件部分に ? を置き、値を別の引数として渡します。
Article.where(title: params[:title])
Article.where("created_at >= ?", Time.current.beginning_of_day)1行目はタイトルが一致する記事、2行目はアプリケーションのタイムゾーンで今日の午前0時以降に作成された記事を対象にします。? は値を入れる位置を表すプレースホルダーです。SQLの条件文字列に入力値を直接埋め込むと、入力がSQLの一部として解釈される危険があります。値を別に渡せば、Active Recordが値として適切に扱います。
また、安全な条件指定と、閲覧権限の確認は別の話です。ログイン中の利用者自身の記事を探す画面なら、current_user.articles.where(...) のように、検索対象をその利用者の記事に限定します。これは認証・認可の回と、アソシエーションの回で学んだ内容にもつながります。
関連データも表示するならincludes
記事一覧で投稿者名も表示する場合を考えましょう。Article に belongs_to :user があり、User に name カラムがあるものとします。記事ごとに article.user.name を読むと、投稿者を取得する問い合わせが繰り返されることがあります。最初の記事一覧の検索に加え、各記事で関連先を検索する問題をN+1問題と呼びます。
articles = Article.includes(:user)
.order(created_at: :desc, id: :desc)
.limit(10)
articles.each do |article|
puts article.user.name
endincludes(:user) を指定すると、表示に使う投稿者をまとめて読み込めます。この例では通常、記事と投稿者をそれぞれ取得する2回の問い合わせになります。ただし、条件によってはテーブルを結合して取得するため、includes が常に同じSQLを実行するわけではありません。関連先を表示する一覧で検討し、実際の問い合わせは開発時のログで確認しましょう。
模擬問題
問題1
タイトルが一致する記事を1件探し、見つからなければ nil を受け取りたい場合、最も適切なものを1つ選びなさい。
1. Article.find(10)
2. Article.find_by(title: "Rails入門")
3. Article.where(title: "Rails入門")
4. Article.find_by!(title: "Rails入門")
正解:2
解説:find_by は条件で1件探し、該当しなければ nil を返します。1はタイトルではなく主キーで検索します。3は Relation を返すため、1件または nil を受け取る用途とは異なります。4の find_by! は見つからない場合に例外を発生させます。検索後に行う処理に合わせて選びましょう。
問題2
公開中の記事を新しい順に最大10件表示する設計として、最も適切なものを1つ選びなさい。
1. 全記事を配列に変換してから、公開状態と並び順を調整する
2. find_by で公開中の記事を取得し、10回表示する
3. where、order、limit を組み合わせる
4. limit(10) だけで新しい記事が選ばれると考える
正解:3
解説:3なら必要な条件、順序、件数をデータベースへの問い合わせに反映できます。1は不要な記事まで読み込みます。2は1件を取得するため一覧になりません。4は並び順の指定がなく、新しい順は保証されません。必要なデータを取得する段階で絞ることが、負荷を抑える設計につながります。
問題3
記事一覧で投稿者名を表示したところ、記事ごとに投稿者の検索が発生していました。改善方法として、最も適切なものを1つ選びなさい。
1. 記事の取得時に includes(:user) を指定する
2. 各記事で User.find を呼び直す
3. where を find_by に置き換える
4. ビューに投稿者検索のSQLを直接書く
正解:1
解説:1は関連する投稿者をまとめて読み込み、繰り返しの検索を減らします。2では記事ごとの検索が残ります。3は取得結果が1件になり、一覧の要件を満たしません。4はSQLを書く場所を変えるだけで解決せず、ビューにデータ取得の責務も持たせてしまいます。
まとめ
今回は、必要なデータに合わせた検索方法を確認しました。主キーで探すなら find、条件で1件探すなら find_by、条件に合う一覧を扱うなら where を使います。試験対策では、見つからない場合の動作と、モデルのインスタンスか Relation かという戻り値の違いを整理しましょう。
実務では、order と limit で取得する範囲を決め、入力値を安全に渡します。関連データを表示する場合は includes も検討します。短いコードでも、どのデータを取得するのかを説明できることが大切です。次回はテストの基礎を取り上げ、期待した動作をコードで確かめる方法を学びます。
Rails7認定ベーシック試験について
全国300か所で通年実施しています。詳細は以下をご覧ください。

また、450ページを超える教科書も安価に出版しています。学習にあたってご活用ください。詳細は以下をご覧ください。
Rails 7 技術者認定ベーシック試験公式教科書ベータ版
著者:小澤昌樹 発行:Rails技術者認定試験運営委員会
価格(税込):ペーパーバック版 2,497円 Kindle版 1250円
ページ数:471ページ


