【初学者必見】Railsで「複数のカラムからの」検索機能を実装してみた。

### はじめに
あけましておめでとうございます🐄
Railsで定番の検索機能。
検索機能に関しての記事はQiita上でもたくさんあるが、どれも**一つのテーブル**の中の**一つのカラム**から検索する方法ばかり。（あとは、もう古くなったform_tagが使われていたり）
でも実際は**一つのテーブル**の中の**複数のカラム**から検索したいケースもあるはず。
例えば、

```ruby:db/schema.rb
ActiveRecord::Schema.define(version: 2020_12_18_025546) do

  create_table "agents", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "last_name", null: false
    t.string "first_name", null: false
    t.string "last_name_kana", null: false
    t.string "first_name_kana", null: false
    t.string "company_name", null: false
    t.string "company_location", null: false
    t.bigint "user_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["user_id"], name: "index_agents_on_user_id"
  end
end
```
テーブルはこのような感じ↓
![スクリーンショット 2021-01-05 16.53.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/914577/807157e1-dc5b-3dd8-acd5-39f742d306b2.png)

上記のagentsテーブルは「氏」と「名」と「氏のフリガナ」と「名のフリガナ」が独立しているので、agentを特定する際に一つのカラムから検索することはできません。
ましてやこの場合は一つの検索フォームから会社名(company_name)だったり会社の所在地(company_location)からagentを特定できればもっと便利になるはずです。

# 順序
1. Viewに検索フォームを配置する
2. searchアクションのルーテインングを設定する
3. 検索するためのメソッド(searchメソッド)をモデルに定義する  <font color="Red">←ココ重要</font>
4. searchアクションをコントローラーに定義する
5. 検索結果画面のViewを作成する

では早速やっていきましょう。

## 1. Viewに検索フォームを配置する
検索フォームを作りたいViewの中に検索フォームを書いていきます。

```erb:app/views/agents/index.html.erb
<%= form_with(url: search_agents_path, local: true, method: :get, class: "field has-addons") do |form| %>
  <%= form.text_field :keyword, placeholder: "営業マンを検索する", class: "input" %>
  <%= form.submit "検索", class: "button is-info" %>
<% end %>
```
※ class名はCSSフレイムワークのBULMAを使っています。
↓以下のように検索ボックスが作れました！
![スクリーンショット 2021-01-05 18.17.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/914577/ae20b6ee-e238-16ae-d25c-fbf45ad93850.png)

## 2. searchアクションのルーテインングを設定する
searchアクションはRailsのデフォルトである7つのアクション(index, show, new, create, edit, update, destroy)以外なので”search”と言う名前でアクションを定義します。

自分で定義したアクションには**member**または**collection**というオプションを使う必要があります。
詳細は [ココ](https://qiita.com/j-sunaga/items/cd50726e311190f0787c)で確認してみてください。
***今回は:idを関連付ける必要が無いので、collectionを使います。***

```ruby:config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: 'homes#top'
  resources :agents, only: [:index, :show, :new, :create] do
    resources :reviews, only: [:index, :create, :edit, :update, :destroy]
    collection do     #←ココ
      get 'search'    #←ココ
    end               #←ココ
  end
end
```

## 3. 検索するためのメソッド(searchメソッド)をモデルに定義する  <font color="Red">ココ重要</font>
**where**メソッドと**Like句**を使います。

> ### whereメソッドとは
モデルが使用できる、ActiveRecordメソッドの1つ。
**モデル名.where(条件)**のの形で引数部分に条件を指定すること、テーブル内の「条件に一致したレコードのインスタンス」を配列の形で取得できます。引数の条件には、「検索対象となるカラム」を必ず含めなければなりません。


> ### Like句とは
曖昧な文字列で検索しても、テーブルの対象のカラムから検索したいもの探し出せるようにするために使用するSQLのクエリです。whereメソッドの条件の中で使います。


一つのカラムから検索する場合は、以下のような構文をモデル内で用います。
```モデル名.where('探し出したいカラム名 LIKE(?)', "%#{search}%")```

複数のカラムから検索したい場合は、(例として3つのカラムから探したい時)
```モデル名.where('探し出したいカラム名 LIKE(?) OR 探し出したいカラム名 LIKE(?) OR 探し出したいカラム名 LIKE(?)', "%#{search}%", "%#{search}%", "%#{search}%")```
のような構文になります。
**「探し出したいカラム名 LIKE(?)」を「OR」で繋いで、カラムの数の分だけ「"%#{search}%"」を書くことによって複数のカラムから検索をすることができます。**

具体的には、以下のようにsearchメソッドをモデル内で定義しました。

```ruby:app/models/agent.rb
class Agent < ApplicationRecord
  with_options presence: true do
    validates :user_id
    validates :company_name
    validates :company_location

    with_options format: { with: /\A(?:\p{Hiragana}|\p{Katakana}|[ー－]|[一-龠々])+\z/ } do
      validates :first_name
      validates :last_name
    end
    with_options format: { with: /\A[ァ-ヶー－]+\z/ } do
      validates :last_name_kana
      validates :first_name_kana
    end
  end

  def avg_score
    if reviews.empty?
      0.0
    else
      reviews.average(:score).round(1).to_f
    end
  end

  def review_score_percentage
    if reviews.empty?
      0.0
    else
      reviews.average(:score).round(1).to_f / 5 * 100
    end
  end

#↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ココ
  def self.search(search)
    if search != ""
      Agent.where('last_name LIKE(?) OR first_name LIKE(?) OR last_name_kana LIKE(?) OR first_name_kana LIKE(?) OR company_name LIKE(?) OR company_location LIKE(?)', "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%")
    else
      Agent.all
    end
  end
#↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ココ

  has_many :reviews, dependent: :destroy
  belongs_to :user
end

```
if文を用いて、検索結果が一つも無い場合は全てのagentを出力するようにしました。

## 4. searchアクションをコントローラーに定義する

以下のようにsearchアクションをコントローラーに追加します。

```ruby:app/models/agent.rb
def search
  @agents = Agent.search(params[:keyword])
end
```
検索フォームから送られて来ているパラメーターである**:keyword**を引数にします。

## 5. 検索結果画面のViewを作成する
新しくsearch.html.erbを作成することで、検索結果を表示するviewを作成します。
コントローラーで定義した＠agentsをeach文を用いて全て表示していきます。

```erb:app/views/agents/search.html.erb
<div class="section pb-0">
  <div class="container">
    <div class="columns is-centered">
      <div class="column is-6">
        <%= form_with(url: search_agents_path, local: true, method: :get, class: "field has-addons") do |form| %>
          <%= form.text_field :keyword, placeholder: "営業マンを検索する", class: "input" %>
          <%= form.submit "検索", class: "button is-info" %>
        <% end %>
        <div>
          次の検索結果を表示しています:<span class="has-text-weight-bold is-size-4"> <%= params[:keyword] %></span>
        </div>
      </div>
    </div>
  </div>
</div>

<section class="section">
  <div class="container">
    <div class="columns is-centered">
      <div class="column is-5">
        <% @agents.each do |agent| %>
          <div class="card mb-6">
            <header class="card-header">
              <p class="card-header-title">
                <%= agent.last_name %> <%= agent.first_name %>
                <span class="has-text-weight-light is-italic is-size-7">(<%= agent.last_name_kana %> <%= agent.first_name_kana %>)</span>
              </p>
            </header>
            <div class="card-content">
              所属会社： <%= agent.company_name %><br>
              場所： <%= agent.company_location %><br>
              <div class="content">
                <div class="content average-score">
                  <div class="star-rating mb-2">
                    <div class="star-rating-front" style="width:  <%= agent.review_score_percentage %>%">★★★★★</div>
                    <div class="star-rating-back">★★★★★</div>
                  </div>
                  <div class="average-score-display ml-3 pt-2">
                  <%= agent.avg_score %>点（<%= agent.reviews.count %>件のレビュー）
                  </div>
                </div>
              </div>
            </div>
            <footer class="card-footer">
            <%= link_to agent_reviews_path(agent), class: "button card-footer-item" do %>
            レビューを見る
            <% end %>
            <%= link_to agent_path(agent), class: "button card-footer-item" do %>
            レビューを書く
            <% end %>
            </footer>
          </div>
        <% end %>
        <div class="is-centered">
          <%= link_to new_agent_path, class: "button is-primary  mt-3" do %>
            新しい営業マンを追加する
          <% end %>
        </div>
    </div>
  </div>
</div>
```

※ class名はCSSフレイムワークのBULMAを使っています。


↓以下のような検索結果画面ができました。
これは検索ボックスに「木」と入力して出た検索結果です。
![スクリーンショット 2021-01-05 23.26.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/914577/1bbe0d8a-1a19-02fc-bab8-00e4ef398f11.png)

# 最後に
自分がRailsで検索機能を実装していてあったら良いなと思うものを書いてみました。
いかがでしたでしょうか。
間違っている点やわかりにくい点があったらお気軽に教えてください！
では次回の投稿でお会いしましょう。