Rails+ransackで検索機能作る

Railsで簡単に検索機能つけるのに、ransackというgemがあるようなので使ってみた。
Railsは4.2.0、ransackは1.6.3を使いました。

まず、Gemfileに追加。

gem "ransack", '~> 1.6.3'

bundleでインストールする。

$ bundle install --path vendor/bundle

直接的にransack関係ないけど、検索フォームを作らないと検索できないので、検索フォームのビューを作る。
基本的にform_tagとかで作ればよいっぽい。form_tagとbootstrapのスタイルとかを組み合わせるのはclassとかを渡してやればよい。
text_field_tagでkeywordパラメータセットして、button_tagでsubmitする。ボタンのラベルにただの文字列じゃなくてアイコンフォント埋め込むにはブロックで渡してやるとHTML埋め込めるようです。

<%= form_tag({controller: :commits, action: :search}, class: "form-inline") do %>
  <%= text_field_tag :keyword, '', size: '100%', class: "form-control", placeholder: "keyword" %>
  <%= button_tag(type: "submit", class: "btn btn-default") do %>
  Search <i class="fa fa-search"></i>
  <% end %>
<% end %>

フォーム送信先のルーティングのためにconfig/routes.rbに以下を追加しておく。各自必要なルーティングに読み替えてください。

  match 'commits/search', to: 'commits#search', via: ['post', 'get']

あと検索結果出す画面も作っておく。
とりあえずtableにしてコレクションを一覧表示するようにしておいて、will_paginateでページングできるようにしておく。

<% if @commits.any? %>
  <table class="table table-hover">
    <thead>
      <tr>
        <th class="col-xs-9">Message</th>
        <th class="col-xs-2">Repository</th>
        <th class="col-xs-1">SHA</th>
      </tr>
    </thead>
    <tbody>
      <% @commits.each do |commit| %>
      <tr>
        <td><%= commit.message %></td>
        <td><%= link_to commit.repo_full_name, "https://github.com/#{commit.repo_full_name}", { :target => "_blank" } %></td>
        <td><%= link_to commit.sha[0,7], "https://github.com/twbs/bootstrap/commit/#{commit.sha}", { :target => "_blank" } %></td>
      </tr>
      <% end %>
    </tbody>
  </table>
  <%= will_paginate %>
<% end %>

最後に本題のコントローラ。ransackの検索の基本はこんなイメージになる。

モデル名.search(:プロパティ名_検索方法 => 検索キー).result

例えば、Commitモデルのmessageプロパティを部分一致で検索する場合はこんなかんじ。

class CommitsController < ApplicationController
  (略)
  def search
    keyword = params[:keyword]
    @commits = Commit.search(:message_cont => keyword).result
  end
end

部分一致以外の検索方法とかは以下が参考になります。
http://morizyun.github.io/blog/ransack-gem-rails-search/

あと、will_pagenateと組み合わせてページングさせるにはそのままpaginateに渡したらうまくいった。

    @commits = Commit.search(:message_cont => keyword).result.paginate(page: params[:page])

とりあえず簡単な検索のUIできた。

が、作りながらなんとなく気づいてたけど、主キー項目の一致検索とかならまだしも、今回やりたいことの用途だと、インデックス貼ってない文字列のLIKE検索なので性能とか出ない。少量データなら問題ないけど、データ量多いと全然まともな性能出ないので、ちゃんと全文検索用のデータ構造使るとかDB周りもうちょい工夫しないといけなさそう。まぁとりあえず簡単なUIできてしまえばあとは裏の実装を改良してけばよいかなぁというかんじです。

参考