【Rails6】 詳細検索機能をgemなしで実装する
2022年1月からプログラミングの勉強をしております。詳細検索を実装した際に大変苦労したので皆さんの参考になるような記事を書きたいと思い筆を取りました。ransackgemなしでの詳細検索の実装になります。SQLの勉強にもなりますので、詳細検索を実装する際はぜひgemなしで実装してみてください。
開発環境
Rails: 6.1.4
Ruby: 2.6.3
目標
テーブルに登録されているすべてのカラムでAND検索を可能にする。(タグ検索も)
前提
検索対象projectテーブルは実装済み。タグは作成済み。
タグ作成で参考にさせていただいた記事
https://qiita.com/Kairi_Yasunnde/items/935dcdb8ec88b9ed9d91
controllerの作成
$ rails g controller searches search
class SearchesController < ApplicationController
def search
end
では、searchコントローラーを書いていきましょう。
class SearchesController < ApplicationController
def search
# tag_mapsはtagテーブルとprojectテーブルの中間テーブル。
# includesでtag_mapsテーブルとまとめて取得
@result = Project.includes(:tag_maps)
# 入力されたキーワード(タイトルか備考)で検索
@keyword = params[:keyword]
if @keyword.present?
@result = @result.where("title like ? OR caption like ?", "%#{@keyword}%", "%#{@keyword}%")
end
# date_selectで選択された日付
# 投稿日で検索
year = params['date(1i)']
month = params['date(2i)']
day = params['date(3i)']
if year.present? && month.present?
# date_selectで選択された値をparamsからTimeオブジェクトにする
@date = Time.gm(year, month, day)
@result = @result.where(created_at: [@date.at_beginning_of_month..@date.end_of_month])
end
# チェックされたタグで検索
@tag_ids = params[:tag_ids]
# 複数のタグがチェックされた場合は以下の処理を実行
if @tag_ids.count > 1
@result = @result.where(tag_maps: { tag_id: @tag_ids })
end
end
ポイント
タグ検索もするのでtag_mapsテーブルもまとめて取得する@resultを定義。
その@resultからさらに検索していくという形を取る。
date_selectについて
今回projectの投稿日を検索するためにdate_selectをビューページで採用しました。今回は投稿年と月が揃った時のみ検索を実行するようにしております。ただ、コントローラーに送られる情報には注意が必要です。以下の画像のようにビューページで日を非表示にして検索をしても、コントローラーには日付の最小値、「1」が自動で送られてしまいます。
# 何も入力せずに検索ボタンを押した場合
Parameters: {"date(1i)"=>"", "date(2i)"=>"", "date(3i)"=>"1"}
また、投稿日の検索に関してはcreated_atカラムを検索するのですが、こちらはTimeオブジェクトのため、paramsのデータをTime.gm(year, month, day)
でTimeオブジェクトにします。
@date.at_beginning_of_month..@date.end_of_month
で月の最初から最後まで、つまり選択した月に投稿されたものを取得するようにしております。
日付に関してはこちらの記事を参考にいたしました。
https://qiita.com/mmmm/items/efda48f1ac0267c95c29
タグについて
タグに関しては検索者自身が作成できるようなものにはしませんでした。seedファイルにあらかじめ作成をしております。このタグは一つのprojectに対して、複数つけることが可能になっております。また、検索も複数指定することが可能です。tag_mapsはtagsテーブルとprojectsテーブルの中間テーブルです。includesを使ってprojectsテーブルとtag_mapsテーブルは左外部結合のような形になっているので、@result.where(tag_maps: { tag_id: @tag_ids })
という記述になっております。
キーワード検索について
キーワード検索についてはたくさんの方が記事にしているためここでは割愛いたします。
viewの作成
部分テンプレートで検索フォームを作成いたしました。
<%= form_with url: result_path, method: :get, local: true do |f| %>
<%= f.label :keyword,"案件名"%>
<%= f.text_field :keyword ,value: keyword,placeholder: "案件名or備考欄の文言"%>
<%= f.label :date,"投稿年月を選択"%>
<!--終わりの年を設定しないと、続けて検索をする際に表示年数が少なくなってしまうからend_year設定。-->
<%= f.date_select :date,start_year: 2013,end_year: 2027,use_month_numbers: true,discard_day: true,include_blank: true,selected: date%>
<%= f.collection_check_boxes(:tag_ids, Tag.all, :id, :name) do |tag|%>
<%= tag.label do %>
<%= tag.check_box checked: tag_ids.include?(tag.object.id.to_s)%>
<%= tag.text%>
<% end %>
<% end %>
<%= f.submit '検索'%>
<% end %>
ポイント
検索ボタンを押した後に検索した値を保持するため、f.text_fieldにはvalue,f.selectにはselected,tag_check_boxにはcheckedを設定しました。ホームヘルパーによって値保持の書き方が違うので注意が必要です。
次に検索画面のviewを作成します。
<h5>詳細検索</h5>
<%= render 'form',keyword: @keyword,date: @date,tag_ids: @tag_ids %>
<h5>検索結果</h5>
<% if @result.present?%>
<p>検索結果は以下のとおりです</p>
<%= render 'index',project: @result %>
<% else %>
<p>検索結果はありません</p>
<% end %>
今回は詳細検索と検索結果を同じページに表示しております。
検索結果の部分も部分テンプレートで必要事項を表示するようにしております。
今回は割愛させていただきます。
最後に
Qiita初投稿のため、至らぬ点多々あるかと思います。その場合はご指摘いただければと思います。
今回ポートフォリオに詳細検索をgemなしで実装しまして大変勉強になりました。また、SQLをしっかり勉強しなければまずいという危機感を感じたのでしっかり勉強をしたいと思います。