何をしたか
form object
で検索フォームを実装しました。
**「赤い りんご」**のように、空白で区切られた検索ワードが入力された時には、空白で区切って複数単語のOR検索にしました。
実装環境は以下の通りです。
Rails 5.2.3
Ruby 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
のロジックは消えてしまったのですが、面白い書き方だったので記事にしてとっておくことにしました