🦔

[rails]タグ検索とキーワード検索を一つのフォームで実装

2023/03/14に公開

今回は検索バーの実装を行なっていきます。
今回の検索バーは、以下の両方をできるように作成しました。

  • キーワード検索
  • タグ検索:
    => User,Event,hashtag,全てで検索できるようにし、
     完全一致のものなのか、含みのものなのか絞れるように作成。

デザインはまだついてませんが、ご了承くださいな。

必要なものを揃えていきましょ!!!

Routing Controller Model View

[ Routing ]

  get "/search", to: "searches#search"

[controller, model]

  • searches controller作成
    他の検索かけるものたちは、出来上がってるものとします!
  • search model (migration 忘れずに)

[ view ]

  • _search.html.erb: 検索入力フォーム用のpartial
    (私はヘッターにこれをrenderで貼り付けました。)
  • search.html.erb: 検索結果表示用view

検索フォーム view

<_search.html.erb>

<div class="serch_form">
  <%= form_with url: searches_search_path,method: :get, local: true do |f| %>
    <%= f.text_field :content %>
    <%= f.select :model, options_for_select({"全てから探す" => nil, "User" => "user", "Post" => "post", "Event" => "event", "Hashtag" => "hashtag"}) %>
    <%= f.select :method,options_for_select({"完全一致"=>"perfect","部分一致"=>"partial"})%>
    <%= f.submit'検索' %>
  <% end %>
</div>

  • form_withのオプション local: true
      今までなんとなく使用していたがこれは何なのか。公式の中にあるもので、今回出てきてるのを以下に載せる。
オプション 説明 デフォルト値
:method HTTPメソッドを指定 POST
:model モデルを指定
:local リモート送信の無効 false

この表で分かる通り、デフォルトはAjax通信で、非同期通信になっています。
これを、local: trueと引数を渡す事で、これが通常のHTTPリクエストとすることができる。

公式ドキュメントはこちらから

  • <%= f.select :model, options_for_select({"全てから探す" => nil, "User" => "user", "Post" => "記事", "Event" => "イベント", "Hashtag" => "ハッシュタグ"}) %>

キーワード検索をできるように知るために、"全てから探す" => nil,を作成。
値としてnilを設定。これにより、ユーザーが「全てから探す」を選択した場合に、値がnilになり、全てのタグから検索されるようになる

search modelに使用するモデルの定義

複数のモデルで同じ検索条件を使用することができるようにするため
search_all_modelsメソッドを作成し定義していく。

複数のモデルで同じ検索条件を使い回すことができ、コードの重複を減らすことができる。
また、Searchモデルに検索関連のメソッドを集約することで、コードの管理性を高めることができる。

class Search < ApplicationRecord
  def self.search_all_models(content, model, method)
    if model.present?
      if model == "user"
        User.search_content(content, method)
      elsif model == "post"
        Post.search_content(content, method)
      elsif model == "event"
        Event.search_content(content, method)
      elsif model == "hashtag"
        Hashtag.search_content(content, method)
      end
    else
      results = []
      results << User.search_content(content, method)
      results << Post.search_content(content, method)
      results << Event.search_content(content, method)
      results << Hashtag.search_content(content, method)
      results.flatten
    end
  end
end

[引数]

  • content(=検索対象となるキーワードや文字列など。)
  • model(= 検索するモデルの種類)
  • method (= 検索方法。 ex. 完全?部分?)

これら3つを先に定義。そして、

  • modelがnilまたは"全てから探す"の場合、各モデルのsearch_contentメソッドを呼び出して、検索結果を配列にまとめて返します。
  • search_contentメソッドは、各モデル に記述できるようにし、モデルごとに検索条件をカスタマイズすることができます。
    => modelが指定されている場合は、そのモデルのsearch_contentメソッドを呼び出して、検索結果を返します。

各モデルに検索メソッドを追加

検索をかけるモデル全てに先ほどの**search_contentメソッド** を記述していきます。

基本型:

def self.メソッド名(引数)
  # 検索処理を記述する
end

今回で言うと、メソッドはモデルに定義したsearch_contentメソッド
引数はcontent(検索対象となるキーワードや文字列),method(検索方法)

def self.search_content(content, method)
  # 検索処理を記述する
end

下記のトグルの中に埋め込んだコードの理解の参考に。

  • method == "perfect": 完全一致
  • method == "partial": 部分一致
  • where.("カラム名 = ?",値)の式を使用してますが.
    さまざまな書き方があるので後日ここに注目した記事を書けたらと思っています。
User model
#検索用
  def self.search_content(content, method)
    if method == "perfect"
      where("first_name = ? OR last_name = ?", content, content)
    elsif method == "partial"
      where("first_name LIKE ? OR last_name LIKE ?", "%#{content}%", "%#{content}%")
    else
      all
    end
  end
Event model
#検索用
  def self.search_content(content, method)
    if method == "perfect"
      where(event_name: content)
    elsif method == "partial"
      where("event_name LIKE ?", "%#{content}%")
    else
      all
    end
  end
Post model
#検索用
  def self.search_content(content, method)
    if method == "perfect"
      where(title: content)
    elsif method == "partial"
      where("title LIKE ?", "%#{content}%")
    else
      all
    end
  end

search controller

class SearchesController < ApplicationController
  def search

    @content = params[:content]
    @model = params[:model] || "nil"
    @method = params[:method]

    # 検索ワードが空の場合は何もしない
    return if @content.blank?

    # Array
    @results = Search.search_all_models(@content, @model, @method)

    render "searches/search"
  end
end
  • return if @content.blank?
    @contentが空の場合は、以降の処理をスキップするようにしている。
    これは、何も入力されていない場合に検索処理を行わないようにするため。
    => 入力されていれば、Search.search_all_modelsメソッドを呼び出して、
    @content、@model、@methodを引数に渡しています。
  • @model = params[:model] || "nil"
    => モデルの選択において、全てから検索でnilを設置したので演算子を活用しこのように記述。
    = params[:model] が存在する場合は、その値を @model 変数に代入し、
     もし params[:model] が nil であれば、"||" 演算子により "nil" が @model 変数に代入.

検索結果表示 view

search.html.erb
<div class="container">
  <% if @model == 'user' %>
    <h3>Users search for "<%= @content %>"</h3>
    <% if @results.present? %>
      <% @results.each do |user| %>
        <div class="card">
          <%= render partial: 'public/users/shared/profile-card', locals: { user: user }  %>
        </div>
      <% end %>
    <% else %>
      <p>検索結果がありません</p>
    <% end %>
  <% elsif @model == 'post' %>
    <h3>Posts search for "<%= @content %>"</h3>
    <% if @results.present? %>
      <%= render partial: 'public/posts/index', locals: { posts: @results }  %>
    <% else %>
      <p>検索結果がありません</p>
    <% end %>
  <% elsif @model == 'event' %>
    <h3>Events search for "<%= @content %>"</h3>
    <% if @results.present? %>
      <%= render partial: 'public/events/index', locals: { events: @results }  %>
    <% else %>
      <p>検索結果がありません</p>
    <% end %>
  <% else @model == 'nil' %>
    <h3>Search for "<%= @content %>"</h3>
    <% if @results.present? %>
      <% @results.each do |result| %>
        <% if result.is_a?(User) %>
          <h4>User</h4>
          <%= render partial: 'public/users/shared/profile-card', locals: { user: result } %>
        <% elsif result.is_a?(Post) %>
          <h4>Post</h4>
          <%= render partial: 'public/posts/index', locals: { posts: [result] } %>
        <% elsif result.is_a?(Event) %>
          <h4>Event</h4>
          <%= render partial: 'public/events/index', locals: { events: [result] } %>
        <% end %>
      <% end %>
    <% else %>
      <p>検索結果がありません</p>
    <% end %>
  <% end %>
</div>


以上!

Discussion