Railsの検索とフィルタの設計!初心者でもわかるParamsとクエリオブジェクトの使い方
生徒
「Railsで一覧表示の検索や絞り込みをしたいんですが、どこに書けばいいのかわかりません…」
先生
「それなら、コントローラをスッキリ保ちながら検索を実装できる方法がありますよ。」
生徒
「パラメータをどう処理するのかもよく分からなくて…」
先生
「それでは、Paramsオブジェクトとクエリオブジェクトを使って、検索とフィルタ処理を分かりやすく設計する方法を学びましょう!」
1. Railsで検索・フィルタをする場面とは?
Webアプリケーションでは、データの一覧画面で検索やフィルタ(絞り込み)が必要になる場面がよくあります。たとえば、ブログ記事をタイトルで検索したり、カテゴリーで絞り込んだりするケースです。
Railsでは、こういった機能をコントローラに直接書くこともできますが、それではコードが長くなり、メンテナンスがしづらくなってしまいます。そこで、検索専用の処理を切り出す「クエリオブジェクト」や、パラメータを整理して使いやすくする「Paramsオブジェクト」が登場します。
2. パラメータとは?Paramsの基本
パラメータとは、ユーザーが検索フォームなどから送信する「条件」のことです。Railsでは、この情報はparamsというハッシュの形でコントローラに渡されます。
たとえば、タイトルを検索するフォームで「Ruby」と入力した場合、以下のようなリクエストになります。
<form action="/posts" method="get">
<input type="text" name="title" value="Ruby">
<button type="submit">検索</button>
</form>
このフォームを送信すると、URLには?title=Rubyのようにパラメータが付きます。Railsではこれを次のように受け取ります。
params[:title] #=> "Ruby"
3. フィルタ処理をコントローラに書くとどうなる?
検索や絞り込みの処理をすべてコントローラに書くと、次のようにゴチャゴチャしてしまいます。
def index
posts = Post.all
posts = posts.where("title LIKE ?", "%#{params[:title]}%") if params[:title].present?
posts = posts.where(category: params[:category]) if params[:category].present?
render :index, locals: { posts: posts }
end
このように、条件が増えるとコントローラの中身がどんどん複雑になっていきます。これでは、コードの再利用やテストがしづらくなります。
4. クエリオブジェクトで検索処理を整理しよう
そこで登場するのがクエリオブジェクトです。検索専用のクラスを作ることで、ロジックをスッキリまとめることができます。
次のようなPostSearchクラスを作成します。
class PostSearch
def initialize(params)
@params = params
end
def results
scope = Post.all
scope = scope.where("title LIKE ?", "%#{@params[:title]}%") if @params[:title].present?
scope = scope.where(category: @params[:category]) if @params[:category].present?
scope
end
end
コントローラ側は次のようにシンプルになります。
def index
search = PostSearch.new(params)
render :index, locals: { posts: search.results }
end
これにより、ロジックと表示の分離ができ、保守性の高いコードになります。
5. Paramsオブジェクトでパラメータの取り扱いを安全に
さらに一歩進めて、Paramsオブジェクトを使うことで、パラメータの安全性や意図の明確化ができます。
次のように、Strong Parametersの仕組みを使って安全なパラメータだけを許可します。
def search_params
params.permit(:title, :category)
end
これをクエリオブジェクトに渡せば、不正なパラメータが混入するのを防げます。
search = PostSearch.new(search_params)
6. 検索フォームとの連携
クエリオブジェクトとParamsオブジェクトを使えば、検索フォームともスムーズに連携できます。
例えば、indexページに以下のようなフォームを用意します。
<form action="/posts" method="get" class="mb-3">
<input type="text" name="title" placeholder="タイトルで検索" class="form-control mb-2">
<select name="category" class="form-select mb-2">
<option value="">すべてのカテゴリ</option>
<option value="tech">技術</option>
<option value="life">生活</option>
</select>
<button type="submit" class="btn btn-primary">検索</button>
</form>
検索条件を入力して送信すると、コントローラがクエリオブジェクトで結果を取得し、ビューに表示する仕組みです。
7. 使い方の工夫と応用
このような設計にすると、次のようなメリットがあります。
- テストしやすくなる(クエリオブジェクトを単体でテスト可能)
- 条件の追加や変更がしやすい
- 複数の画面で共通の検索処理を使い回せる
Railsでよくある検索機能やフィルタ機能を実装する際は、このように「責務を分ける設計」を意識すると、アプリ全体が読みやすく、拡張しやすくなります。
まとめ
ここまで、Ruby on Railsにおける検索機能とフィルタ機能の設計について詳しく解説してきました。Webアプリケーションを開発する際、データが増えるにつれて「特定の条件で情報を絞り込む」という機能は欠かせない存在になります。しかし、そのロジックを安易にコントローラへ書き並べてしまうと、プログラムの可読性が著しく低下し、バグの温床となってしまいます。
そこで重要になるのが、今回のテーマである「クエリオブジェクト(Query Object)」と「Paramsオブジェクト」の活用です。これらを導入することで、コントローラは「リクエストを受け取り、結果をビューに渡す」という本来の役割に専念でき、複雑なSQLの発行条件やパラメータの加工処理を別クラスに切り出すことができます。これはオブジェクト指向における「単一責任の原則」に基づいた非常にクリーンな設計手法です。
検索機能におけるSQLの挙動を確認しよう
実際にデータベースに対してどのようなクエリが投げられているのか、具体的なデータ構造を例に見てみましょう。まず、検索実行前のpostsテーブルの状態を想定します。
id | title | category | created_at
---+----------------------+----------+--------------------
1 | Rails入門ガイド | tech | 2026-01-01 10:00:00
2 | 週末のキャンプ記録 | life | 2026-01-05 12:00:00
3 | Rubyの便利なメソッド | tech | 2026-01-10 15:00:00
4 | 美味しいカフェ巡り | life | 2026-01-15 09:00:00
5 | Dockerで環境構築 | tech | 2026-01-20 18:00:00
ここで、ユーザーがタイトルに「Ruby」を含み、かつカテゴリが「tech」のものを検索したとします。このとき、内部で実行されるSQLとクエリオブジェクトの挙動は以下のようになります。
SELECT "posts".*
FROM "posts"
WHERE (title LIKE '%Ruby%')
AND "posts"."category" = 'tech';
このSQLが実行された結果、次のようなレコードが抽出されます。
id | title | category | created_at
---+----------------------+----------+--------------------
3 | Rubyの便利なメソッド | tech | 2026-01-10 15:00:00
クエリオブジェクトの実装を深掘りする
クエリオブジェクト内では、ActiveRecord::Relationオブジェクトをメソッドチェーンで繋いでいくのが一般的です。これにより、条件が指定されていない場合は全件取得のスコープを維持しつつ、条件がある場合のみwhere句を追加していく柔軟な処理が可能になります。
class PostSearch
def initialize(params)
# paramsはStrong Parametersでフィルタリング済みであることを想定
@params = params
end
def results
scope = Post.all
scope = filter_by_title(scope)
scope = filter_by_category(scope)
scope
end
private
def filter_by_title(scope)
return scope if @params[:title].blank?
scope.where("title LIKE ?", "%#{@params[:title]}%")
end
def filter_by_category(scope)
return scope if @params[:category].blank?
scope.where(category: @params[:category])
end
end
上記のようにプライベートメソッドとして切り出すことで、検索条件が増えた際(例えば「作成日での絞り込み」など)も、メソッドを追加するだけで対応できるようになります。
フロントエンド(View)での実装ポイント
Railsのform_withヘルパーを使用する場合、検索フォームはmethod: :getで作成するのが基本です。検索はデータの更新を伴わないため、URLにパラメータを含めることで、検索結果のページをブックマークしたり、URLを共有したりすることが可能になります。
<%= form_with url: posts_path, method: :get, local: true do |f| %>
<div class="field">
<%= f.label :title, "キーワード" %>
<%= f.text_field :title, value: params[:title] %>
</div>
<div class="field">
<%= f.label :category, "カテゴリ" %>
<%= f.select :category, [['技術', 'tech'], ['生活', 'life']], include_blank: "選択してください", selected: params[:category] %>
</div>
<%= f.submit "検索する" %>
<% end %>
このように、value: params[:title]のように指定しておくことで、検索実行後も入力した値がフォームに残るようになり、ユーザー体験が向上します。
さらなる応用:並び替え(ソート)機能
検索条件だけでなく、並び替え機能もクエリオブジェクトに追加するとより強力です。例えば、「新着順」「古い順」などの切り替えも、パラメータ一つで制御できます。
def results
scope = Post.all
scope = filter_by_title(scope)
scope = filter_by_category(scope)
scope = sort_scope(scope)
scope
end
private
def sort_scope(scope)
case @params[:sort]
when 'recent'
scope.order(created_at: :desc)
when 'old'
scope.order(created_at: :asc)
else
scope.order(id: :desc)
end
end
このように、クエリオブジェクトという「データの問い合わせ担当者」を用意することで、Railsアプリケーションのコードは格段に読みやすく、そして強固なものへと進化します。最初は難しく感じるかもしれませんが、中規模以上の開発では必須のテクニックですので、ぜひマスターしてください。
生徒
「先生、ありがとうございました!クエリオブジェクトを使うことで、コントローラが驚くほどスッキリしました。今まではif params[:xxx].present?をコントローラに羅列していたので、コードを見るのが苦痛だったんです。」
先生
「そうですね。コントローラにビジネスロジックや複雑なクエリが入り込むと、いわゆる『Fat Controller(太ったコントローラ)』になってしまいます。クエリオブジェクトに切り出すことで、テストも書きやすくなったと思いませんか?」
生徒
「はい!これなら『検索ロジックだけをテストする』ということができますね。データベースの実行結果もSQLのログを見ると、ちゃんと意図した通りにANDで結合されていて安心しました。」
先生
「その通りです。また、今回はParamsオブジェクトについても触れましたが、Strong Parametersをしっかり通すことで、意図しないカラムを検索条件に入れられる脆弱性を防ぐことも重要です。セキュリティ面でもこの設計は有利なんですよ。」
生徒
「なるほど。ただ、小さな個人開発でもクエリオブジェクトは作ったほうがいいんでしょうか?」
先生
「検索項目が1つや2つなら、モデルのscopeを使うだけでも十分な場合があります。でも、項目が3つを超えたり、並び替えやページネーションが絡んでくるなら、最初からクエリオブジェクトを導入しておくと、後々の拡張が楽になりますよ。大事なのは、そのコードが『どこに何が書いてあるか一目でわかるか』という点です。」
生徒
「分かりました。まずは今のプロジェクトの検索機能を、このクエリオブジェクト形式にリファクタリングしてみます!」