ransackで雑にRDBMSを使った簡易的なキーワードのOR検索を実装する

一般的なRDBMSを使ったよくあるLIKEでの曖昧検索実装では、検索ボックスに「寿司 和食」と入力してもただの単語での曖昧検索だと、 当たり前なんだけど、キーワードのOR検索を実現することができない。 select * from contents where LIKE body '%寿司 和食%' となってしまって、空白を含めた1単語として認識されてしまい、思うような結果が得られないと思う。

実際に組み立てるクエリは…?

多分イマイチなんだろうけど、簡易的にOR検索を実現するにはこんな感じになると思う。

"SELECT "contents".* FROM "contents" WHERE (("title" LIKE '%寿司%' OR "title" LIKE '%和食%') OR ("body" LIKE '%寿司%' OR "body" LIKE '%和食%'))"

これで擬似的にキーワードのOR検索を実装することができた。精度もまぁ無いよりかはマシくらいなもんだろう。 これでOKな事例というのは、検索対象が小規模な場合で、設定できるキーワードに上限を一応設けたほうが良いかもしれない。 以上の注意点を踏まえた上で、Railsで簡単に上記SQLを作る方法に進んでみよう。

実際にRansackで雑に実装

もちろん複雑なことはやっていないので、自分でさっきのSQL文を組み立てて投げるのでも良いのだけど、 面倒だし、検索機能を実装する時によく使われているRansackで実装してみようと思う。

github.com

以下のようにかけばOK。

Content.search(title_or_body_cont_any: ['寿司', '和食']).result

それでは上記で実行されるSQLを見てみよう。

irb(main):007:0> Content.search(title_or_body_cont_any: ['寿司', '和食']).result.to_sql

=> "SELECT \"contents\".* FROM \"contents\" WHERE ((\"contents\".\"title\" LIKE '%寿司%' OR \"contents\".\"title\" LIKE '%和食%') OR (\"contents\".\"body\" LIKE '%寿司%' OR \"contents\".\"body\" LIKE '%和食%'))"

おお、大丈夫そうだ。 あとは、JOINでスペースを区切り文字にして、splitでキーワードの配列作ればOKな感じ。

 # controller 
  def index
    if params[:keyword]
      @contents = Content.search(title_or_body_cont_any: params[:keyword].split(' ')).result
    else
      @contents = Content.all
    end
  end

これで完成。 意外とシュッとできた。やっぱり小規模用途だとransackは便利だった。