【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_wordsplit(/[[: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つ以上のワードが全て、指定したカラムのどこかに入っているレコードのみを取得してきます。