検索機能

##searchアクション
7つのアクション以外のsearchアクションという命名でアクションを定義します。
7つのアクション以外でルーティングを定義する方法は、collectionとmemberというものがあります。
【例】今回はモデル名をTweet、Comment、Userがあるとします。

```app/views/tweets/index.html.erb
<%= form_with(url: search_tweets_path, local: true, method: :get, class: "XXXX") do |form| %>
  <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "YYYY" %>
  <%= form.submit "検索", class: "ZZZZ" %>
<% end %>
〜省略〜
```

検索の入力欄とボタンには、フォームを使います。
##collectionとmember
collectionはルーティングに:idがつかない、memberは:idがつく。という違いがあります。
```

【例】collectionで定義した場合

```
Rails.application.routes.draw do
  resources :tweets do
    collection do
      get 'search'
    end
  end
end
```

【例】memberで定義した場合

```
Rails.application.routes.draw do
  resources :tweets do
    member do
      get 'search'
    end
  end
end
```
今回の検索機能の場合、詳細ページのような:idを指定して特定のページにいかない使用にするので、collectionを使用してルーティングを設定してみます。
【例】

```config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: 'tweets#index'
  resources :tweets do
    resources :comments, only: :create
    collection do
      get 'search'
    end
  end
  resources :users, only: :show
end
```
##searchメソッドを定義
モデルにテーブルから検索するためのsearchメソッド定義していきます。
検索したキーワードが含まれている投稿を取得するために、whereメソッドとLIKE句を利用します。
##whereメソッド
モデル.where(条件)のように引数部分に条件を指定することで、テーブル内の条件に一致したレコードのインスタンスを配列の形で取得できます。
引数の条件には、「検索対象となるカラム」を必ず含めて、条件式を記述します。
【例】

```
モデル.where('検索対象となるカラムを含む条件式')
```
条件式には'カラム名 > 5'やキーバリューの形でカラム名: 値などの記述が可能です。
対象のカラムに条件と一致する値を持つレコードを全て取得します。
whereメソッドを連続して記述することによって、複数の条件に一致したレコードを取得することもできます。
【例】

```
Tweet.where('id < 5')
```
```
Tweet.where('id < 5').where(user_id: 1)
```
上記の記述でも取得できます。
##LIKE句
検索機能にはこのwhereメソッドに加えてLIKE句を使用します。
LIKE句は、曖昧な文字列の検索ができるもので、whereメソッドと一緒に使います。
例えば、1文字目に'a'という文字列が入ったデータや最後の文字に'b'が入っているデータ、文字列の途中に'c'が入ったデータなどを検索したい時に、曖昧文字列というものを使って検索できます。
##曖昧文字列
| 文字列 | 意味 |
|:-----------|------------:|:------------:|
| % | 任意の文字列(空白文字列含む) |
| _ | 任意の1文字 |
##実行サンプル
| 実行例 | 詳細 |
|:-----------|------------:|:------------:|
| where('title LIKE(?)', "a%") | aから始まるタイトル |
| where('title LIKE(?)', "%b") | bで終わるタイトル |
| where('title LIKE(?)', "%c%") | cが含まれるタイトル |
| where('title LIKE(?)', "d_") | dで始まる2文字のタイトル |
| where('title LIKE(?)', "_e") | eで終わる2文字のタイトル |
【例】

```app/models/tweet.rb
class Tweet < ApplicationRecord
  validates :text, presence: true
  belongs_to :user
  has_many :comments

  def self.search(search)
    return Tweet.all unless search
    Tweet.where('text LIKE(?)', "%#{search}%")
  end
end
```
もし、引数で渡されるsearchの中身に何もなければ全ての投稿を取得できるようにしています。
そのため、unless文を使ってreturn Tweet.all unless searchと記述しています。
unless文は条件式が偽である場合にだけ処理を実行するという意味です。

```
実行する処理  unless 条件式
```
ちなみにif文でも実行できます。

```app/models/tweet.rb
class Tweet < ApplicationRecord
  validates :text, presence: true
  belongs_to :user
  has_many :comments

  def self.search(search)
    if search
      Tweet.where('text LIKE(?)', "%#{search}%")
    else
      Tweet.all
    end
  end
end
```
ですがunless文で記述したほうがコードがすっきりしますね。
##searchアクションをコントローラーに定義
コントローラーにsearchアクションを定義します。

```app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  before_action :set_tweet, only: [:edit, :show]
  before_action :move_to_index, except: [:index, :show, :search]

  def index
    @tweets = Tweet.includes(:user).order("created_at DESC").page(params[:page]).per(5)
  end

〜省略〜

 def search
    @tweets = Tweet.search(params[:keyword])
  end

 def set_tweet
    @tweet = Tweet.find(params[:id])
  end

  def move_to_index
    redirect_to action: :index unless user_signed_in?
  end
end
```
Tweetモデルに書いたsearchメソッドを呼び出しています。seachメソッドの引数にparams[:keyword]と記述して、検索結果を渡しています。
また、未ログイン状態にトップページへリダイレクトされてしまうことを回避するため、before_actionのexceptオプションに:searchを追加しています。
##検索結果画面のビューを作成
search.html.erbを作成します。
【例】

```app/views/tweets/search.html.erb
<%= form_with(url: search_tweets_path, local: true, method: :get, class: "XXXX") do |form| %>
  <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "YYYY" %>
  <%= form.submit "検索", class: "ZZZZ" %>
<% end %>
<div class="AAAA">
  <% @tweets.each do |tweet| %>
    <%= render partial: "tweet", locals: { tweet: tweet } %>
  <% end %>
</div>
```
今回はすでにrenderしてあることにしています。