Ruby
Rails
Gem
ransack

検索用のgem「ransack」を使ってみる

More than 5 years have passed since last update.

ransackはrails用の検索機能を実装するためのgemです。

比較的シンプルなコードで複雑な検索を実装することができます。

ransackの概要と使い方についてはRansackのススメを参照してください。

ここでは実際に使用するまでのサンプルを作ってみます。


プロジェクトを作成

rails new ransack_study -T --skip-bundle

cd ransack_study

localeとtimezoneを設定


config/application.rb

require File.expand_path('../boot', __FILE__)

〜〜 ( 中略 ) 〜〜
module RansackStudy
class Application < Rails::Application
〜〜 ( 中略 ) 〜〜
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local

# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
config.i18n.default_locale = :ja

# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
〜〜 ( 中略 ) 〜〜
end
end



ransackのインストール

Gemfileに追記

vi Gemfile

source 'https://rubygems.org'

gem 'rails', '3.2.11'

# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'

gem 'sqlite3'

gem 'rails-i18n' # この行を追加(ransackには関係ないけどdate_select用)
gem 'ransack' # この行を追加

〜〜( 以下 略 )〜〜

bundle installを実行

bundle install


今回のサンプル用にscaffold

ユーザーモデルと、ユーザーが紐づく掲示板モデルを作成します。

rails g scaffold user name:string

rails g scaffold topic title:string content:text user:references

掲示板モデルでユーザーを登録するためにちょろっと修正


app/models/topic.rb

class Topic < ActiveRecord::Base

belongs_to :user
attr_accessible :content, :title, :user_id #この行を修正
end

ユーザーをselectタグで選択できるように


app/views/_form.html.erb

<%= form_for(@topic) do |f| %>

〜〜 ( 中略 ) 〜〜
<div class="field">
<%= f.label :user %><br />
<%= f.select :user_id, User.all.map { |u| [u.name, u.id] } %><!--この行を修正-->
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>


一覧からcontentを削除し、created_atを追加、ユーザー名を表示するように変更


app/views/index.html.erb

<h1>Listing topics</h1>

<table>
<tr>
<th>Title</th>
<th>User</th>
<th>Created At</th><!--この行を修正-->
<th></th>
<th></th>
<th></th>
</tr>

<% @topics.each do |topic| %>
<tr>
<td><%= topic.title %></td>
<td><%= topic.user.name %></td><!--この行を修正-->
<td><%= topic.created_at.strftime('%Y/%m/%d %H:%M') %></td><!--この行を修正-->
<td><%= link_to 'Show', topic %></td>
<td><%= link_to 'Edit', edit_topic_path(topic) %></td>
<td><%= link_to 'Destroy', topic, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>

<br />

<%= link_to 'New Topic', new_topic_path %>


詳細でユーザー名を表示するように変更


app/views/topic/show.html.erb

  〜〜 ( 中略 ) 〜〜

<p>
<b>User:</b>
<%= @topic.user.name %><!--この行を修正-->
</p>
〜〜 ( 中略 ) 〜〜

とりあえず実行してみる

rake db:migrate

rails s

http://localhost:3000/usersでユーザーを適当に登録

http://localhost:3000/topicsで掲示板を何件か登録

今の画面はこんな感じ。

ユーザー一覧

掲示板一覧

ここまでで準備は完了です。

ここからは実際にransackの機能を使って検索フォームを作成してきます。


ransackの基本的な使い方

ransackは基本的にはsearchメソッドで条件を指定し、resultで結果を返します。

User.search(name_cont: 'ほげ').result

上の処理を実行すると次のようなSQL文を発行し、ActiveRecord::Relationオブジェクトを返します。

SELECT "users".* FROM "users" WHERE ("users"."name" LIKE '%ほげ%')

また、ransackには検索フォーム用のフォームビルダーも用意されています、

今回はそれを利用して掲示板の検索フォームを作成していきます。


search_form_forの利用

TopicsControllerでsearchオブジェクトを生成


app/controllers/topics_controller.rb

class TopicsController < ApplicationController

# GET /topics
# GET /topics.json
def index
@search = Topic.search(params[:q]) # この行を追加
@topics = @search.result #この行を修正

respond_to do |format|
format.html # index.html.erb
format.json { render json: @topics }
end
end
〜〜 ( 中略 ) 〜〜
end


search_form_forを利用して検索フォームを作成

まずはtopicのtitleを前後あいまい検索する場合index.html.erbを以下のように修正


app/views/topics/index.html.erb

<h1>Listing topics</h1>

<%= search_form_for @search do |f| %>
<%= f.label :title_cont, 'タイトル' %>
<%= f.text_field :title_cont %>
<div>
<%= f.submit '検索する' %>
</div>
<% end %>
〜〜 ( 以下 略 ) 〜〜


以上

これだけtitleの曖昧検索が実装されます。

動かしてみるとこんな感じになってると思います。

掲示板一覧検索語

ここからはindex.htmlを追加するだけで検索処理を実装することが可能です。

試しに作成日の範囲指定を追加してみます。

index.html.erbを以下のように修正します。


app/views/topics/index.html.erb

<h1>Listing topics</h1>

<%= search_form_for @search do |f| %>
<%= f.label :title_cont, 'タイトル' %>
<%= f.text_field :title_cont %>
<!--ここから追加-->
<br />
<%= f.label :created_at, '作成日' %>
<%= f.date_select :created_at_gteq, include_blank: true %>〜
<%= f.date_select :created_at_lteq, include_blank: true %>
<!--ここまで追加-->
<div>
<%= f.submit '検索する' %>
</div>
<% end %>
〜〜 ( 以下 略 ) 〜〜

動かしてみると以下のようになり、作成日での絞込みが実装されました。

掲示板一覧日付範囲指定

日付の指定をした場合に発行されるSQLを確認してみます。

SELECT "topics".* FROM "topics" WHERE (("topics"."title" LIKE '%について%' AND "topics"."created_at" >= '2013-05-01 00:00:00.000000' AND "topics"."created_at" <= '2013-05-09 00:00:00.000000'))

このままではtoのほうの日付の条件が指定した日付の 00:00:00までしか対象になりません。

日付で絞り込むなら 23:59:59までを検索対象にしたいですよね。

なので、以下のようなファイル(config/initializers/ransack.rb)を作成します。


config/initializers/ransack.rb

Ransack.configure do |config|

config.add_predicate 'lteq_end_of_day',
:arel_predicate => 'lteq',
:formatter => proc { |v| v.end_of_day },
:compounds => false
end

formatterを指定し、index.html.erbも以下のように修正します。


app/views/topics/index.html.erb

<h1>Listing topics</h1>

<%= search_form_for @search do |f| %>
<%= f.label :title_cont, 'タイトル' %>
<%= f.text_field :title_cont %>
<br />
<%= f.label :created_at, '作成日' %>
<%= f.date_select :created_at_gteq, include_blank: true %>〜
<%= f.date_select :created_at_lteq_end_of_day, include_blank: true %><!--この行を修正-->
<div>
<%= f.submit '検索する' %>
</div>
<% end %>
〜〜 ( 以下 略 ) 〜〜

ここでサーバーを一度再起動し、再度検索をし発行されるSQLを確認します。

SELECT "topics".* FROM "topics" WHERE (("topics"."title" LIKE '%について%' AND "topics"."created_at" >= '2013-05-01 00:00:00.000000' AND "topics"."created_at" <= '2013-05-08 23:59:59.999999'))

いい感じですね♪

最後に、関連するテーブルの項目での絞込みも試してみます。

topicsにひもづくユーザの名前で前後あいまい検索を行います。

index.html.erbを以下のように修正します。


app/views/topics/index.html.erb

<h1>Listing topics</h1>

```app/views/topics/index.html.erb
<h1>Listing topics</h1>
<%= search_form_for @search do |f| %>
<%= f.label :title_cont, 'タイトル' %>
<%= f.text_field :title_cont %>
<br />
<%= f.label :created_at, '作成日' %>
<%= f.date_select :created_at_gteq, include_blank: true %>〜
<%= f.date_select :created_at_lteq_end_of_day, include_blank: true %>
<!--ここから追加-->
<br />
<%= f.label :user, 'ユーザー' %>
<%= f.text_field :user_name_cont %>
<!--ここまで追加-->
<div>
<%= f.submit '検索する' %>
</div>
<% end %>
〜〜 ( 以下 略 ) 〜〜


実行すると以下のようになり、ユーザー名での絞込みが実装できましたね。

掲示板一覧ユーザー名指定


ソートの実装

ransackにはソート用のhelperも用意されています。

それらを利用して掲示板一覧にソートを実装してみます。これもviewの修正だけで実装することが可能です。

index.html.erbのtableのヘッダーを以下のように修正します。


app/views/topics/index.html.erb

<h1>Listing topics</h1>

〜〜 ( 中略 ) 〜〜
<table>
<tr>
<th><%= sort_link(@search, :title, 'タイトル') %></th><!--この行を修正-->
<th><%= sort_link(@search, :user_name, 'ユーザー') %></th><!--この行を修正-->
<th><%= sort_link(@search, :created_at, '作成日時') %></th><!--この行を修正-->
<th></th>
<th></th>
<th></th>
</tr>
〜〜 ( 以下 略 ) 〜〜

これで実行すると、ヘッダーがリンクになりクリックするとソートが実装されていることがわかります。

掲示板一覧ソート


まとめ

ransackとransackのフォームビルダーを利用することで、比較的複雑な検索条件もほぼviewを修正するだけで実装することができました。動的に検索条件を作成することもview側を動的に作成するだけで可能ですので比較的簡単に実装できるかと思います。それらのチュートリアルがRailsCasts(#370 Ransack)にありますので、そちらを参照してみてください。


今回のソース

今回のソースは以下で公開しています。

LuckOfWise/ransack_study https://github.com/LuckOfWise/ransack_study