39
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-11-07

やりたいこと

1つの検索フォームで、複数の値を同時に検索したい。
今回は、customersテーブルのname_kana、phoneとreservationsテーブルのcheck_inの日付範囲検索を1つの検索フォームで実装する
ba95a6737f4ce1951d75b96a7497e8fe.png

前提条件

下記のように、一人のcustomerが多くのreservationsを持つ、1対多の関係。

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

実装方法

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

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

実装

1.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以下で検索。
各フォームにデータがなければその検索は行わない。
上記の条件を同時に定義した形です。

2.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つのハッシュとして送られている。

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

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

3.ビューを作成する。

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という名前の1つのハッシュにまとめられているイメージです。それをコントローラのストロングパラメータで受け取っていました。
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 ~

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

39
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?