Ransackが解決する問題
Rails開発において、ほとんどのデータリスト(users、products、orders…)には、複数の条件に基づく検索・フィルター機能が必要です。もし手でSQLスコープやArel queryを書く場合、以下の問題に直面します:
- 検索条件の組み合わせごとにWHERE句を書き直す必要がある
- エッジケースやバリデーション処理の実装
- 複数のパターンに対するテスト時間の増加
- 要件変更時の保守が困難
Ransack は、パラメータDSLとpredicates(_eq、_cont、_gteqなど)を通じて、ActiveRecord queryやSQLを直接書かずに、検索条件を宣言的に定義できるgemです。
Ransackを選ぶべき理由
Ransackが活躍するユースケース
Ransackは以下の場面で最適です:
-
Admin Dashboard & CRUD インターフェース
複数フィルタ条件を持つusers、orders、productsのリスト。Ransackなら複雑なフィルター機能でもコードをシンプルに保てます。 -
複雑なレポート機能が必要なビジネスアプリケーション
日付範囲、数値範囲、文字列マッチング、ブール値フラグを同時にフィルタリングする必要があるケース。例:「X年X月からY年Y月の間、ステータスが「pending」、かつcustomer_idが特定の値」という複合条件。 -
迅速なMVP・プロトタイプ開発
Ransackを使えば、検索機能を数分で実装でき、最初からクエリ最適化を設計する手間がかかりません。 -
Rails 7.2+ / Ruby 3.1+ で、中程度の規模のデータ量
数十万~数百万件のレコードでも、適切にインデックスを張ればRansackは十分なパフォーマンスを発揮します。
Ransackを使うべきでない場面
- 本格的なフルテキスト検索:複雑な関連性ランキングが必要 → Elasticsearch、Algolia推奨
- 大規模リアルタイム分析:数百万件以上のレコード、複雑なクエリ → 独自のDBインデックス戦略やデータウェアハウス検討
- 多言語・ダイアクリティカル対応検索:Ransackには制限あり → Elasticsearch検討
- SaaS規模のパフォーマンス要件:パフォーマンスが極めて重要 → 専門のサービス利用
Ransackのインストール
ステップ1:Gemfileに追加
gem 'ransack'
ステップ2:bundle install
bundle install
Ransackはマイグレーションやジェネレータが不要です。すぐにコントローラーとビューで使用開始できます。
基本概念
1. Search Object(検索オブジェクト)
Search objectは Model.ransack(params[:q]) で作成します。このオブジェクトは、Ransack DSLに基づいてパラメータをパースし、ActiveRecord::Relation に変換します:
# コントローラー内
@q = User.ransack(params[:q]) # 検索オブジェクトを作成
@users = @q.result # ActiveRecord::Relationを返す
2. Predicates(検索演算子)
Predicateはカラム名の後ろに付加するサフィックスで、比較の種類を指定します:
-
name_cont→ 名前に含まれる(LIKE '%value%') -
email_eq→ メールが完全一致 -
price_gteq→ 価格が以上
よく使用するpredicateリスト:
| Predicate | データ型 | 意味 | 例 |
|---|---|---|---|
_eq |
String、Number | 完全一致 | status_eq: "active" |
_cont |
String | 含まれている | name_cont: "John" |
_start、_end
|
String | 開始/終了 | email_start: "admin" |
_gt、_lt
|
Number、Date | より大きい/小さい | price_gt: 100 |
_gteq、_lteq
|
Number、Date | 以上/以下 | created_at_gteq: Date.today |
_in |
Array | リストに含まれている | status_in: ["active", "pending"] |
_true、_false
|
Boolean | ブール値 | is_active_true: true |
実践例:Userリストの検索・フィルタリング
シナリオ
以下の機能を持つ管理者ユーザーリストを構築します:
- 名前またはメールアドレスで検索(部分一致)
- 作成日で日付範囲をフィルタリング
- 名前または作成日でソート
モデル
class User < ApplicationRecord
# 検索可能なカラムを制限(セキュリティとパフォーマンス)
def self.ransackable_attributes(auth_object = nil)
["name", "email", "created_at", "status"]
end
end
コントローラー
class UsersController < ApplicationController
def index
@q = User.ransack(params[:q])
@users = @q.result(distinct: true)
.includes(:company) # N+1対策
.page(params[:page])
.per(20)
end
end
説明:
-
ransack(params[:q])→ Ransack DSLに基づいてパラメータをパース -
result(distinct: true)→ ActiveRecord::Relationを返す(JOIN時の重複排除) -
includes(:company)→ eager load(N+1回避)
ビュー
<div class="admin-search">
<%= search_form_for @q, local: true, url: users_path do |f| %>
<div class="form-group">
<%= f.label :name_or_email_cont, "名前またはメールで検索" %>
<%= f.search_field :name_or_email_cont, class: "form-control", placeholder: "キーワード入力" %>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<%= f.label :created_at_gteq, "開始日" %>
<%= f.datetime_select :created_at_gteq, include_blank: true, class: "form-control" %>
</div>
<div class="form-group col-md-6">
<%= f.label :created_at_lteq, "終了日" %>
<%= f.datetime_select :created_at_lteq, include_blank: true, class: "form-control" %>
</div>
</div>
<div class="form-actions">
<%= f.submit "検索", class: "btn btn-primary" %>
<%= link_to "フィルター解除", users_path, class: "btn btn-secondary" %>
</div>
<% end %>
</div>
<table class="table table-hover">
<thead>
<tr>
<th><%= sort_link(@q, :name, "名前") %></th>
<th><%= sort_link(@q, :email, "メール") %></th>
<th><%= sort_link(@q, :created_at, "作成日") %></th>
<th>アクション</th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= link_to user.name, edit_user_path(user) %></td>
<td><%= user.email %></td>
<td><%= user.created_at.strftime("%d/%m/%Y %H:%M") %></td>
<td>
<%= link_to "編集", edit_user_path(user), class: "btn btn-sm btn-info" %>
<%= link_to "削除", user_path(user), method: :delete, class: "btn btn-sm btn-danger" %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= paginate @users %>
ポイント:
-
search_form_for→ Ransack DSLをサポートする専用フォームビルダー -
name_or_email_cont→ 名前とメール両方を同時に検索(OR条件) -
sort_link→ クリック時に昇順・降順を切り替え可能 -
page、per→ kaminari gemのページネーション
アソシエーション経由の検索
Ransackは belongs_to、has_many 経由の検索を association_attribute_predicate 構文でサポートします:
例:UserがCompanyに属する場合
class User < ApplicationRecord
belongs_to :company
def self.ransackable_associations(auth_object = nil)
["company"] # 企業経由のフィルタリングを許可
end
end
# コントローラー
@q = User.ransack(params[:q])
@users = @q.result.includes(:company)
<!-- ビュー -->
<%= f.label :company_name_cont, "企業名" %>
<%= f.search_field :company_name_cont, placeholder: "例:Google、Amazon" %>
Ransackが自動的にusersからcompaniesテーブルへのLEFT OUTER JOINを構築します。
ベストプラクティス
1. 検索可能なカラムの制限
機密データの漏洩とクエリの最適化を防ぐため:
def self.ransackable_attributes(auth_object = nil)
["name", "email", "created_at"]
end
def self.ransackable_associations(auth_object = nil)
["company", "department"]
end
2. JOIN時に distinct: true を使用
アソシエーション経由のフィルタリング時、重複を避けるため:
@users = @q.result(distinct: true).includes(:company)
3. 必ずページネーションを使用
大量のデータロードを防ぐため:
@users = @q.result(distinct: true).page(params[:page]).per(25)
4. アソシエーションのeager load
N+1クエリ問題を防ぐため:
@users = @q.result.includes(:company, :department).page(params[:page])
5. デバッグ時にパラメータをログ出力
Rails.logger.info("Ransack query: #{params[:q].inspect}")
拡張:高度な検索(AND/OR条件)
Ransackはグループ条件でAND/OR論理をサポートしています。例えば、名前に「John」を含む または メールに「john」を含むユーザーを検索:
# params[:q] = { g: { "0" => { m: "or", name_cont: "John", email_cont: "john" } } }
@q = User.ransack(params[:q])
@users = @q.result
SimpleForm連携
SimpleFormを使用している場合、Ransackとシームレスに統合できます:
<%= search_form_for @q, builder: SimpleForm::FormBuilder do |f| %>
<%= f.input :name_cont, label: "名前", input_html: { class: "form-control" } %>
<%= f.submit "検索" %>
<% end %>
まとめ
Ransack は以下を可能にする素晴らしいgemです:
- ✅ 複雑な検索機能をシンプルなコードで実装
- ✅ 複数フィルタ + ソートをミニマルなボイラープレートで
- ✅ アソシエーション経由の検索を自然に
- ✅ 要件変更時の保守性が高い
ただし、アプリが成長し、フルテキスト検索が重要になったり、スケーリング要件が厳しくなったら、Elasticsearchやアルゴリアの導入を検討してもいいでしょう。ただし、Rails環境の大多数のビジネスアプリケーションでは、Ransackは標準的な選択肢です。
参考資料
- GitHub Ransack: https://github.com/activerecord-hackery/ransack
- RubyGems: https://rubygems.org/gems/ransack
- Rails Guides: https://guides.rubyonrails.org/
タグ: Rails Ransack 検索 Ruby gem ActiveRecord