はじめに
ポートフォリオ制作中です!
,で区切って複数のワードを検索できるように実装したためアウトプットします✍️
複数検索のアウトプットなので、検索機能についてはさらっと書いています!
検索機能実装については下記記事がわかりやすかったです!
複数ワード検索はもっといい方法を検討中です🧐
もしあれば教えていただけると幸いです!
6/27追記 記述方法変えました!plan_searchカラムを作成しています
※旅行やデートのプランを共有するアプリなので、投稿機能はPostではなくPlanを使用しています!
開発環境
・ruby: 3.1.2
・Rails: 6.1.7.7
・Cloud9
・bootstrap導入済
ER図
ルーティング
Rails.application.routes.draw do
scope module: :public do
:
get 'search' => 'searches#search'
end
end
作成されたルーティング↓
search GET /search(.:format) public/searches#search
ビュー(検索窓)
<li class="nav-item list-unstyled d-flex justify-content-center mb-2">
<div class="search_form">
<%= form_with url: object, local: true, method: :get do |f| %>
<%= f.text_field :word, placeholder: ' 東京, カフェ' %>
<%= f.select :range, options_for_select([['Plan'],['User']]) %>
<button type="submit" class="btn btn-secondary btn-sm mb-1">
<i class="fas fa-search" style="color: #ffffff;"></i>
</button>
<% end %>
</div>
</li>
コントローラー
class Public::SearchesController < ApplicationController
before_action :authenticate_user!
def search
@range = params[:range]
if @range == "User"
@users = User.looks(params[:search], params[:word]).order(created_at: :desc)
elsif @range == "Plan"
@plans = Plan.looks(params[:search], params[:word]).order(created_at: :desc)
end
end
end
モデル
Userモデル
class User < ApplicationRecord
# アソシエーション
has_many :plans, dependent: :destroy
:
# 検索方法分岐
def self.looks(search, word)
# 退会ユーザーの投稿を除く
@user = User.where("name LIKE ? AND is_active = ?", "%#{word}%", true)
end
:
end
Planモデル
class Plan < ApplicationRecord
# アソシエーション
belongs_to :user
has_many :plan_details, dependent: :destroy
has_many :plan_tags, dependent: :destroy
has_many :tags, through: :plan_tags
:
# コールバック
before_save :update_plan_search
# 検索用カラム保存
def update_plan_search
self.plan_search = "#{title} #{body} #{plan_details.map(&:title).join(' ')} #{plan_details.map(&:body).join(' ')} #{plan_details.map(&:address).join(' ')}"
end
# 検索方法分岐
def self.looks(range, words)
query = joins(:user)
.left_joins(:tags) # タグがない投稿も内部結合する
.where(is_draft: false) # 下書き投稿を除く
.where(users: { is_active: true }) # 退会ユーザーの投稿を除く
# ,で区切った複数の文字列を検索
if words.present?
conditions = words.split(',').map(&:strip).compact.reject(&:empty?).map do |word|
"(plans.plan_search LIKE '%#{word}%' OR tags.name LIKE '%#{word}%')"
end
query = query.where(conditions.join(" AND "))
end
query.distinct
end
:
end
# コールバック
before_save :update_plan_search
# 検索用カラム保存
def update_plan_search
self.plan_search = "#{title} #{body} #{plan_details.map(&:title).join(' ')} #{plan_details.map(&:body).join(' ')} #{plan_details.map(&:address).join(' ')}"
end
before_save
新規投稿時、投稿更新時にupdate_plan_search
を実行
plansのtitle,body,plan_detailsのtitle,body,addressをplan_searchカラムに保存
query = joins(:user)
.left_joins(:tags) # タグがない投稿も内部結合する
.where(is_draft: false) # 下書き投稿を除く
.where(users: { is_active: true }) # 退会ユーザーの投稿を除く
query
絞り込んだ情報をqueryに代入する
joins
Planモデルと関連付けられた、userテーブルがplanテーブルと結合される
left_joins
Planモデルと関連付けられた、tagsテーブルがplanテーブルと結合される
joinsとleft_joinsの違い
例えばtagsにleft_joinsを使用するのはタグが保存されていない投稿も結合するために使用しています。joinsにしてしまうとタグが一つも保存されていない投稿は除外されてしまうためです
where
userの退会ユーザーの投稿とplansの下書き投稿を除く
# ,で区切った複数の文字列を検索
if words.present?
conditions = words.split(',').map(&:strip).compact.reject(&:empty?).map do |word|
"(plans.plan_search LIKE '%#{word}%' OR tags.name LIKE '%#{word}%')"
end
if words.present?
wordsが存在する場合に処理を実行する
words.split(',').map(&:strip).compact.reject(&:empty?)
words文字列をカンマ,split(',')
を使用して分割し、それぞれのキーワードとします
map(&:strip).compact.reject(&:empty?)
で不要な空白やnilを除去します
.map do |word|
カンマで分割されたそれぞれのキーワードをmapを使用して検索を実行します
"(plans.plan_search LIKE '%#{word}%' OR tags.name LIKE '%#{word}%')"
conditions配列に追加する条件式
それぞれのモデル、カラムに渡されたwordの部分検索が行われます
query = query.where(conditions.join(" AND "))
先ほどの条件式が入ったconditionsをjoin(" AND ") メソッドを使用して、
conditions配列の各要素を連結します。
query.distinct
distinctメソッドを使用して重複するレコードを除外します
ビュー(検索結果)
<%= render 'layouts/search', object: search_path %>
<!--検索対象モデルがUserの時 -->
<% if @range == "User" %>
<% if @users.present? %>
<% @users.each do |user| %>
<%= render 'public/users/user_page', user: user %>
<% end %>
<% else %>
検索にヒットしませんでした
<% end %>
<!--検索対象モデルがPlanの時-->
<% elsif @range == "Plan" %>
<% if @plans.present? %>
<%= render 'public/plans/index', plans: @plans %>
<% else %>
検索にヒットしませんでした
<% end %>
<% else %>
検索にヒットしませんでした
<% end %>
さいごに
一旦実装はできたものの、まだまだ改善点がありそうなので
改善策が見つかれば記事を更新予定です!
6/27更新
・plan title,body plan_detail title,body,addressをplan_searchカラムに保存
・joins(:tags)をleft_joins(:tags)に変更
参照記事
参考にさせていただきました!
ありがとうございました!