【rails】複数カラムでの複数フリーワード検索を実装する
はじめに
railsで複数カラムに対して、検索されたワードが部分一致したレコードのみを取得する検索機能を実装する。
また、複数ワードで検索された場合は、そのワードが全てどこかのカラムに入っているレコードのみを取得する。
構成
機能の構成としては、
複数ワードで検索する際はスペースでワードを区切ってフォームに入力してもらう。(疑似カラム
search_word
を作る)スペースで検索ワードを分解して配列にいれる。
injectメソッドを使って繰り返しの処理をする。 (複数のワードがマッチしたレコードだけ取得する)
実装
実際には複数モデルでの検索も入れているので、form objectを使っております。
class SearchBugsForm include ActiveModel::Model include ActiveModel::Attributes attr_accessor :search_word def search relation = Bug.distinct if search_word.present? search_words = search_word.split(/[[:blank:]]+/) search_words.inject(relation) do |result, word| relation = result.where('name LIKE ?', "%#{word}%").or(result.where('feature LIKE ?', "%#{word}%")).or(result.where('approach LIKE ?', "%#{word}%")).or(result.where('prevention LIKE ?', "%#{word}%")).or(result.where('harm LIKE ?', "%#{word}%")) end end end end
解説
まず、search_word
という擬似カラムを作ってそれをattri_accessor
でアクセスできるようにします。
attr_accessor :search_word
searchメソッドでは最初に、レコードの集合を取得してrelation
に代入します。
relation = Bug.distinct
取得したsearch_word
をsplit(/[[:blank:]]+/)
を使ってスペースで分解し、ワードを配列search_words
に代入します。
search_words = search_word.split(/[[:blank:]]+/)
/[[:blank:]]+/
にすると、スペースが全角でも半角でも対応できるようになります。
続いて、inject
メソッドを使います。
search_words.inject(relation) do |result, word| relation = result.where('name LIKE ?', "%#{word}%").or(result.where('feature LIKE ?', "%#{word}%")).or(result.where('approach LIKE ?', "%#{word}%")).or(result.where('prevention LIKE ?', "%#{word}%")).or(result.where('harm LIKE ?', "%#{word}%")) end
初期値をレコードの集合であるrelation
に設定し、これがresult
に代入されます。
word
には先程のsearch_words
の検索ワードが順番に入ります。
検索ワードごとにor
検索をして、最低1つのカラムの値に、その検索ワードが含まれているレコードをrelation
に代入します。
そのrelation
の結果がresult
に代入され、次のワードがword
に代入されて、再びor
検索が実行されます。
これを検索されたワードの数だけ繰り返します。
これで検索されたワードが2つ以上の場合は、その2つ以上のワードが全て、指定したカラムのどこかに入っているレコードのみを取得してきます。