🗽

Rails 検索機能

2023/04/29に公開

検索機能の追加

ヘッダー下部に検索窓を設置する

gemfileを使うと簡単に実装できるようだが、今回は使わない方法で実装!

実装する機能

コントローラ

  • searchesコントローラ追加
  • searchアクション追加(用途:検索を行う)

ビュー

  • ログインしている場合に限り、ヘッダーに検索窓・検索ボタンを設置すること
  • 検索結果表示画面を作成し、検索結果を表示すること
  • 検索対象(ユーザーか投稿か)の選択かをプルダウンメニューで選択できること
  • 完全一致, 前方一致, 後方一致, 部分一致の検索手法をプルダウンメニューで選択できること

ルーティング定義

routes.rb

get "search" => "searches#search"

検索ボタンが押された時、Searchesコントローラーのsearchアクションが実行されるように定義。

検索フォーム作成

ヘッダーの真下に検索フォームを表示したかったため、部分テンプレートとして作成。

_search.html.erb

<% if user_signed_in? %>
<div class="search_form">
  <%= form_with url: search_path, local: true, method: :get do |f| %>
      <%= f.text_field :word %>
      <%= f.select :range, options_for_select([['User'], ['Book']]) %>
      <%= f.select :search, options_for_select([["完全一致","perfect_match"], ["前方一致","forward_match"],  ["後方一致","backward_match"], ["部分一致","partial_match"]]) %>
      <%= f.submit "検索", class: "btn-sm btn-primary" %>
    <% end %>
  </div>
<% end %>

~解説~
url: search_path
検索内容を、先ほど作成したルーティングに送信。

<%= f.text_field :word %>
検索内容を、wordとしてアクションに送る。

<%= f.select :range, options_for_select([['User'], ['Book']]) %>
複数のモデルを検索できるように実装する為、
UserモデルとBookモデルを選択できるようにしている。
選択したモデルをrangeとしてアクションに送っている。

<%= f.select :search, options_for_select([["完全一致","perfect_match"]以下略]) %>
検索手法を定義。
今回のようにカンマ区切りで複数定義することができる。
選択した検索手法をsearchとしてアクションに送っている。

application.html.erbに

<%= render 'partial/search' %>

コントローラー

rails g controller searches

searchアクションを定義。

searches.controller

class SearchesController < ApplicationController
  before_action :authenticate_user!

  def search
    @range = params[:range]
    @word = params[:word]

    if @range == "User"
      @users = User.looks(params[:search], params[:word])
    else
      @books = Book.looks(params[:search], params[:word])
    end
  end
end

〜解説〜

このコードで検索フォームからの情報を受け取っている。
検索モデル→params[:range]
検索方法→params[:search]
検索ワード→params[:word]

looksメソッドを使い、検索内容を取得し、変数に代入!
検索方法params[:search]と、検索ワードparams[:word]を参照してデータを検索し、
1:インスタンス変数@usersにUserモデル内での検索結果を代入。
2:インスタンス変数@booksにBookモデル内での検索結果を代入。

モデル内に検索方法の分岐定義

このままでは検索方法による切替が行われないので、
各モデルに条件分岐を追記。

user.rb

# 検索方法分岐
  def self.looks(search, word)
    if search == "perfect_match"
      @user = User.where("name LIKE?", "#{word}")
    elsif search == "forward_match"
      @user = User.where("name LIKE?","#{word}%")
    elsif search == "backward_match"
      @user = User.where("name LIKE?","%#{word}")
    elsif search == "partial_match"
      @user = User.where("name LIKE?","%#{word}%")
    else
      @user = User.all
    end
  end

nameは検索対象であるusersテーブル内のカラム名

LIKEによるあいまい検索

SQLにはLIKE句を使ったあいまい検索の構文がある。

〜 WHERE 列名 LIKE '%検索値%'
〜 WHERE 列名 LIKE '検索値_'
% → 0文字以上の任意の文字列
_ → 任意の1文字

同様のことをRailsで実現するために、whereメソッドにSQLのLIKE句で検索条件を記述!

https://qiita.com/seri1234/items/765423c2c46ca4114da0

book.rb

# 検索方法分岐
  def self.looks(search, word)
    if search == "perfect_match"
      @book = Book.where("title LIKE?","#{word}")
    elsif search == "forward_match"
      @book = Book.where("title LIKE?","#{word}%")
    elsif search == "backward_match"
      @book = Book.where("title LIKE?","%#{word}")
    elsif search == "partial_match"
      @book = Book.where("title LIKE?","%#{word}%")
    else
      @book = Book.all
    end
  end

titleは検索対象であるbooksテーブル内のカラム名

~解説~
検索フォーム作成時に記載した内容を見返してみる。

  • 完全一致→perfect_match
  • 前方一致→forward_match
  • 後方一致→backword_match
  • 部分一致→partial_match

送られてきたsearchによって条件分岐させて、
whereメソッドを使いデータベースから該当データを取得し、変数に代入する。

完全一致以外の検索方法は、
#{word}の前後(もしくは両方に)、_%_を追記することで定義することができる。

これにより、検索方法毎に適した検索が行われるようになった!

検索結果の一覧表示

searchesコントローラ内で、検索結果を代入したインスタンス変数(@usersと@books)に対し、each文をつかって1つずつ取り出していく。

<table class="table table-hover table-inverse">
  <!--検索対象モデルがUserの時 -->
  <% if @range == "User" %>
  <h2>Users search for "<%= @word %>"</h2>
    <thead>
      <tr>
        <th>image</th>
        <th>name</th>
        <th colspan="3"></th>
      </tr>
    </thead>
    <tbody>
      <% @users.each do |user| %>
        <tr>
          <td><%= image_tag user.get_profile_image(50, 50) %></td>
          <td><%= user.name %></td>
        </tr>
      <% end %>
    </tbody>
  <% else %>
    <!--検索対象モデルがUserではない時(= 検索対象モデルがBookの時) -->
    <thead>
      <tr>
        <th></th>
        <th>Title</th>
        <th>opinion</th>

      </tr>
    <tbody>
    <h2>Books search for "<%= @word %>"</h2>
    <% @books.each do |book| %>
      <tr>
        <td>
          <%= link_to user_path(book.user) do %>
            <%= image_tag book.user.get_profile_image(50, 50) %>
          <% end %>
        </td>
        <td><%= link_to book.title , book_path(book.id) %></td>
        <td><%= book.body %></td>
        </tr>
      <% end %>
    </tbody>
  <% end %>
</table>

参照
https://qiita.com/hapiblog2020/items/6c2cef49df5616da9ae3

部分テンプレートなど使いもっとコード量を減らせる気がするが、
一旦完成!

Discussion