Railsで検索バーを設ける〜複数テーブルを結合させて検索を行う


# はじめに
今回は、投稿データ（Post）を検索バーでキーワード検索を行い、表示させる。また、複数のテーブルを結合させて、その範囲で検索できるように実装していきます。

# 完成イメージ
※後日掲載予定


# 環境
MacOS 10.15.7
ruby 2.6.5
Ruby on Rails 6.0.0

# 執筆者の状況

###テーブル
![searchFunction1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683220/f2c6251a-407e-f77c-a4ea-1ea784ca7072.png)

利用者を「User」、投稿を「Post」、画像を「Image」、都道府県を「Prefecture」（seedデータ）としてテーブルを作成しております。
（テーブルの中身は投稿用に簡略化してます）

リレーションは次の通りです。

```ruby:user.rb
has_many :posts, dependent: :destory
```

```ruby:post.rb
belongs_to :user
belongs_to :prefecture

has_many :images, dependent: :destory


accepts_nested_attributes_for :images, allow_destroy: true
```

```ruby:image.rb
belongs_to :post

mount_uploader :image, ImageUploader
```

```ruby:prefecture.rb
has_many :posts
```

このような形になっています。userとimageも紹介してはいるのですが、今回は関わりが無いので説明を省略します。
prefectureはpost（投稿）を複数持っているので、「has_many :posts」としています。
postはprefectureに属しているので、「belongs_to :prefecture」になります。

# 作業していきます!

###①検索バーの設定〜機能実装

####①-1 検索バーの作成
このようにして検索バーを作成します。

```erb:index.html.erb
<%= form_with(url: search_posts_path, local: true, method: :get, class: "rootpageSearchForm") do |form| %>
  <%= form.text_field :keyword, placeholder: "キーワードを入力", id: 'Search_Form', class: "rootpageSearchForm__content" %>
  <%= form.submit "検索", class: "rootpageSearchForm__bottum" %>
<% end %>
```
```scss:index.scss
.rootpageSearchForm {
  width: auto;
  &__content {
    width: 40vw;
    height: 30px;
    color: #000000;
    border-radius: 5px;
    border-color: #008bbb;
  }
  &__bottum {
    margin-left: 0.5vw;
    height: 30px;
    width: 150px;
    border-radius: 20px;
    background-color: #87cefa;
    border: none;
    box-shadow: 0 0 8px gray;
    color: #ffffff;
    -webkit-transition: all 0.3s ease;
    -moz-transition: all 0.3s ease;
    -o-transition: all 0.3s ease;
    transition: all 0.3s ease;
  }
  .rootpageSearchForm__bottum:hover {
    background-color: #00bfff;
  }
}
```

以上となります。完成イメージはこんな感じです。
![searchForm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683220/a7feab78-b2bd-f610-618f-6fe6f8e305c9.png)

####①-2 searchアクションの作成
routes.rbにsearchアクションを新規で定義します。今回はpostアクションにネストさせた形で定義します。
以下のように定義します。

```ruby:routes.rb
Rails.application.routes.draw do
  resources :posts do
    collection do
      get 'search'
    end
  end
  下記一部省略...
end
```
今回、検索のキーワードで該当する投稿を一覧として表示させるので、idが不要なため

```ruby
collection do
  get 'search'
end
```
と定義します。

仮にidを必要とする場合（投稿の詳細ページなど）はcollectionではなく、memberを用いて定義します。
collectionとmemberの違いはURLにidが付くか、付かないかの違いです。

collection　・・・　ルーティングにidが付かない。
member ・・・　ルーティングにidが付く。

###①-3 searchメソッドをpostモデルに定義

```ruby
class Post < ApplicationRecord
  ...上記一部省略
  def self.search(search)
    if search != ""
      Post.where('content LIKE(?) OR title LIKE(?), "%#{search}%", "%#{search}%")
    else
      Post.all
    end
  end
  下記一部省略...
end
```

`whereメソッド`を使って、引数に記述した条件を基に、テーブル内の「条件に一致したレコードのインスタンス」を配列の形で取得。

`LIKE句`を使い、曖昧な文字列を検索するようにしています。

| 文字列 | 意味 |
|:-----------:|:-----------:|
| ％ | 任意の文字列（空白文字列含む）| 
| _ | 任意の１文字      | 


実行例

| 実行例 | 意味 |
|:-----------:|:-----------:|
| where('title LIKE(?)', "a%") | aから始まるタイトル| 
| where('title LIKE(?)', "%b") | bで終わるタイトル  | 
| where('title LIKE(?)', "d_") | cが含まれるタイトル| 
| where('title LIKE(?)', "%b") | dで始まる2文字のタイトル  | 
| where('title LIKE(?)', "_e") | eで終わる2文字のタイトル  | 


引数searchは、検索フォームから送信されたパラメーターが入る。
そのため、`if search != ""`と記述し、検索フォームに何か値が入力されていた場合を条件とする。

検索フォームに何も入力されない（空の状態）で検索ボタンが押されると、引数に渡されるsearchの中身が空になるので、その場合の処理をelse文内で記述。
今回は`Post.all`として、全ての投稿を取得して表示させます。

__テーブルとのやりとりに関するメソッドはモデルに置くのが基本！__

####①-4 コントローラーを記述

posts_controller.rbにsearchアクションを定義します。

```ruby:posts_controller.rb
class PostsController < ApplicationController
  ...上記一部省略...
  def search
    @posts = Post.search(params[:keyword]).order(created_at: :desc)
  end
  ...下記一部省略...
```
searchメソッドの引数に`params[:keyword]`と記述して、検索結果を渡します。

また、検索結果を投稿が新しい順で並べるため、`order(created_at: desc)`と記述してます。

####①-5 search.html.erbを作って、検索結果を表示させる
あとは、検索結果画面を表示させるsearch.html.erbを作って完了です。

```ruby:search.html.erb
<div class="wrapper">
  <% @posts.each do |post| %>
    <div class='contents row'>
    ...以下省略...
  <% end %>
</div>
```

####①-6 完成！
これで、検索バー並びにキーワード検索により、検索結果を表示できるようになりました。
ここまでの設定では、Postテーブルの「content(投稿文）」と「title(投稿タイトル）」が検索対象になっている状態です。


###② 他のテーブルを結合させて、検索対象に追加する。

今回、都道府県名でも検索できるように、検索対象にPrefectureテーブルの「name(都道府県名)」を追加していきたいと思います。

postモデルに記述を追加していきます。

```ruby
class Post < ApplicationRecord
  ...上記一部省略
  def self.search(search)
    if search != ""
      Post.joins(:prefecture).where('content LIKE(?) OR title LIKE(?) OR prefectures.name LIKE(?)', "%#{search}%", "%#{search}%", "%#{search}%")
    else
      Post.all
    end
  end
  下記一部省略...
end
```

prefectureテーブルも検索対象にするために、`joins(:prefecture)`として追加しています。
`joinメソッド`は`複数のテーブルを１つに結合したいときに使うメソッド`です。
今回の場合は、postテーブルとprefectureテーブルを結合させて、１つのテーブルとして扱います。


whereメソッドの引数の部分には、`OR prefectures.name LIKE(?)'`を追加して都道府県名を検索対象に指定しています。この分の`"%#{search}%"`も１つ追加して完了です。

#最後に
初学者のため、間違い等ありましたらご指摘いただけますと幸いです。
最後までご覧いただきありがとうございました。