【Rails】複数の条件の検索機能を作る。日付範囲検索を行う

# やりたいこと
１つの検索フォームで、複数の値を同時に検索したい。
今回は、customersテーブルのname_kana、phoneとreservationsテーブルのcheck_inの日付範囲検索を１つの検索フォームで実装する
<img width="1129" alt="ba95a6737f4ce1951d75b96a7497e8fe.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524466/a8c9b67a-0b21-23d2-2bda-71574d30145e.png">
# 前提条件
下記のように、一人のcustomerが多くのreservationsを持つ、１対多の関係。

```models/customer.rb
has_many :reservations
```
```models/reservation.rb
belongs_to :customer
```

# 実装方法
１.reservationモデルに、scopeを使って複数の検索条件を定義する。
２.reservationsコントローラにsearchアクションを定義。その際に、①で作ったscope（search）を利用する。各フォームのparamsは１つのハッシュにしてまとめて受け取る。
３.ビューを作成する。

※scopeとは・・・複数のクリエ（SQL文による検索条件）をまとめたメソッドを定義できるメソッド

# 実装
### １.reservationモデルに、scopeを使って複数の検索条件を定義する。

```reservation.rb
class Reservation < ApplicationRecord
  belongs_to :customer

  scope :search, -> (search_params) do      #scopeでsearchメソッドを定義。(search_params)は引数
    return if search_params.blank?      #検索フォームに値がなければ以下の手順は行わない

    name_kana_like(search_params[:name_kana])
      .check_in_from(search_params[:check_in_from])
      .check_in_to(search_params[:check_in_to])
      .phone_like(search_params[:phone])   #下記で定義しているscopeメソッドの呼び出し。「.」で繋げている
  end

  scope :name_kana_like, -> (name_kana) { where('name_kana LIKE ?', "%#{name_kana}%") if name_kana.present? }  #scopeを定義。
  scope :check_in_from, -> (from) { where('? <= check_in', from) if from.present? }
  scope :check_in_to, -> (to) { where('check_in <= ?', to) if to.present? }
   #日付の範囲検索をするため、fromとtoをつけている
  scope :phone_like, -> (phone) { where('phone LIKE ?', "%#{phone}%") if phone.present? }
 #scope :メソッド名 -> (引数) { SQL文 }
 #if 引数.present?をつけることで、検索フォームに値がない場合は実行されない

end
```

上から、scope :メソッド名, -> (引数) do 〜〜 end　で、:searchというメソッド名のscopeを定義している。
今回引数にしているsearch_paramsは、この後reservationコントローラから渡す引数の名前と同じにしています。

```reservation.rb
    name_kana_like(search_params[:name_kana])
      .check_in_from(search_params[:check_in_from])
      .check_in_to(search_params[:check_in_to])
      .phone_like(search_params[:phone]) 
```
この部分は一見わかりずらいですが、各scopeのメソッドを繋げているだけです。

```a.rb
where('search_params[:name_kana] LIKE ?', "%#{search_params[:name_kana]}%") if search_params[:name_kana].present? . where('? <= check_in', search_params[:check_in_from]) if search_params[:check_in_from].present? .  where('check_in <= ?', search_params[:check_in_to]) if search_params[:check_in_to].present? . where('phone LIKE ?', "%#{search_params[:phone]}%") if search_params[:phone].present?
```
蓋を開けると上記のようになるので、Model.where('id>?',3)みたいなコードのwhere文が連結していて、検索条件がいっぱいあるようなイメージです。

name_kanaと、phoneはあいまい検索。check_in_to以上、check_in_from以下で検索。
各フォームにデータがなければその検索は行わない。
上記の条件を同時に定義した形です。

### ２.reservationsコントローラにsearchアクションを定義。その際に、①で作ったscope（search）を利用する。

```reservations_controller.rb
  def search
    @search_params = reservation_search_params  #検索結果の画面で、フォームに検索した値を表示するために、paramsの値をビューで使えるようにする
    @reservations = Reservation.search(@search_params).joins(:customer)  #Reservationモデルのsearchを呼び出し、引数としてparamsを渡している。
  end

  private

  def reservation_search_params
    params.fetch(:search, {}).permit(:name_kana, :check_in_from, :check_in_to, :phone)
    #fetch(:search, {})と記述することで、検索フォームに値がない場合はnilを返し、エラーが起こらなくなる
    #ここでの:searchには、フォームから送られてくるparamsの値が入っている
  end
```

下のストロングパラメーターから。
この後、ビューのform_withに、scope: :searchというオプションをつける。
params.fetch(:search, {})の:searchには、検索フォームに入力されたデータの全てが、１つのハッシュとして送られている。

[1] pry> params[:search]
 => {"check_in_from"=>"", "check_in_to"=>"", "name_kana"=>"", "phone"=>"09000000000"}

[fetch](https://ref.xaio.jp/ruby/classes/hash/fetch)は、引数にキーを渡すことで、その値を返してくれるメソッド。このfetchの第二引数に、{}を渡すことで、検索フォームの全てに値がない場合でもエラーが起こらなくなります。

permitの後は、いつも通り受け取りたいキーを記述してください。
permit(:name_kana, :check_in_from, :check_in_to, :phone)
check_inカラムは範囲検索をするので、fromとtoを加えて独自の名前をつけています。form内、モデルのscope内で使う名前と統一していればなんでもいいはずです。


searchアクションでは、上記で説明したストロングパラメーターの値を変数化させることで、ビューでparamsの値を使えるようにします。
検索結果画面に、検索をした値がフォームに残るようにさせるためです。

```
@reservations = Reservation.search(@search_params).joins(:customer)
```
で、①で作成したreservationモデルのscope（search）を呼び出しています。
その際に、引数にストロングパラメータの値を渡すことで検索させています。
アソシエーションさせているので、includesが使えると思うのですが、なぜかうまく行かないのでjoinsを使ってます。わかる方がいれば教えてください。

上記の検索結果をビューに持っていくために、reservationsという変数に格納しています。

searchアクションのルーティングも作成しておきます。

```routes.rb
  resources :reservations, only: [:index, :show] do
    collection do
      get :search
    end
  end
```

### ３.ビューを作成する。
```reservations/search.html.erb
<%= form_with(scope: :search, url: search_reservations_path, method: :get, local: true) do |f| %>

  <%= f.label :check_in, "到着日" %>
  <%= f.date_field :check_in_from, value: @search_params[:check_in_from] %> ~
  <%= f.date_field :check_in_to, value: @search_params[:check_in_to] %>

  <%= f.label :name_kana, "ナマエ" %>
  <%= f.text_field :name_kana, placeholder: '半角のみ', value: @search_params[:name_kana] %>

  <%= f.label :phone, "電話番号" %>
  <%= f.text_field :phone, value: @search_params[:phone] %>

  <%= submit_tag '検索', class: "button" %>
<% end %>
```
form_withのオプションでscope: :searchとすることで、各フォームのparamsが、:searchという名前の１つのハッシュにまとめられているイメージです。それをコントローラのストロングパラメータで受け取っていました。
urlは②で作成したsearchアクションのパスを指定。
form_withはデフォルトでremote: trueなので、local: trueを記述。

各formに、value: @search_params[:キー]をつけることで、検索後に検索した値を表示することができます。

```reservations/search.html.erb
<% unless @search_params.blank? %>
  <% @reservations.each do |reservation| %>
    <tr>
      <td><%= reservation.customer.name_kana %></td>
      <td><%= reservation.customer.phone %></td>
      <td><%= reservation.check_in %></td>
    </tr>
  <% end %>
<% end %>
```
reservationsコントローラで取得した検索結果をeachで回すことでヒットした情報を表示！
そのままだと、検索画面に遷移した時に全てのレコードが取得されて表示されていたので、search_paramsに検索したデータがない場合はデータを表示させないようにしています。

# 備考
参考にさせていただいた記事
[【Rails】一覧ページ上部に検索機能を実装する ~ form_with ~](https://qiita.com/shin1rok/items/779e581e9d12a92310c3)

真似して書いたらなんかできた！ってレベルだったので、理解を深めたくて記事にしました。
全く意味がわからない！から、多少読めたになった程度で、いまいち理解しきれてない・・・。
何か間違いなどがあればぜひご指摘ください。