LoginSignup
1
0

,で区切って 複数ワード検索 実装

Last updated at Posted at 2024-06-22

はじめに

ポートフォリオ制作中です!
,で区切って複数のワードを検索できるように実装したためアウトプットします✍️
複数検索のアウトプットなので、検索機能についてはさらっと書いています!
検索機能実装については下記記事がわかりやすかったです!

複数ワード検索はもっといい方法を検討中です🧐
もしあれば教えていただけると幸いです!

6/27追記 記述方法変えました!plan_searchカラムを作成しています

※旅行やデートのプランを共有するアプリなので、投稿機能はPostではなくPlanを使用しています!

開発環境

・ruby: 3.1.2
・Rails: 6.1.7.7
・Cloud9
・bootstrap導入済

ER図

ルーティング

routes.rb
Rails.application.routes.draw do
  scope module: :public do
  :
    get 'search' => 'searches#search'
  end
end

作成されたルーティング↓

search GET  /search(.:format)       public/searches#search

ビュー(検索窓)

_search.html.erb
<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>

コントローラー

seaches_controller.rb
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モデル

user.rb
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モデル

plan.rb
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
plan.rb
  # コールバック
  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カラムに保存

plan.rb
    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の下書き投稿を除く

plan.rb
# ,で区切った複数の文字列を検索
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の部分検索が行われます

plan.rb
query = query.where(conditions.join(" AND "))

先ほどの条件式が入ったconditionsをjoin(" AND ") メソッドを使用して、
conditions配列の各要素を連結します。

plan.rb
query.distinct

distinctメソッドを使用して重複するレコードを除外します

ビュー(検索結果)

search.html.erb
<%= 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)に変更

参照記事

参考にさせていただきました!
ありがとうございました!

1
0
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
1
0