【Rails】ActiveHashのデータでプルダウン検索する機能を実装

# 概要
ActiveHashのデータでプルダウン検索する機能を実装する方法について、まとめます。
機能の実装には、ransackというgemを用いました。

ransackは複雑な検索を可能にするgemです。
今回の機能はシンプルな検索なので、ransackを用いなくてもできるかも知れません。
その点については今後の課題とします。

# 参照
- ActiveHashのGitHubページ<br>
https://github.com/zilkey/active_hash
- 【Rails】active_hashを使って疑似モデルを作ろう<br>
https://pikawaka.com/rails/active_hash
<br>
<br>
- ransackのGitHubページ<br>
https://github.com/activerecord-hackery/ransack
- Ransackで簡単に検索フォームを作る73のレシピ<br>
http://nekorails.hatenablog.com/entry/2017/05/31/173925

# 完成イメージ
今回は、写真投稿アプリを題材にカテゴリーを選択後、そのカテゴリーの写真一覧を表示する方法を説明します。

[![Image from Gyazo](https://i.gyazo.com/3d37261c8d38e9320ffdb5fcad160303.gif)](https://gyazo.com/3d37261c8d38e9320ffdb5fcad160303)

# 開発環境
- macOS Catalina 10.15.7
- ruby 2.6.5
- Rails 6.0.3.4

# 実装の流れ

1. gem ransackをインストール
1. ルーティングを設定
1. コントローラーにアクションを設定
1. 検索ボックスを作成
1. 検索結果ビューを作成

# 今回のコード
コードは、必要な部分以外（CSSなど）は省略して記載します。
ActiveHashを用いた疑似モデルの作成方法についても省略します。疑似モデルの作成が完了しているという前提で話を進めます。作成方法については参照ページなどをご覧ください。

## 1. gem ransackをインストール

```:Gemfile
# ファイルの一番下に追記
gem 'ransack'
```
```:ターミナル
% bundle install
```

## 2. ルーティングを設定

```routes.rb
get '/photo/category', to: "photos#category"
```
photosコントローラーにcategoryアクションを設定します。
categoryアクションが実行されると、カテゴリー検索結果のページが表示されるようにします。

## 3. コントローラーにアクションを設定
```photos_controller.rb
　before_action :search_category_photo, only: [:index, :category, :hashtag, :search]

　（省略）

  private

  def search_category_photo
    @q = Photo.ransack(params[:q])
  end
```
privateメソッドに`search_category_photo`メソッドを作成します。これを実行すると、`@q = Photo.ransack(params[:q])`という検索オブジェクトが作成されます。

今回、検索結果ボックスをヘッダーに格納しました。それにより、カテゴリー検索結果画面（:category）だけでなく、トップ画面（:index）、ハッシュタグ検索結果画面（:hashtag）、キャプション検索結果画面（:search）にも、検索結果ボックスが表示されています。そのため、`before_action`で各画面のロード時に`search_category_photo`メソッドが実行されるようにします。これがないとエラーがでました。この解決に苦労しました…。

```photos_controller.rb
  def category
    @photos = @q.result
    category_id = params[:q][:category_id_eq]
    @category = Category.find_by(id: category_id)
  end
```
categoryメソッド内に`@photos = @q.result`を記入し、検索結果を取得します。

params[:q]には検索パラメーターが格納されています。

例えば、ActiveHash疑似モデルのid:5のカテゴリーをプルダウンで選択した場合、params[:q]には、以下のものが格納されていました。

```:console
[1] pry(#<PhotosController>)> params[:q]
=> <ActionController::Parameters {"category_id_eq"=>"5"} permitted: false>
```

検索結果ビューで選択したカテゴリー名を表示するため、`category_id = params[:q]:category_id_eq]`として、params[:q]内のcategory_id（この例では"5"）を取得しました。そのcategory_idを用いて`@category = Category.find_by(id: category_id)`として、選択したカテゴリー名を取得しました。

## 4. 検索ボックスを作成
以下は、今回作成したカテゴリーのActiveHash疑似モデルです。

```catetory.rb
class Category < ActiveHash::Base
  self.data = [
    { id: 0, name: '--' },
    { id: 1, name: '犬' },
    { id: 2, name: '猫' },
    { id: 3, name: '鳥' },
    { id: 4, name: '魚' },
    { id: 5, name: '爬虫類' },
    { id: 6, name: '両生類' },
    { id: 7, name: '昆虫' },
    { id: 8, name: 'その他' }
  ]
  include ActiveHash::Associations
  has_many :photos
end
```


```erb:_header.html.erb
<%= search_form_for @q, url: photo_category_path do |f| %>
    <%= f.collection_select :category_id_eq, Category.where.not(id: 0), :id, :name, include_blank: "カテゴリー検索" %>
    <%= f.submit '検索' %>
<% end %>
```
ransackのヘルパーメソッド`search_form_for`メソッドで、検索フォームを作成します。引数に検索オブジェクト`@q`を渡しています（参照：『3. コントローラーにアクションを設定』）。urlのパス名はcategoryアクションのパス名です（`rails routes`で確認しました）。
ヘルパーメソッド`collection_select`メソッドでプルダウンのカテゴリー選択ボックスを作成します。

**注）.collection_selectメソッドについて理解が不十分の可能性があります。以下、間違いがありましたらご指摘いただきたいです。**

`<%= f.collection_select ①:category_id_eq, ②Category.where.not(id: 0), ③:id, ④:name, ⑤include_blank: "カテゴリー検索" %>`

### ①カラム名＋ransackの検索メソッド
photosテーブルの:category_idで検索するため、カラム名は`:category_id`とし、選択したカテゴリーと同じidの写真を取得するため、検索メソッドを`-eq`としました。`-eq`は条件に合った検索を行うメソッドです（イコールのイメージ）。
### ②プルダウンリストで使う配列データ
ActiveHashのCategoryモデルのデータを使用するため、モデル名`Category`を引数としました。`.where.not(id: 0)`でid: 0は表示せず、かわりに⑤で`include_blank: "カテゴリー検索"`とし、未選択時は"カテゴリー検索"という文字が検索ボックス内に表示されるようにしました。
### ③検索に使うカラム名
Categoryモデルのidカラムの値を検索に使うため、`:id`としました。
### ④プルダウンメニューに表示されるカラム名
プルダウンメニューには、Categoryモデルの`:id`ではなく`:name`の値を表示したいため、`:name`としました。

## 5. 検索結果ビューを作成
カテゴリー検索結果ページの内容を`category.html.erb`に記載します。

```erb:category.html.erb
# 実装に必要な部分のみ記載しています
<h2>カテゴリー <%= @category.name %></h2>
  <ul>
    <% @photos.each do |photo| %>
      <li>
          <%= image_tag photo.image if photo.image.attached? %>
      </li>
    <% end %>
  </ul>
</div>
```
`@category`と`@photos`は、上記、photosコントローラーのcategoryメソッドで設定したインスタンス変数です（参照：『3. コントローラーにアクションを設定』）。

`@category.name`で、プルダウンリストから選択したカテゴリー名を取得し検索結果ビューに表示させました。

`@photos`にはプルダウンリストから選択したカテゴリーの写真が格納されているので、`.each`メソッドで1枚ずつ取り出しビューに表示させました。

# おわりに
`.collection_selectメソッド`の引数の入力方法が難しく、何度も試行錯誤して正解にたどり着きました。今回の記事が参考になりましたら幸いです。

初学者のため、間違いや改善点などありましたらご指摘お願いいたします。