Fun Done Run

yazawa's tech blog

Rails で複雑なレコード検索処理を簡単に組み込める『Ransack』の紹介

f:id:yazawa_tech:20190728134048p:plain

はじめに

Rails アプリケーションでよく使用されるgem の使い方や導入方法をまとめたく、手始めにRansack について、簡単な入門記事をまとめてみる。今回記述したソースコードは以下のリポジトリにプッシュしてある。

github.com

Ransack とは

Rails アプリケーション用に単純な検索フォームと高度な検索フォームの両方を作成できるgem である。

github.com

使用方法としてはSimple ModeAdvanced Mode があるようだが、今回はSimple Mode の使用方法を簡単にまとめる。

入門

gem のインストール

Gemfile に以下の記述を追加して、bundle コマンドでインストールする。

gem 'ransack'
$ bundle

Simple Mode で使う

フォームの作成

もともとRansack がMetaSearch というgem の書き換えとして作られており、シンプルモードはMetaSearch に精通している人であればすぐに導入できるようだ。 現在サンプル用に作成しているタスク管理アプリに実装してみる。

app/views/tasks/index.html.slim

= search_form_for @q, class: 'mb-5' do |f|
  .form-group.row
    = f.label :name_cont, '名称', class: 'col-sm-2 col-form-label'
    .col-sm-10
      = f.search_field :name_cont, class: 'form-control'
  .form-group
    = f.submit class: 'btn btn-outline-primary'

まずview にsearch_form_for というヘルパーメソッドを使ってRansack 用の検索フォームを作る。q というオブジェクトに name_cont というパラメータを設定してサーバーサイドに送信するように設定。_cont というサフィックスは、contains の略で、検索するときに対象の文字列を含むものを検索してくれる。

パラメータの処理

app/controllers/tasks_controller.rb

def index
  @q = current_user.tasks.ransack(params[:q])
  @tasks = @q.result(distinct: true).recent
end

コントローラ側ではq パラメータを受け取り、Task モデルに組み込まれたransack メソッドを使用してRansack::Search クラスのオブジェクトを生成している。以下は ransack メソッドの中身。

module Ransack
  module Adapters
    module ActiveRecord
      module Base
      # 一部省略
      def ransack(params = {}, options = {})
        ActiveSupport::Deprecation.warn("#search is deprecated and will be removed in 2.3, please use #ransack instead") if __callee__ == :search
        Search.new(self, params, options)
      end

生成したRansack::Search のメソッドである result メソッドを呼び出して@tasks を取得している。オプションとしてdistinct: true を渡すと、重複したデータを除外してセレクトしてくれる。重複しているかのデータの判断は、抽出対象の全てのカラムが一致しているかなので、カラムが複数対象であれば複数のカラムの値が一致していた場合、カラムが単一カラムであれば、そのカラムの値が一致していた場合に除外対象となる。

recent は今回のサンプルアプリに定義してあるscope で、task.rb に以下のように記述してある。

  scope :recent, -> {order(created_at: :desc)}

これによってカスタムクエリ用のメソッドとして、SQLの実行時に ORDER BY "tasks"."created_at" DESC が付与されるようになる。

デモ

これだけで基本的な検索フォームが作成できるので、試しに動かしてみる。

f:id:yazawa_tech:20190728174029p:plain
タスクの一覧画面

f:id:yazawa_tech:20190728174119p:plain
検索するキーとして「設計」と入力して検索ボタンをクリックしたあと

無事にタスクの名称に検索キーワードを含むものだけを表示することができた。検索ボタンをクリックした時のアプリケーションログは以下のように出力されている。

Started GET "/tasks?utf8=%E2%9C%93&q%5Bname_cont%5D=%E8%A8%AD%E8%A8%88&commit=%E6%A4%9C%E7%B4%A2" for ::1 at 2019-07-28 15:09:45 +0900
Processing by TasksController#index as HTML
  Parameters: {"utf8"=>"", "q"=>{"name_cont"=>"設計"}, "commit"=>"検索"}
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 4], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:8
  Rendering tasks/index.html.slim within layouts/application
  Task Load (0.3ms)  SELECT DISTINCT "tasks".* FROM "tasks" WHERE "tasks"."user_id" = $1 AND "tasks"."name" ILIKE '%設計%' ORDER BY "tasks"."created_at" DESC  [["user_id", 4]]
  ↳ app/views/tasks/index.html.slim:22
  Rendered tasks/index.html.slim within layouts/application (7.7ms)
Completed 200 OK in 45ms (Views: 39.3ms | ActiveRecord: 1.9ms)

これで簡単に検索フォームを実装することができた。

ソート機能の付与

Ransack ではsort_link というヘルパーを使用することで、テーブルヘッダーをソート可能なものにすることができる。

app/views/tasks/index.html.slim

:
.mb-3
table.table.table-hover
  thead.thead-default
    tr
      th= sort_link(@q, :name)
      th= sort_link(@q, :created_at)
      th
      th
:

上記のコードではname(名称) とcreated_at(作成日時)をソート可能にしている。

デモ

f:id:yazawa_tech:20190728182540p:plain
「新規」というキーワードで検索した結果

f:id:yazawa_tech:20190728182659p:plain
検索結果の表示部分の「名称」ヘッダーをクリック。ヘッダー文字の横に▲が付いている。

f:id:yazawa_tech:20190728182857p:plain
もう一度クリックすると、▼に変わり、降順になる。

f:id:yazawa_tech:20190728182955p:plain
「登録日時」ヘッダーも同様にクリックすると昇順になる。

f:id:yazawa_tech:20190728183033p:plain
もう一度クリックすると降順になる。

まとめ

  • 今回はRails アプリの開発でよく使われるgem の入門手順を書いた
  • よく使われているgem はGitHub のREADME.md やWiki が充実しているので、初めて使う場合でもわかりやすい
  • Advanced Mode やもっと複雑な検索もできるようなので、次はそれを試したい