経緯
現在絶賛作成中のポートフォリオで、前回はあいまい検索機能の実装した。
ので、次はgemのransackを用いた検索機能の実装をしようと思い作成。参考になる記事は多く有ったが、それでもチェックボックスでの検索等では結構詰まってしまったので、誰かの参考になればと思い記載しておく。
前回の記事も必要で有れば参照して見てください。
【Rails】 あいまい検索機能の付け方
環境
ruby 2.6.5
Rails 6.0.3.2
haml使用
イメージ図
完成図はこんな感じ
手順
1、gemの導入
2、検索フォームのビュー作成
3、コントローラーに設定
4、検索結果のビュー作成
の4つの手順になります。
gemの導入
先ずは、サクっとgemを入れていきましょう。
gem 'ransack'
変な間違いが、ないように一番下に記載するのが望ましいです。
入力が終わったら、
ターミナルで% bundle install して % rails s忘れずに行いましょう。
rails sしないと反映されないので忘れずに!
これで、gemの導入は終了です。
検索フォームのビュー作成
先ずは、コードから
構成は、①キーワードでの検索、②値段での検索、③状態の検索、④配送料の検索の構成にしてあります。
.title-box
  %p.title 詳細検索
.detail_search--box
  = search_form_for(@q,url: detail_search_items_path) do |f|
    .detail-keyword
      .detail-keyword--box
        %i.fas.fa-plus
        %p.wd キーワードを追加する
      = f.search_field :name_cont, placeholder: "例)バイク", class: "detail-keyword--form"
    .detail-price
      .detail-price--label
        %i.fas.fa-coins
        %p.wd 価格
      .detail-price--forms
        = f.search_field :price_gteq, placeholder: "¥300以上", min:300, class: "price-min"
        %p.wd 〜
        = f.search_field :price_lteq, placeholder: "¥9999999以下", max:9999999, class: "price-max"
    .detail-status
      .detail-status--label
        %i.fas.fa-paperclip
        %p.wd 商品の状態
      .detail-status--checkbox
        .checkboxes
          = f.check_box :status_id_eq_any, { multiple: true }, 1, ''
          = '新品、未使用'
        .checkboxes
          = f.check_box :status_id_eq_any, { multiple: true }, 2, ''
          = '未使用に近い'
        .checkboxes
          = f.check_box :status_id_eq_any, { multiple: true }, 3, ''
          = '目立った傷や汚れなし'
        .checkboxes
          = f.check_box :status_id_eq_any, { multiple: true }, 4, ''
          = 'やや傷や汚れあり'
        .checkboxes
          = f.check_box :status_id_eq_any, { multiple: true }, 5, ''
          = '傷や汚れあり'
        .checkboxes
          = f.check_box :status_id_eq_any, { multiple: true }, 6, ''
          = '全体的に状態が悪い'
    .detail-deliveryFee
      .detail-deliveryFee--label
        %i.fas.fa-truck
        %p.wd 配送料の負担
      .detail-deliveryFee--checkbox
        .checkboxes
          = f.check_box :delivery_fee_id_eq_any, { multiple: true }, 1, ''
          = '送料込み(出品者負担)'
        .checkboxes
          = f.check_box :delivery_fee_id_eq_any, { multiple: true }, 2, ''
          = '着払い(購入者負担)'
    .detail-btn
      = f.submit "検索", class: "sbt-btn"
では、コード事に説明していきます。
・= search_form_for(@q,url: detail_search_items_path) do |f|
普通のフォームを作る際は form_for や form_with を使いますが、Ransackで検索フォームを作る場合、search_form_forメソッドを利用します。
まぁ、これはRansackを用いる場合は、search_form_for を使うんだと言う程度の理解で良いと思います。
次に、search_form_forの引数に@qを取りました。@qのクエリ情報を元に検索を行っていきます。
これは後程、コントローラーで定義していきます。
そして、urlの設定部分ですが、これは検索結果を表示させてい場所へのパスを入力しますので適宜変えてください。今回はdetail_search.html.hamlに検索結果を表示させていので、ルーティングで設定されるplefixを書きます。plefixをパスとして書く時は文字の最後に_pathをつけるような書き方をします。
調べ方は、ターミナルで %rails routes です。
①キーワードでの検索
コードで言うとこの部分
.detail-keyword
  .detail-keyword--box
    %i.fas.fa-plus
    %p.wd キーワードを追加する
  = f.search_field :name_cont, placeholder: "例)バイク", class: "detail-keyword--form"
Ransackを用いた検索で特徴的なのが、後ろについている「:name_cont」の_contの部分です。
ちなみに、:nameはカラム名です。なので、適宜変更してください。また、どのテーブル(モデル)のカラムのデータを取得するかは、コントローラーにて設定します。
話を戻しますが、これはnameカラムに対してLike句を使った部分一致検索を行うという意味になります。分かりやすくイメージしやすく言うと、nameカラムの中に(例えば)みかんと言うキーワードが入ったデータを検索して抽出すると言う意味です。
_contの部分をRansackでは述語(predicate)といい、よく使いそうな述語をいくつか記載しておきます。
| 述語 | 意味 | 
|---|---|
| *_eq | 完全に一致したものを抽出します。 | 
| *_in | 与えられた配列に含まれるものを抽出します。 | 
| *_cont | 文字列が含まれるものを抽出します。 | 
| *_lteq | ある値より小さいものを抽出します。 | 
| *_gteq | ある値より大きなものを抽出します。 | 
②値段での検索
コードで言うとこの部分
.detail-price
  .detail-price--label
    %i.fas.fa-coins
    %p.wd 価格
  .detail-price--forms
    = f.number_field :price_gteq, placeholder: "¥300以上", min:300, class: "price-min"
    %p.wd 〜
    = f.number_field :price_lteq, placeholder: "¥9999999以下", max:9999999, class: "price-max"
ここでの説明が必要なのは、後ろについている「:price_gteq」の_gteqの部分と、「:price_lteq」の_lteqの部分でしょうか。
これは上記の表にも書かれているように、
_gteqは、ある値より大きなものを抽出、
_lteqは、ある値より小さいものを抽出すると言う意味です。
要は、priceカラムの、(例えば)300よりも大きいもの2000より小さいものを抽出すると言う意味です。
③状態の検索
コードで言うとこの部分
.checkboxes
  = f.check_box :status_id_eq_any, { multiple: true }, 1, ''
  = '新品、未使用'
  (略)
.checkboxes
  = f.check_box :status_id_eq_any, { multiple: true }, 6, ''
  = '全体的に状態が悪い'
ここはちょっとややこしいと言うか、僕もちゃんと理解している訳でない為拙い説明になるかもです。
先ずは、後ろについている「:status_id_eq_any」の_eq_anyの部分です。
_eqの部分は、これは上記の表にも書かれているように、
_eqは、完全に一致したものを抽出と言う意味です。じゃあ、_anyはと言うと複数の場合にはこれを付けなくてはいけません。今回、チェックボックスは6つあるので、_eq_any となる訳です。
そして、「multiple: true」も同様の理由で必要です。チェックボックスを複数使う場合は、これを記載しないといけません。これを記載する事で、データの変数が配列として認識されます。
実際にパラメーターで送られているデータです:["1", "", "", "", "", ""]
数字で記載されているものが、チェックボックスがチェックされたものです。
要は、チェックボックスを複数使う場合は、_anyをつける事、multiple: trueを記載する事と覚えて置いてください。
次に、1, '' この部分です。
これは、チェックボックスがチェックされた場合、1というデータを返し、チェックボックスがチェックされなかった場合''と返すという意味です。
''??って何だと思いますが、要はチェックボックスがチェックされなかったからなんのデータも送りませんよっと言う意味です。空と言った方が分かりやすいですかね。
では、この数字の意味は言うと、今回はidの番号ですね。
今回はActiveHashと言うgemを使って、こんな感じでデータを入れています。
↓↓↓

数字は、このidの番号になります。
ActiveHashはこちらの記事を見れば簡単に出来ます。
Railsのgem 'active_hash'で都道府県データを作成してみた
改めて、パラメーターで送られているデータを使って説明すると、
["1", "", "", "", "", ""]
id:1 の新品、未使用というチェックボックスだけがチェックされて、残りの5つはチェックされていないので ''となっています。
なので、status_idカラムの中の新品、未使用と言うデータと一致したものを抽出します。
これで、なんとなくでも分かって頂ければ幸いです。
④配送料の検索は割愛します。③状態の検索とやっている事が全く一緒なので。
コントローラーに設定
先ずは、コードから
def index #indexは、検索フォームのビュー
  @q = Item.ransack(params[:q])
end
def detail_search #detail_searchは、検索結果のビュー
  @q = Item.ransack(params[:q])
  @items = @q.result(distinct: true)
end
params[:q]で検索フォームで受け取ったパラメータを受け取り、Item.ransack(params[:q])とすると、受け取ったパラメータ(検索データ)を元に検索結果のオブジェクトを作成することが出来ます。
そして、そのオブジェクトに対して、@items = @q.resultとすると検索結果を取得する事ができます。
分かりにくですが、入力したデータを受け取り、検索オブジェクトを作って、検索結果を出力してるんだなっという認識をしていただけたら大丈夫です。
そして、distinct: trueについてですが、distinctはSQLにある重複を排除する機能です。trueにしておくことで結果から重複した内容のものを排除できます。
検索結果のビュー作成
後は、検索結果のビューを作成して(ここでは、detail_search.html.hamlと言うファイルですが)、eachメソッドを使って出力すればオッケーです。
.contents__box
  - @items.each do |item|
  ここより下は今自分が作っているものに照らし合わせて作成お願いします。
最後に
一応SCSSも添付しておきます。
.title-box{
  width: 40%;
  height: 60px;
  border: 1px solid #EEEEEE;
  background-color: red;
  box-shadow: 1px 1px 1px rgba(1,0,0,0.4);
  margin-left: 40%;
  margin-top: 40px;
  padding: 14px 3px 0 0;
  .title{
    color: #fff;
    font-size: 20px;
    font-weight: bold;
    text-align: center;
  }
}
.detail_search--box{
  margin-left: 40%;
  margin-top: 5px;
  width: 40%;
  border: 1px solid #EEEEEE;
  background-color: #fff;
  box-shadow: 1px 1px 1px rgba(1,0,0,0.1);
  .detail-keyword{
    margin-top: 20px;
    &--box{
      display: flex;
      margin-left: 15px;
      .fa-plus{
        margin: 3px 5px 0 0;
      }
    }
    &--form{
      width: 90%;
      height: 40px;
      margin-left: 15px;
      margin-top: 5px;
      padding-left: 5px;
      border: 1px solid #cccccc;
      border-radius: 5px 5px 5px 5px / 5px 5px 5px 5px;
    }
  }
  .detail-price{
    margin-top: 25px;
    &--label{
      display: flex;
      margin-left: 15px;
      .fa-coins{
        margin: 3px 5px 0 0;
      }
    }
    &--forms{
      margin-top: 5px;
      .price-min{
        width: 90%;
        height: 40px;
        margin-left: 15px;
        border: 1px solid #cccccc;
        border-radius: 5px 5px 5px 5px / 5px 5px 5px 5px;
        padding-left: 5px;
      }
      .wd{
        margin-left: 15px;
        color: #AAAAAA;
      }
      .price-max{
        width: 90%;
        height: 40px;
        margin-left: 15px;
        border: 1px solid #cccccc;
        border-radius: 5px 5px 5px 5px / 5px 5px 5px 5px;
        padding-left: 5px;
      }
    }
  }
  .detail-status{
    margin-top: 25px;
    &--label{
      display: flex;
      margin-left: 15px;
      .fa-paperclip{
        margin: 3px 5px 0 0;
      }
    }
    &--checkbox{
      margin-top: 5px;
      .checkboxes{
        margin-left: 15px;
      }
    }
  }
  .detail-deliveryFee{
    margin-top: 25px;
    &--label{
      display: flex;
      margin-left: 15px;
      .fa-truck{
        margin: 3px 5px 0 0;
      }
    }
    &--checkbox{
      margin-top: 5px;
      .checkboxes{
        margin-left: 15px;
      }
    }
  }
  .detail-btn{
    margin-top: 20px;
    .sbt-btn{
      width: 60%;
      height: 35px;
      margin: 15px 0 20px 45px;
      border: none;
      border-radius: 10px;
    }
    .sbt-btn:hover{
      opacity: 0.5 ;
      background-color: #C0C0C0;
    }
  }
}
後は、今回はアイコンを使っているのでgem 'font-awesome-sass'の導入も必要ですね。これに関しては必要な方は簡単なので下記の記事から行ってください。
rails font-awesome-sass導入方法
これで上記の最初の方に添付した画像のようになるはずです!
実装出来なかったなどの不備が有ればご連絡ください!
ありがとうございました。