Activehashとransackを併用する前の注意点

# 概要
こんばんは。プログラミング初学者です。
Activehashを使用してデータを管理しているカテゴリーがあったのですが、のちに検索機能を実装する際にransackを使用した時に少し面倒なことが起きたので、備忘録的にまとめたいと思います。
少し対象が限定的なので、下記対象となる実装に心当たりがあれば読み進めていただけると幸いです。
タイトルの割に大衆受けするような注意点ではありませんが、どなたかのご参考になれば幸いです。

# 対象となる実装
Activehashのモデルファイルに、下記のような実装をしている場合
・「id: 0」などに「---」の項目を設けている。
#### 例
```ruby
class Displacement < ActiveHash::Base
  self.data = [
    { id: 0, name: '---' },
    { id: 1, name: '50cc以下' },
    { id: 2, name: '51~125cc' },
    { id: 3, name: '126~250cc' },
    { id: 4, name: '251~400cc' },
    { id: 5, name: '401~750cc' },
    { id: 6, name: '751~1000cc' },
    { id: 7, name: '1001cc以上' }
  ]
```

加えて「---」選択時は保存できないように実装をしている。

```ruby
validates :displacement, numericality: { other_than: 0 }
```

#### 表示例

[![Image from Gyazo](https://i.gyazo.com/2d39198e81207fda8e81d99bd818ae1e.png)](https://gyazo.com/2d39198e81207fda8e81d99bd818ae1e)


# 問題点

上記のようにActivehashを用いてデータを保存できるように実装しており、
検索機能時にransackを用いてこのidの中から絞り込み検索できるように実装したい場合。

例えば上記例でいうと、検索機能実装にあたって、Activehashのカテゴリーを一覧化して、「251~400cc」のデータを検索したい、といった機能を考えているとします。

#### コントローラーファイル例
```ruby
  before_action :search_reviews, only: [:index, :search]

  def search
    @results = @r.result
  end

  def search_reviews
    @r = Review.ransack(params[:q])
  end
```

#### ビューファイル例
```erb
<div class="refined-title">▶︎　絞り込み検索</div>
    <%= search_form_for @r, url: search_reviews_path do |f| %>
      <%= f.label :displacement_id_eq, '排気量' %>
      <%= f.collection_select :displacement_id_eq, Displacement.all, :id, :name %>
```

このように実装すると、実際の表示では
[![Image from Gyazo](https://i.gyazo.com/96d7ad0b7362061473ec265a3ab2443e.png)](https://gyazo.com/96d7ad0b7362061473ec265a3ab2443e)

と、それぞれのActivehashカテゴリーから絞り込んで検索できるように見えますが、問題点が一つあります。

上記例の場合、例えば排気量は指定せずにメーカーと車種タイプを選んで検索をかける場合、
[![Image from Gyazo](https://i.gyazo.com/03d32cb4ea56ca458f8c560c47b5ce75.png)](https://gyazo.com/03d32cb4ea56ca458f8c560c47b5ce75)

実際に検索にかかるのは
メーカーが「HONDA」に該当するidと車種タイプが「スーパースポーツ」に該当するidだけでなく、
**排気量のidが「0」に該当するもの**も検索してしまいます。

しかも**ransackで使えるメソッドの中に、特定のidを表示させない、取得しないものはありませんでした。**

保存時には「numericality」のバリデーションをかけているため、当然「id: 0」に該当するデータはなく、**検索結果は「該当なし」**となります。
これが今回私の実装にあたって大きな問題となりました。

# ではどうすればいいのか？
Activehashとransack併用を考えているのであれば、
Avtivehash作成時に「---」の項目を設けるのではなく、
### 表示するときにpromptを使おう！！
というのが私なりの解となります。

詳しく解説します。

#### まずはActivehashに関する実装部分です。
Activehashモデルファイルの「id: 0」を消し去ります。

```ruby
class Displacement < ActiveHash::Base
  self.data = [
    { id: 1, name: '50cc以下' },
    { id: 2, name: '51~125cc' },
    { id: 3, name: '126~250cc' },
    { id: 4, name: '251~400cc' },
    { id: 5, name: '401~750cc' },
    { id: 6, name: '751~1000cc' },
    { id: 7, name: '1001cc以上' }
  ]
```

すると標準表示がこのようになります。
[![Image from Gyazo](https://i.gyazo.com/a67a86f94ebbfcca1e1a4c40326cbe7a.png)](https://gyazo.com/a67a86f94ebbfcca1e1a4c40326cbe7a)

これでは毎回初期表示が「５０cc以下」になって、なんか嫌ですよね。
しかも選択し忘れていた場合、全てのデータが「50cc以下」で登録されてしまいます。

したがって、オプションのpromptを使用します。
保存時のビューファイルにて

#### ビューファイル例
```erb
<%= f.collection_select(:displacement_id, Displacement.all, :id, :name, prompt: '---') %>
```

activehash表示時に上記のようにプルダウンボタンで実装する場合、各引数と役割は下記の通りです。

```erb
<%= form.collection_select(保存されるカラム名, オブジェクトの配列, カラムに保存される項目, 選択肢に表示されるカラム名, オプション, htmlオプション) %>
```

第５引数に用いるオプションとして、**promptを使用します。**
htmlオプションはclassの付与などです。
私の場合、うまくいかなかったので、検証ツールを用いてidセレクタでCSSを適用しました。

prompt使用により、初期表示がpromptで設定した文字列となります。
[![Image from Gyazo](https://i.gyazo.com/2d39198e81207fda8e81d99bd818ae1e.png)](https://gyazo.com/2d39198e81207fda8e81d99bd818ae1e)
うまくいきました。
さらにモデルファイルにおいて、NOT NULL制約をかけることで、「---」選択時は保存されずエラーメッセージを返すようにできます。
（promptで設定した項目は『nil』として返すため。）



#### 次にransackでの検索機能実装部分です。

##### 検索機能実装のビューファイル例

```erb
    <div class="refined-title">▶︎　絞り込み検索</div>
    <%= search_form_for @r, url: search_reviews_path do |f| %>
      <%= f.label :displacement_id_eq, '排気量' %>
      <%= f.collection_select :displacement_id_eq, Displacement.all, :id, :name, prompt: '指定なし' %>
```

ここでも**第５引数にpromptを用いて、検索項目「指定なし」を設けました。**
（ちなみに私はここで、第２引数に無理矢理exceptメソッドとかを使ってなんとか「id: 0」抜きで表示させようと必死でしたが、NoMethodErrorに怒られてました。）
（もっと悪いやり方では、`Displacement.find [1, 2, 3, 4, 5, 6, 7]`なんてことまで思いつきましたがあまりにもスマートでない笑）

##### 表示例
[![Image from Gyazo](https://i.gyazo.com/dd66e0eb133986687f7608a7464f6db6.png)](https://gyazo.com/dd66e0eb133986687f7608a7464f6db6)
これで、絞り込み検索時に必要ない情報は「指定なし」を選択することができるし、
**「指定なし」とした項目は検索結果に引っかからない**ので、上記例でいうと3つの項目の内2つだけの項目で絞りたい、なんてことも可能です。

# まとめ
結論：ActiveHashの表示には、
##### promptを積極活用しよう！！！
です。

ここまでご覧いただいた方ありがとうございました。