3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rails × Ransack: SQLを書かずに高度な検索機能を構築する完全ガイド【基礎から実践まで】

Last updated at Posted at 2025-12-04

Ransackが解決する問題

Rails開発において、ほとんどのデータリスト(users、products、orders…)には、複数の条件に基づく検索・フィルター機能が必要です。もし手でSQLスコープやArel queryを書く場合、以下の問題に直面します:

  • 検索条件の組み合わせごとにWHERE句を書き直す必要がある
  • エッジケースやバリデーション処理の実装
  • 複数のパターンに対するテスト時間の増加
  • 要件変更時の保守が困難

Ransack は、パラメータDSLpredicates_eq_cont_gteqなど)を通じて、ActiveRecord queryやSQLを直接書かずに、検索条件を宣言的に定義できるgemです。

Ransackを選ぶべき理由

Ransackが活躍するユースケース

Ransackは以下の場面で最適です:

  1. Admin Dashboard & CRUD インターフェース
    複数フィルタ条件を持つusers、orders、productsのリスト。Ransackなら複雑なフィルター機能でもコードをシンプルに保てます。

  2. 複雑なレポート機能が必要なビジネスアプリケーション
    日付範囲、数値範囲、文字列マッチング、ブール値フラグを同時にフィルタリングする必要があるケース。例:「X年X月からY年Y月の間、ステータスが「pending」、かつcustomer_idが特定の値」という複合条件。

  3. 迅速なMVP・プロトタイプ開発
    Ransackを使えば、検索機能を数分で実装でき、最初からクエリ最適化を設計する手間がかかりません。

  4. 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 → クリック時に昇順・降順を切り替え可能
  • pageper → kaminari gemのページネーション

アソシエーション経由の検索

Ransackは belongs_tohas_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は標準的な選択肢です。

参考資料


タグ: Rails Ransack 検索 Ruby gem ActiveRecord

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?