##searchアクション
7つのアクション以外のsearchアクションという命名でアクションを定義します。
7つのアクション以外でルーティングを定義する方法は、collectionとmemberというものがあります。
【例】今回はモデル名をTweet、Comment、Userがあるとします。
<%= 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文字のタイトル |
【例】
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文でも実行できます。
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アクションを定義します。
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を作成します。
【例】
<%= 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してあることにしています。