Rails 検索機能の実装


##はじめに

閲覧ありがとうございます！
今回は「検索機能」の実装についてまとめていきます。

__ransack__という__Gemfile__を使うと
簡単に実装できるみたいですが、まだ学習しておりません...。

そのため、今回はransackを使わずに、
「検索機能」を実装していこうと思います！
<br>
__ポイント__
・複数のモデルを検索できるようにします。(今回はUserとBook)
・検索方法は完全一致、部分一致、前方一致、後方一致の4つ作ります。
・検索後は、検索結果一覧ページを表示します。

##目次
・開発環境
・前提条件
・ルーティング定義
・検索フォーム作成
・Searchesコントローラー作成
・モデル内に検索方法の分岐定義
・検索結果の一覧表示



##開発環境

・ruby: 2.6.3
・rails: 5.2.4.5
・OS: macOS Catalina ver10.15.7
・Cloud9

##前提条件

■下記モデル実装済み
・Userモデル
・Bookモデル

■device導入
■Bootstrap導入

##ルーティング定義
まずはルーティングから定義していきましょう。

```routes.rb
get "search" => "searches#search"
```

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

パス名は__「search_path」__です。
<br>
余談ですが、パス名は下記2種類の方法で確認することができます。
１：ターミナルにて`rails routes`を実行
２：サーバーを起動し、URL末尾に`/rails/info/routes`を追記

##検索フォーム作成

次に、検索フォームを設置します。
今回はヘッダー内に検索フォームを表示したかった為、部分テンプレートとして作成しています。

```html:_serach.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 btn-primary" %>
    <% end %>
  </div>
<% end %>
```

__■解説__

①`url: search_path`
検索内容を、先ほど作成したルーティングに送信します。
<br>
②`<%= f.text_field :word %>`
検索内容を、`word`としてアクションに送っています。
<br>
③`<%= f.select :range, options_for_select([['User'], ['Book']]) %>`
今回は複数のモデルを検索できるように実装する為、
__Userモデル__と__Bookモデル__を選択できるようにしています。

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

選択した検索手法を`search`としてアクションに送っています。
<br>
##Searchesコントローラーにアクション定義

まず、コントローラーを作成します。

```ターミナル.
rails g controller searches
```

次に、searchアクションを定義します。

```searches.controller.rb
class SearchesController < ApplicationController
  before_action :authenticate_user!

  def search
    @range = params[:range]

    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]`
<br>
__②if文を使い、検索モデル`User`or`Book`で条件分岐させます。__
<br>
__③looksメソッドを使い、検索内容を取得し、変数に代入します。__
検索方法`params[:search]`と、検索ワード`params[:word]`を参照してデータを検索し、
1：インスタンス変数`@users`にUserモデル内での検索結果を代入します。
2：インスタンス変数`@books`にBookモデル内での検索結果を代入します。

これでsearchアクションは完成です。

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

しかし、このままでは検索方法による切替が行われません。
そこで、各モデルに条件分岐を追記します。

```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テーブル内のカラム名です。
適宜、適したカラム名を指定しましょう。

```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テーブル内のカラム名です。
各々、適したカラム名を指定しましょう。<br>
__■解説__

各検索方法を下記のように指定しました。
検索フォーム作成時に記載した内容を見返してみてください。
・完全一致→`perfect_match`
・前方一致→`forward_match`
・後方一致→`backword_match`
・部分一致→`partial_match`

送られてきた`search`によって条件分岐させましょう。

そして、whereメソッドを使いデータベースから該当データを取得し、変数に代入します。

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

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


##検索結果の一覧表示

ビューファイルを作成します。

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

```html:search_result.html.erb
<h2>Results index</h2>

<table class="table table-hover table-inverse">
  <!--検索対象モデルがUserの時 -->
  <% if @range == "User" %>
    <tbody>
      <% @users.each do |user| %>
        <tr>
          <td><%= user.name %></td>
        </tr>
      <% end %>
    </tbody>
  <% else %>
    <!--検索対象モデルがUserではない時(= 検索対象モデルがBookの時) -->
    <tbody>
      <% @books.each do |book| %>
        <tr>
          <td><%= book.title %></td>
          <td><%= book.body %></td>
        </tr>
      <% end %>
    </tbody>
  <% end %>
</table>
```

これで完成です！お疲れ様でした！

##完成デモ

![画面収録 2021-03-13 12.24.02.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1055762/4a971b68-e576-2b78-028d-9488c5405926.gif)


今回の実装はシンプルなものである為、
モデルが3つ以上の時など臨機応変に修正してください！

閲覧ありがとうございました！
誤字、脱字、知識の誤りなどあればコメントいただけると幸いです！