検索機能の作り方(覚書)
検索機能はこれからも使うと思うので、覚書です。
(1)ビューを作る
app/views/tweets/ index.html.erb
#検索フォーム
<%= form_with(url: search_tweets_path, local: true, method: :get, class: "search-form") do |form| %>
<%= form.text_field :keyword, placeholder: "投稿を検索する", class: "search-input" %>
<%= form.submit "検索", class: "search-btn" %>
<% end %>
#検索結果後の画面 (4)のコントローラーで定義したキーワード入り@tweetを引っ張ってきています
<div class="contents row">
<% @tweets.each do |tweet| %>
<%= render partial: "tweet", locals: { tweet: tweet } %>
<% end %>
</div>
(2)searchアクションのルーティングを設定する
collectionとmemberについて
両者の違いは、
- 「member」は、「resources以外のメソッドを追加したい」且つ「id情報を伴うURIを生成したい」時に使用。
- 「collection」は、「resources以外のメソッドを追加したい」且つ「id情報を伴わないURIを生成したい」時に使用。
アクション | rails routesで見たときの違い | 使用例 | 指定可能なHTTP動詞 |
---|---|---|---|
member | :idが付く | 購入機能 (どの商品が選択され購入されたのか特定が必要なのでid指定の必要あり) |
get 、patch 、put 、post 、delete
|
collection | :idが付かない | 検索機能 (検索するだけのアクションなのでid指定する必要なし) |
GETリクエスト+/photos/searchなどの (idを伴わない) パスを認識 |
今回はコレクションで。
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
(3)モデルに検索処理を行うメソッドを定義
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
今回はsearchメソッドとする。
if search != ""
とすることで、検索フォームに何か値が入力されていた場合を条件としている。検索したキーワードが含まれている投稿を取得するために、whereメソッドとLIKE句を利用する。
whereメソッド
モデルが使用できるActiveRecordメソッドの一つ。
モデル.where(条件)のように、引数部分に条件を指定することで、テーブル内の「条件に一致したレコードのインスタンス」を配列の形で取得できる。
条件部分には「検索対象となるカラム」を必ず含める。
whereメソッド
モデル名.where('検索対象となるカラムを含む条件式')
- whereメソッドを連続して記述することによって複数の条件に一致したレコードを取得することもできる。
例
Tweet.where('id < 3').where(user_id: 1)
LIKE句
- 曖昧な文字列を検索する時に使用(whereメソッドとセットで使う)
曖昧文字列について
文字列 | 意味 |
---|---|
% | 任意の文字列(空白文字列含む) |
_ | 任意の一文字 |
実行サンプル
文字列 | 意味 |
---|---|
where('title' LIKE(?), "a%") | aから始まるタイトル |
where('title' LIKE(?), "%a") | aで終わるタイトル |
where('title' LIKE(?), "%a%") | aが含まれるタイトル |
where('title' LIKE(?), "a_") | aで始まる2文字のタイトル |
where('title' LIKE(?), "_a") | aで終わる2文字のタイトル |
- なお、モデルが肥大化してしまうのを防ぐためにサービスクラスを設定して記載することも可能。
(4)コントローラーに定義
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")
end
(省略)
def search
@tweets = Tweet.search(params[:keyword])
end
private
def tweet_params
params.require(:tweet).permit(:image, :text).merge(user_id: current_user.id)
end
def set_tweet
@tweet = Tweet.find(params[:id])
end
def move_to_index
unless user_signed_in?
redirect_to action: :index
end
end
end
searchメソッドの引数に、
params[:keyword]
と記述して、検索結果を渡している。未ログイン時に検索するとトップページへリダイレクトしてしまうため、
before_action
のexceptオプション
に:search
を追加している。(補足)
exceptオプション
とは、before_actionで使用できるオプション。「除外」するという意味。 つまり、exceptで指定したアクションには、事前処理(move_to_indexメソッド内容)は実行されない。
以上です。
番外編(モデルではなく、検索機能を「サービスクラス」に持たせる場合には)
(1)appディレクトリ配下に、services
というディレクトリを作成。
(2)その配下に、ファイルを作成。(今回はsearch_tweets_service.rb
)
app/services/ search_tweets_service.rb
class SearchTweetsService
def self.search(search)
if search != ""
Tweet.where('text LIKE(?)', "%#{search}%")
else
Tweet.all
end
end
end
(3)呼び出すコントローラーのクラス名を変更する。
- (2)のファイル名と同じにすること。
app/controllers/ tweets_controller.rb
class TweetsController < ApplicationController
(省略)
def search
@tweets = SearchTweetsService.search(params[:keyword])
end
(省略)
end