Active hashで作ったカテゴリーを用いてransackでカテゴリー検索

**今回はアクティブハッシュを用いたカテゴリ検索をしたいと思います**

自分は今コーヒーの感想を共有できるアプリを作ってます。
インスタのコーヒー版的な感じです。

# 期待する動作！

![スクリーンショット 2020-12-18 15.49.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653881/76a1eed2-7eec-e5df-8591-b4914880f06e.png)

このように,「地域　ラテンアメリカ、コク　ほどよい」みたいにカテゴリーを選択して投稿

![スクリーンショット 2020-12-18 15.54.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653881/589f588d-44b6-fa79-39d8-2ca723449a7e.png)

選択した、カテゴリがしっかり表示されてます。

![スクリーンショット 2020-12-18 17.19.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653881/7562e2bf-c04f-c217-fc2f-7f4b2a00eed0.png)

名前は、グアテマラ、地域はラテンアメリカにして、検索してみて

![スクリーンショット 2020-12-18 17.26.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653881/283e5f7a-de23-cf0f-f3f9-c31da1d3e435.png)

しっかり検索結果が表示されました！！

# 具体的な実装方法

今回はカテゴリ選択の**active hash**と言うgemと検索機能を簡単に実装できる**ransack**と言うgemを導入します。

active hashとは、都道府県名一覧やカテゴリーなど「基本的に変更されないデータ」があったとします。基本的に変更されないデータであるため、データベースに保存する必要性はありません。一方、ビューファイルなどにそれらのデータを直接書いてしまうと、可読性に欠けます。
そのようなケースでは、ActiveHashが有用です。
都道府県名などの変更がないデータをモデルに記述し、あたかもデータベースに保存されていたデータとして取り扱うことができるようにするGemです。すなわち、都道府県名などのデータに対して、ActiveRecordのメソッドを用いることができます。
テーブルの数を無駄に増やす必要もなくなります。


ransackとはシンプルな検索フォームと高度な検索フォームの作成を可能にするgemです。

まずはこれらのgemを導入しましょう。

色々なカテゴリーを設けてますが、今回は酸味を表す、acidityカテゴリーだけに着目して解説していきます。

##### active hashでのカテゴリーの実装(わかってる方は飛ばしてください)

**acidity.rb**

```rb

class Acidity < ActiveHash::Base
  self.data = [

    { id: 2, name: 'LOW(少ない)' },
    { id: 3, name: 'MEDIUM(ほどよい)' },
    { id: 4, name: 'HIGH(強い)' }
  ]
end
```

これが、active hashで作った カテゴリーです。

![スクリーンショット 2020-12-18 17.43.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653881/cd78571d-0c51-090a-7583-82b002712125.png)

コーヒーの投稿を保存する、drinksテーブルにacidity_idを保存してます

**drinks.rb**

```rb

class Drink < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :user
  has_one :trade
  has_many :drink_tag_relations
  has_many :tags,through: :drink_tag_relations
  has_one_attached :image
  belongs_to_active_hash :region
  belongs_to_active_hash :body
  belongs_to_active_hash :acidity
  belongs_to_active_hash :processing
  with_options presence: true do
    validates :name
    validates :explain
  end
end
```

  belongs_to_active_hash :acidity

と記述することで、acidityとアソシエーションが組まれて、カテゴリ選択ができるようになります
```ruby

  extend ActiveHash::Associations::ActiveRecordExtensions
```
　と記述して、moduleを取り込むことによって、  belongs_to_active_hashメソッド
が使えます


**drinks/new.html.erb**

```erb

 <%= f.collection_select(:acidity_id,Acidity.all,:id,:name,{},{class: "こんな感じでクラスを設定できます"})%>
```

こんな感じでカテゴリが実装できます

第一引数に、保存先のカラム名,今回はacidity_id

第二引数に、表示したい配列データを指定する、Acidity.all

第三引数に、表示する際に参照するDBのカラム名

第四引数に、実際に表示されるカラム名

Acidity.rbのnameが厳密に言えばカラムではないが、
データベースのように扱えるので、nameを指定する

これで先ほどのようなプルダウン形式のカテゴリーの選択欄が作成できました。

# 検索機能の実装!

## ルーティングの記述

**routes.rb**

```rb

Rails.application.routes.draw do
  root to: 'drinks#index'


  get '/drinks/searchdrink',  to: 'drinks#search_drink'
  resources :drinks, only: [:index,:new,:show,:create,:destroy] do
    collection do
      get 'search'
    end
    
end
```

**resourcesの上に、書かないと意図しない画面に遷移させられたりするので、それより上に書きましょう！**

## コントローラーの記述

**drinks_controller.rb**

```ruby

class DrinksController < ApplicationController
  include SessionsHelper

  before_action :create_searching_object,only: [:index,:search_drink]
  def index
    @user = current_user
    @drinks = Drink.all.order("created_at DESC")
  end

  def new
    @drink = DrinkTag.new
  end

  def create
    @drink = DrinkTag.new(drink_params)
    if @drink.valid?
      @drink.save
      redirect_to drinks_path
    else
      render 'new'
    end
  end

  

  def search_drink

    @results = @p.result
  
  end

  private
  def drink_params
    params.require(:drink_tag).permit(:name,:price,:explain,:image,:tag_name,:region_id,:body_id,:acidity_id,:processing_id).merge(user_id: current_user.id)
  end

  def create_searching_object
    @p = Drink.ransack(params[:q]) 
  end
```

indexアクションでは、全投稿の情報を取得しています

create_searching_objectアクションでは、キー(:q)を使って、drinksテーブルから商品情報を探しています
@p と言う名前の検索オブジェクトを生成しています

index,search_drinkアクションのみで使用するので、before_actionで限定しています


search_drinkアクションでは@pに対して、.resultとすることで検索結果を取得して、@resultに代入しています

コントローラーの処理は以上です。

## 検索フォームの実装

ここでは、投稿の検索フォームを実装しましょう。その際、「search_form_for」と「collection_select」という2つのメソッドを使用します。

search_form_forはransack特有の検索フォームを生成するヘルパーメソッドです。

collection_selectメソッドはDBにある情報をプルダウン形式で表示できるヘルパーメソッドです。

**drinks/index.html.erb**

```ruby

<%= search_form_for @p, url: drinks_searchdrink_path do |f| %>
  
  <%= f.search_field :name_cont%>

  <%# _contはidじゃなくて文字列のときに使う%>
  <p>カテゴリー検索</p>
  <%# ベースはドリンククラスで、第二引数で%>
  <%= f.label '酸味'%>
  <%= f.collection_select :acidity_id_eq,Acidity.all,:id,:name,include_blank: '指定なし' %>

  <%= f.submit '検索' %>
<% end %>
```

search_form_forの引数に「@p(検索オブジェクト)」を渡すことで検索フォームを生成しています。

urlはdrink#search_drinkに飛ばしたいので、rails routeで確認してこう言う感じになりました

```rb

  <%= f.collection_select :acidity_id_eq,Acidity.all,:id,:name,include_blank: '指定なし' %>

```

第一引数　検索したいカラム名

第二引数　実際に表示したい配列データを指定する

今回で言えば、Acidity.allです。

第三引数　表示する際に参照するDBのカラム名

第四引数  実際に表示されるカラム名

オプション　include_black 何も選択してないときに表示される内容、今回は「指定無し」

## 検索結果を表示するビューを作成

search_drinkアクションの処理が終わったら、railsのデフォルトでsearch_drink.html.erbにリダイレクトされるので、
search_drink.html.erbを作成して

```erb
<h1>
  検索結果
</h1>
<%# 検索結果の個数で条件分岐 %>
<% if @results.length !=0 %>

<%  @results.each do |drink| %>
  <div class='main'>

  <%# 商品一覧 %>
  <div class='item-contents'>
    <h2 class='title'></h2>
    <ul class='item-lists'>

      <%# 商品のインスタンス変数になにか入っている場合、中身のすべてを展開できるようにしましょう %>
      <%if drink%>
      <li class='list'>
        <%= link_to drink_path(drink.id) do %>
        <div class='item-img-content'>
          <%= image_tag drink.image , class: "item-img" if drink.image.attached? %>
          
          <%# if drink.trade%>
          
   
          
          <%# end %>
        </div>
        <div class='item-info'>
          <h3 class='item-name'>
            <%= drink.name %>
          </h3>
          <div class='item-price'>
            <span><%= drink.price %>円<br>(税込み)</span>
            <div class='star-btn'>
              <%# image_tag "star.png", class:"star-icon" %>
              <span class='star-count'>0</span>
            </div>
          </div>
          <div class='item-explain'>
            <%= drink.explain%>
          </div>
          <div>
            <% if drink.region %>
            産地 <%= drink.region.name%>              
            <% end %>
          </div>
          <div>
            <% if drink.body%>
            コク <%= drink.body.name %>
            <% end %>
          </div>
          <div>
            <% if drink.acidity %>
              酸味 <%= drink.acidity.name%>
            <% end %>
          </div>
          <div>
            <% if drink.processing%>
              加工法 <%= drink.processing.name%>
            <% end %>
          </div>
        </div>
        <% if logged_in? && current_user.id == drink.user_id %>
        <div class="item-delete">
          <%= link_to "削除する",drink_path(drink),method: :delete %>
        </div>
        <% if drink.trade%>
        <%= link_to "商品を購入する", drink_trades_path(drink) %>
        <% end %>
        <% end %>
      </li>
      <%end%>
     
        
      
    </ul>
  </div>
  <%end%>
</div>

<% end %>

<% else %>
  該当する商品はありません
<% end %>
<br>
<%= link_to 'トップページへ戻る', root_path %>
```

と、検索結果が、@resultに入っていて、
それをeach文で、ローカル変数をdrinkにして、検索結果があるだけ表示させています

以上で、Active hashで作ったカテゴリーを用いてransackでカテゴリ検索をする実装が終わりました！