読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Railsでransackを使って検索機能を作成する

Rails gem 検索

ransackとは

ransackはモデルの検索インターフェースを簡単に作れるgemです。それにより検索機能を簡単に実装できます。

商品の検索機能をステップステップで作成することにより、ransackの使い方を説明していきます。
次のように検索フォームを作成し、検索をすることができます。

f:id:nipe880324:20141008215332p:plain:w480

動作確認

  • Mac OSX 10.9
  • Rails 4.1
  • Ransack 1.4.1

目次

  1. Railsプロジェクトの作成
  2. Ransackのインストール
  3. Ransackで検索機能と検索フォームを作成
  4. Ransackでテーブルのソートリンク
  5. Ransackで検索条件を絞る


1. Railsプロジェクトの作成

まずは、Railsプロジェクトを作成します。

rails new ransack_test
cd ransack_test

Scaffoldで商品を作成します。

rails g scaffold Product name:string description:string price:integer discontinued:boolean carrier_id:integer

商品の取り扱い会社を保持するCarrierモデルを作成します。

rails g model Carrier name:string

モデル間の関連を追加します。
まずは、Carrierモデルです。

# app/models/carrier.rb

class Carrier < ActiveRecord::Base
  has_many :products
end

次は、Productモデルです。

# app/models/carrier.rb

class Product < ActiveRecord::Base
  belongs_to :carrier
end

マイグレートを実行します。

rake db:migrate

初期データを作成します。

# db/seeds.rb

# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
#   cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
#   Mayor.create(name: 'Emanuel', city: cities.first)

carrier1 = Carrier.create!(name: "Mocodo")
carrier2 = Carrier.create!(name: "BankSoft")

carrier1.products..create!(
  name: "iPhone 6",
  description: "今回のモデルチェンジではいくつか見どころがあるが、その中でも特に大きなポイントとなるのが画面サイズのアップだ。「iPhone 6」は4.7インチ、「iPhone 6 Plus」は5.5インチの「Retina HDディスプレイ」を採用している。従来モデル「iPhone 5s」の4インチと比べて、「iPhone 6」はひと回り、「iPhone 6 Plus」ではそれ以上に大きなディスプレイとなっている。",
  price: 96480,
  discontinued: false)

carrier1.products.create!(
  name: "iPhone 3G",
  description: "iPhone 3Gとはアップル インコーポレイテッドが設計・販売しているスマートフォンである。iPhoneの第2世代機で、2008年6月9日にサンフランシスコのモスコーニ・センター(英語版)で開催されたWWDC 2008で発表された。",
  price: 12800,
  discontinued: true)

carrier2.products..create!(
  name: "AQUOS CRYSTAL SoftBank",
  description: "画面のフレームを極限までなくした「フレームレス構造」によって、画面だけを持っているような感覚を実現したスマートフォン。ディスプレイは大画面の5.0型「S-CG Silicon」(1280×720ドット)液晶を搭載しながら、女性でも片手で操作できるコンパクトなサイズに仕上げている。",
  price: 8800,
  discontinued: false)

データを投入します。

rake db:seed

そして、Carrierを表示するようにします。

# app/views/products/index.html.erb

<%# 変更前 %>
<td><%= product.carrier_id %></td>

<%# 変更後 %>
<td><%= product.carriers.name %></td>

では、rails sで画面を表示してみましょう。
f:id:nipe880324:20141008215539p:plain:w480


2. Ransackのインストール

では、準備ができたのでRansackをインストールしていきましょう。

まずは、Gemfileransackを追加します。

# Gemfile
...
gem "ransack"

ransackをインストールします。

bundle install


3. Ransackで検索機能と検索フォームを作成

Ransackで検索機能と検索フォームを追加します。

まず、コントローラーに検索をするコードを追加します。

  ...
  # GET /products
  # GET /products.json
  def index
    #@products = Product.all
    @q        = Product.search(params[:q])
    @products = @q.result(distinct: true)
  end
  ...

次に、商品一覧のビューに検索フォームを追加します。
コメントで記載していますが、
Ransackでは、

  • search_form_forを使う
  • カラム名_startカラム名_contなどで検索条件を指定する

ことにより、検索フォームを作成します。
以下に上げているもので検索パターンをほぼ網羅しています。

<h1>Listing products</h1>

<%# form_for の代わりに search_form_for を使うことで検索フォームを作成 %>
<%= search_form_for @q do |f| %>

  <%# start 特定の文字列で始まる商品名で検索 %>
  <%= f.label :name_start, "商品名" %>
  <%= f.search_field :name_start %>
  <br />

  <%# cont 文字列が含まれる商品説明で検索(not_cont で含まれない商品説明) %>
  <%= f.label :description_cont, "商品説明" %>
  <%= f.search_field :description_cont %>
  <br />

  <%# 関連の名前で検索 Product.carrier.name %>
  <%= f.label :carrier_name_cont, "キャリア" %>
  <%= f.search_field :carrier_name_cont %>
  <br />

  <%# lt で特定の数値より下で検索(gt はより上、eq は同じ、gteqやlteqなどもできる) %>
  <%= f.label :price_lt, "値段(以下)" %>
  <%= f.search_field :price_lt %>
  <br />

  <%# or で複数の値をorで検索(and 複数の値をand条件で検索) %>
  <%= f.label :name_or_description_cont, "名前 or 説明" %>
  <%= f.search_field :name_or_description_cont %>
  <br />

  <%# 検索ボタン %>
  <%= f.submit %>
<% end %>

<hr />

<table>
  ...

では、rails sでサーバーを起動し検索してみましょう。
まず、「商品名」を "iPhone" で検索します。
デフォルトでは大文字小文字を区別しません。「商品名」が "iPhone" で始まる文字列で検索されます。
f:id:nipe880324:20141008220206p:plain:w480

次に、「商品説明」に "ディスプレイ" を入力し、検索します。
すると、複合条件で検索された結果が表示されます。
f:id:nipe880324:20141008220222p:plain:w480


4. Ransackでテーブルのソートリンク

Ransackが提供しているsort_linkヘルパーをテーブルのヘッダーに設定することによりテーブルの列をソートすることができるようになります。
default_orderオプションにより、初期のソート順を指定できます。

では、テーブルのヘッダーにテーブルのソートリンクを追加しましょう。

# app/views/products/index.html.erb
  ...
  <table>
	  <thead>
	    <tr>
	      <th><%= sort_link(@q, :name) %></th>
	      <th>Description</th>
	      <th><%= sort_link(@q, :price, default_order: :desc) %></th>
	      <th>Discontinued</th>
	      <th>Carrier</th>
	      <th colspan="3"></th>
	    </tr>
	  </thead>

	  <tbody>
	    <% @products.each do |product| %>
	      <tr>
	        <td><%= product.name %></td>
	        <td><%= product.description %></td>
	        <td><%= product.price %></td>
	        <td><%= product.discontinued %></td>
	        <td><%= product.carrier.name %></td>
	        <td><%= link_to 'Show', product %></td>
	        <td><%= link_to 'Edit', edit_product_path(product) %></td>
	        <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
	      </tr>
	    <% end %>
	  </tbody>
	</table>
  ...

画面を更新しましょう。「Name」や「Price」がクリック可能になり、昇順や降順ができるようになりました。
f:id:nipe880324:20141008220408p:plain:w480


5. Ransackで検索条件を絞る

ユーザーが独自に検索パラメーターを指定した場合には、その他の項目で検索ができてしまいます。
そのため、セキュリティを意識する必要がある場合は、
「Name」、「Description」、「Carrier.Name」、「Price」以外
の検索条件を利用できないようにします。

  • カラム名を制限するには、ransackable_attributesをオーバーライドし、許可するカラム名を配列で指定します。
  • 関連を制限するには、ransackable_associationsをオーバーライドし、許可する関連名を配列で指定します。
class Product < ActiveRecord::Base
  belongs_to :carrier

  # デフォルトでは全てのカラム名を返す
  # 許可するカラムの名前をオーバーライドする
  def self.ransackable_attributes auth_object = nil
    %w(name description price)
  end

  # デフォルトは全てのアソシエーション名を返す
  # 許可する関連の配列をオーバーライドする
  def self.ransackable_associations auth_object = nil
    %w(carrier)
  end
end