railsで検索機能を実装する方法（複数カラムも対応)

# 開発環境
Mac OS Catalina 10.15.7
ruby 2.6系
rails 6.0系

# 前提
前提として、現在私が作っている、記事を投稿できるアプリを例に説明します。
私はモデル名はpostモデルとしています。(参考にする際は、自分用に置き換えてください。)


# ルーティング設定

まずはルーティングを設定します。
今回はsearchアクションという、基本アクション(index,new,create,show,edit,update,destroy)以外のアクションを定義する必要があるため、collectionを使い設定します。

```routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: "posts#index"
  resources :posts do
    resources :comments, only: [:create, :destroy]
    collection do
      get 'search'
    end
  end
  resources :users, only: :show
end
```

collectionを使うと、ルーティングのURLと実行されるコントローラーを任意にカスタムできます。
また、ルーティングにidを含めたいときはmemberが使えます。

```routes.rb
member do
  get 'search'
end
```

単純にcollectionのところをmemberにするだけです。

今回の私の例ではidは必要ないので、collectionを使っています。

# 検索フォーム作成

ルーティングができたので、ビューに配置する検索フォームを作っていきます。

```index.html.erb
<%= form_with(url: search_posts_path, local: true, method: :get, class: "search-form") do |form| %>
  <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "search-input" %>
  <%= form.submit "検索", class: "search-btn" %>
<% end %>
```
いろいろ書き方があるとは思いますが、自分はこんな感じで習いました。

ポイントは先ほどのルーティング設定で生成したパス(search_posts_path)が入っていること(パスの名前はコンソールで確認しましょう）、それとmethod: :getを忘れないことぐらいですかね。

加えてform_withのtext_fieldのキー名はkeywordとしました。


# モデルに検索の処理を記述

それでは実際に検索する際の処理をモデルに書いていきます。
このとき、コントローラーに書いてしまいたくなる気持ちはわかりますが、テーブルとやり取りをするようなメソッドを定義する際は極力モデルに書くようにしましょう。
def self.search以下(最下部のendは除く)が今回記述した内容です。

```post.rb
class Post < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :category
  belongs_to :user
  has_one_attached :image
  has_many :comments, dependent: :destroy

  with_options presence: true do
   validates :image
   validates :title
   validates :category_id, numericality: { other_than: 1 , message: "は--以外から選んでください"} 
  end

  def self.search(search)
    if search != ""
      Post.where(['title LIKE(?) OR explanation LIKE(?) OR animal_name LIKE(?)', "%#{search}%", "%#{search}%", "%#{search}%"])
    else
      Post.includes(:user).order('created_at DESC')
    end
  end
end
```

基本的な書き方としては

```
オブジェクト名.where('検索をかけたいカラム名　LIKE(?)', "%#{search}%")
```
複数のカラムで検索をかけたいときは

```
オブジェクト名.where(['検索をかけたいカラム名　LIKE(?)　OR 検索をかけたいカラム名　LIKE(?)', "%#{search}%", "%#{search}%"])
```

みたいな感じで実行できます。
自分の場合は3つのカラムで検索をかけたいので上記のような記述になります。

また、if文で検索欄が空だった場合には、postの一覧が表示するように記述しました。


# コントローラー記述

次にコントローラーを記述します。

```posts_controller.rb
def search
  @posts = Post.search(params[:keyword])
end
```

先ほどモデルに定義したsearchメソッドを使っています。
引数のparamsの中の[:keyword]は、検索フォームを作った際に設定したもので、text_fieldのキー名です。


# ビュー作成

最後にsearchアクションが参照するビューを作成します。
別に作らなくても、renderでindexページに飛ばしたりもできると思います。

```search.html.erb
<%= render "shared/header"%>

<%= form_with(url: search_posts_path, local: true, method: :get, class: "search-form") do |form| %>
  <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "search-input" %>
  <%= form.submit "検索", class: "search-btn" %>
<% end %>

<% if @posts == [] %>
<h2 class = "seach-result">検索結果はありません</h2>
<% else %>
<div class = "post-contents">
  <ul class = "post-list">
    <% @posts.each do |post| %>
    <li class = "list">
      <%= link_to post_path(post.id) do %>
      <%= image_tag post.image, class: "post-img" %>
      <h3 class='post-title'><%= post.title %></h3>
      <p class = "root-show">〜クリックして詳細を見る〜</p>
      <% end %>
    </li>
    <% end %>
  </ul>
</div>
<% end %>

<%= link_to new_post_path, class: 'post-btn' do %>
  <span class='post-btn-text'>投稿する</span>
<% end %>

<%= render  "shared/footer" %>
```

以上がrailsにおける検索機能の実装方法です。
参考になれば幸いです。