【超かんたん】ransackを使って検索機能を実装しよう！

ransackを利用して検索機能を実装します。
今回も初心者向けにレシピ投稿アプリを例に作成していきます。

また、検索機能の実装にActive Hashの値も利用するのでActive Hashがわからない方は前回記事にしておりますので、そちらをご覧頂けたらと思います。

https://qiita.com/mitaninjin/items/6a61a4494306988fdc14

#####完成イメージ
![19c0a417740b22b00dc7a7fcfe4ad268.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/4cebda6f-c93a-82d6-cd0e-65c9c37793f4.gif)


それでは、実装していきましょう！

##ransackとは
ransackはRails用の検索機能を実装するためのGem。[公式ドキュメント](https://github.com/activerecord-hackery/ransack)

https://github.com/activerecord-hackery/ransack

####導入方法
Gemfileに下記を記述しbundle installします。

```:Gemfile
gem 'ransack'
```

```:ターミナル
bundle install
```

##デモデータを投入しよう！
seed.rbにデモデータ生成する記述をしていきます。
category_idとtime_required_idはActive Hashの値と紐付いているカラムになります。

また、time_required_idのFakerがわからない方は前回記事にしておりますので、そちらをご覧頂けたらと思います。
[【超かんたん】Fakerを使ってダミーデータを作成しよう！](https://qiita.com/mitaninjin/items/ab07bc239d604627d3d6)



```ruby:db/seed.rb
5.times do |n|
  Recipe.create!(
    title: "すし・魚料理#{n}",
    text: "作り方",
    category_id: 1,
    time_required_id: Faker::Number.within(range: 2..6)
  )
end

#中略

5.times do |n|
  Recipe.create!(
    title: "お菓子・スイーツ#{n}",
    text: "作り方",
    category_id: 10,
    time_required_id: Faker::Number.within(range: 2..6)
  )
end
```

```:ターミナル
rails db:seed
```

デモデータの作成完了。
![ｒ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/90c7e169-0ad8-b903-d1ed-9cce90182ce3.jpeg)

簡易的にindexページに表示します。
![in.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/867504c4-4161-7926-f997-42a94c42b569.png)

##検索機能の実装
####ルーティングの設定
まずは検索ページ(search)のルーティングと検索結果を表示するページ(result)のルーティングの設定をしましょう。

7つのアクション以外のルーティングを設定するので、recipesにネストさせます。
今回は、URLにidがつかないのでcollectionを利用します。

```ruby:config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
  devise_for :users
  resources :users
  resources :recipes do
    collection do
      get :search
      get :result
    end
  end
end
```

####コントローラーの編集
以下の記述は[公式ドキュメント](https://github.com/activerecord-hackery/ransack)を参考にしております。

```ruby:app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
  before_action :search_recipes, only: [:search, :result]

  def index
    @recipes = Recipe.all
  end

  #中略

  def search
  end

  def result
    @results = @q.result
  end

  private
  def search_recipes
    @q = Recipe.ransack(params[:q])
  end
end
```
params[:q]のキー「:q」でrecipesテーブルからレシピ情報を探し「@q」に格納します。
この@qに対して「.result」とすることで検索結果を取得します。
<br>

次にビューファイルを作成していきましょう。

##ビューの作成
####ビューファイルの作成
```:ターミナル
touch app/views/recipes/{search.html.erb,result.html.erb}
```

####ビューファイルの編集
今回はCSSの説明は省きます。

検索フォームには**search_form_for**というメソッドを使います。
form_withのransack版というイメージです。
form_withでは「text_field」ですが、search_form_forだと「search_field」になります。

また、「**:カラム名_マッチャ**」とすることで条件にあった検索を行います。
「**_cont**」だと「**入力された値が含まれている**」という意味になり、「**_eq**」は「**入力された値と等しい**」という意味のマッチャになります。
その他のマッチャについては[公式ドキュメント](https://github.com/activerecord-hackery/ransack)を参照してください。

collection_selectメソッドについては[前回の記事](https://qiita.com/mitaninjin/items/6a61a4494306988fdc14)と[Railsドキュメント](https://railsdoc.com/page/collection_select)を参照してください。

以下のように編集していきます。

```erb:app/views/recipes/search.html.erb
<div class="recipe-form">
  <h1 class="text-center">検索する</h1>
  <%= search_form_for @q, url: result_recipes_path do |f| %>
    <div class="form-group">
      <label class="text-secondary">料理名</label><br />
      <%= f.search_field :title_cont, class: "form-control"%>
    </div>
    <div class="form-group">
       <label class="text-secondary">カテゴリー</label><br />
       <%= f.collection_select(:category_id_eq, Category.where.not(id: 0), :id, :name, include_blank: '指定なし') %>
    </div>
    <div class="form-group">
       <label class="text-secondary">所要時間</label><br />
       <%= f.collection_select(:time_required_id_eq, TimeRequired.where.not(id: 0), :id, :name, include_blank: '指定なし') %>
    </div>
    <div class="form-group">
       <label class="text-secondary">フリーワード</label><br />
       <%= f.search_field :text_cont, class: "form-control"%>
    </div>
    <div class="actions">
      <%= f.submit '検索', class: "btn btn-primary" %>
    </div>
  <% end %>
</div>
```

それでは表示してみましょう。
![せ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/bcabc128-b768-7b75-3709-c3291a6f992c.png)

次に検索結果ページを作成していきましょう。
以下のように編集していきます。

```erb:app/views/recipes/result.html.erb
<div class="recipes-index text-center">
  <h1 class="result-index">検索結果</h1>
  <% if @results.length != 0 %>
    <% @results.each do |recipe| %>
      <div class="recipe">
        <div class="recipe-title">
          <%= recipe.title %>
        </div>
        <div class="recipe-content">
          カテゴリー: <span class="recipe-category"><%= recipe.category.name %></span>
          所要時間: <span class="recipe-time"><%= recipe.time_required.name %></span>
        </div>
      </div>
    <% end %>
  <% else %>
    該当するレシピはありません
  <% end %>
</div>
```
if文で該当する検索結果がない場合は「該当するレシピはありません」と表示させるようにしています。
それでは実際に検索してみましょう。

####該当する検索結果がある場合
![19c0a417740b22b00dc7a7fcfe4ad268.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/4cebda6f-c93a-82d6-cd0e-65c9c37793f4.gif)

####該当する検索結果がない場合
![c8e6ad3cfc012c474e3c28ddb7be9cfe.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/849ea78f-f509-c34f-40be-9f66f3af213c.gif)

以上で完成です。