何をしたか
form objectで検索フォームを実装しました。
**「赤い りんご」**のように、空白で区切られた検索ワードが入力された時には、空白で区切って複数単語のOR検索にしました。
実装環境は以下の通りです。
Rails 5.2.3Ruby 2.6.0
まずは1単語の検索フォームを作る
今回作るのは、投稿postの検索フォームです。
まずはシンプルに、1単語の検索フォーム(フォームオブジェクトではないもの)から作り始めました。
実際に書いたコードは以下の通りです。(装飾等に使用したclassは省いています。以下同。)
def search
@posts = Post.where('body LIKE ?', "%#{params[:body]}%")
end
= form_with url: search_posts_path, method: :get, local: true do |f|
= f.search_field :body
= f.submit
# ページ下方で = render @posts をして、検索結果を呼び出しています
非常にシンプルですね![]()
この辺の基本的なコードの読み解き方は、以下の記事にまとめておりますので、よろしければご覧ください。
Railsの検索メソッドの基礎(whereでLIKE句を使う)
form objectにする
では、こちらのコードをform objectにしていきます。
def search
@posts = SearchForm.new(search_post_params)
end
# 中略
def search_post_params
params.fetch(:q, '').permit(:body) # 解説します
end
= form_with form_with model: search_form, url: search_posts_path, scope: :q, method: :get, local: true do |f|
= f.search_field :body # ↑上記、解説します
= f.submit
class SearchForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :body, :string
def search
Post.includes(:user, :images).where('body LIKE ?', "%#{body}%")
end
end
form objectにすることで、検索ロジックをフォームオブジェクトに移動できました![]()
解説:scope: :qとfetch(:q, '')について
ところで、viewにあったscope: :qが謎ですよね。(↓この部分です)
= form_with form_with model: search_form, url: search_posts_path, scope: :q, method: :get, local: true do |f|
これは、「送るパラメーターをどのハッシュでまとめるのか」を指定している部分で、
▼scope: :qがない時のparams
Processing by PostsController#search as HTML
Parameters: {"utf8"=>"✓", "body"=>"りんご", "commit"=>"SEARCH"}
Unpermitted parameters: :utf8, :commit
▼scope: :qが ある時のparams
Processing by PostsController#search as HTML
Parameters: {"utf8"=>"✓", "q"=>{"body"=>"りんご"}, "commit"=>"SEARCH"}
のように、scope: :qの記述によって、検索単語を"q"=>{ ... }の中にまとめることができます。
キーワードを入力するフォールドが一つの検索フォームだとほとんど意味がありませんが、入力するフィールド数が2つ、3つにになってくると、
"q"=>{ "フィールド名"=>"検索単語", "フィールド名"=>"検索単語", ... }
の形でparamsがまとまってくれるので、便利です。![]()
また、先ほどはcontrollerのstrong paramaterに以下のように設定していましたが...
def search_post_params
params.fetch(:q, '').permit(:body)
end
これによって、paramsの中から、"q"=>{ ... }の部分だけを取り出しています。なお、fetch(:q, '')の''の部分は、キーである:qがなかった時のデフォルト値です。
▼fetchの詳しい説明はこちら
instance method Hash#fetch
複数単語検索にする
それでは、これを複数単語のor検索に変えていきます。
色々やり方はありましたが、今回はこちらの記事と、そのコメントを参考に、以下のように実装しました。
class SearchForm
# 変わっていないので中略
def search
keywords = body.split(/[[:blank:]]+/)
@posts = Post.none
keywords.each do |keyword|
@posts = @posts.or(Post.where('body LIKE ?', "%#{keyword}%"))
end
return @posts
end
end
何点かピックアップして解説します。
-
.split(/[[:blank:]]+/)...検索ワードを空白で複数単語に分け、配列に入れます。 -
Post.none...空のActive Recordを作ります。空の配列を作る[]のActive Record版です。 -
@posts.or(...)...詳細下述します。
解説:@posts.or(...)について
この部分については、記述が複雑になるのでピックアップします。Railsのorメソッドは、データベースへの複数検索のorを実行するメソッドです。
例えば、以下のコードで
Fruit.where(name: 'バナナ').or(Fruit.where(name: 'グレープ'))
以下のようなSQLが発行されます。(サンプルのコードはこちらからお借りしました)
Fruit Load (1.5ms) SELECT `fruits`.* FROM `fruits` WHERE (`fruits`.`name` = 'バナナ' OR `fruits`.`name` = 'グレープ')
実装に用いたコードでは、このように書くことで、
@posts = Post.none
keywords.each do |keyword|
@posts = @posts.or(Post.where('body LIKE ?', "%#{keyword}%"))
end
return @posts
keywordの数だけ
Post.where(body: 'keyword').or(Post.where(body: 'keyword').or(Post.where(...)))
を繰り返し、複数語検索を実現しています。
実際にはリファクタリングを繰り返し、上記orのロジックは消えてしまったのですが、面白い書き方だったので記事にしてとっておくことにしました![]()