View
の入力フォームにスペース区切りで入力された複数キーワードのどれかが、カラム内容に含まれているデータを検索します。
「Ruby on Rails 4アプリケーションプログラミング」などの本は持っているのですが、OR検索についてはあまり情報がなかったのでまとめます。
概要
たとえばkeywords = "卵 牛”
から以下のようなSQL文を作成したいとします。
FROM "foods" WHERE (("foods"."ingredient" ILIKE '%卵%' OR "foods"."ingredient" ILIKE '%牛%'
ここでRuby on Rails 4でDB検索を行おうとすると、ほぼwhere文に頼ることになります。
しかし、以下の実装では複数キーワードのOR条件検索が出来ません
-
model.where("条件A").where("条件B")
: 「条件A AND 条件B」になる -
model.where(["member LIKE ? or member LIKE ?", "条件A", "条件B"
: キーワードの数が固定数になってしまう。
解決策はArelで検索条件文を生成後、where()
に代入することになります。
前提
今回はSeminar
クラスのsummary
カラムを検索対象のフィールドとします。
ActiveRecord::Schema.define(version: 20140713000000) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "seminars", force: true do |t|
t.string "summary"
end
end
View
のフォームから以下のようなテキストボックスでユーザの入力を受け取ります。
<div id="condition-cards" class="clearfix">
<%= form_tag seminars_path, :method => 'get' do %>
<div id="keyword">
<dl class="clearfix">
<dt><%= text_field_tag :search, params[:search] %></dt>
<dd><%= submit_tag "検索", :name => nil %></dd>
</dl>
</div>
<% end %>
</div>
Controler
モデルへ実装を行う。
ロジックを実装していきます。
View
から受け取ったparams[:search]
を用いて、Seminar
の:summary
をOR LIKE条件で検索するように実装していきます。
実装の全体は以下のようになります。
# encoding: utf-8
class Seminar < ActiveRecord::Base
def self.search(params)
params[:search] ||= ""
keyword_arrays = params[:search].gsub(/ /," ").split()
seminars = Seminar.arel_table[:summary]
seminars_sel = seminars.matches("\%#{keyword_arrays[0]}\%")
for i in 1...keyword_arrays.length
seminars_sel = seminars_sel.or(seminars.matches("\%#{keyword_arrays[i]}\%"))
end
logger.debug("SQL: #{Seminar.where(seminars_sel).to_sql}")
Seminar.where(seminars_sel)
end
end
以下、ひとつずつ処理の確認をしていきます。
入力BOXに何も入力がなかった場合の対処をしておく
params[:search] ||= ""
入力BOXが未入力だった場合、""
を入れて空文字列にします
未対応のままですと後続の処理がnil
に対して行われてしまうので、nil
ガードを書けておきます。
#本当は条件分岐で最初から後続の処理を飛ばせばいいのですが保留
入力文字列をパースする。
keyword_arrays = params[:search].gsub(/ /," ").split()
keyword_arrays
を検索ワードを含んだ文字列配列とします。
split()
で文字列を分解し、検索ワードの文字列配列を作ります。
split()
前にgsub()
で全角スペースを半角スペースに置換してあります。
ActiveRecordからArel::tableインスタンスを取得する
seminars = Seminar.arel_table[:summary]
検索を行いたいsummary
カラムのArel::table
のインスタンスを取得します。
このインスタンスから、条件式を作成していきます。
一つ目の検索条件からActiveRecord::Relationを作成する
seminars_sel = seminars.matches("\%#{keyword_arrays[0]}\%")
まずは一つ目の条件からActiveRecord::Relation
を作成します。
ここでは正規表現の検索条件として"%文字列%"
と条件を作成していることに注意してください。
このseminars_sel
の検索条件を完成させれば、それをwhere()
に渡すことができます。
残りの検索条件をORで結んでいく
for i in 1...keyword_arrays.length
seminars_sel = seminars_sel.or(seminars.matches("\%#{keyword_arrays[i]}\%"))
end
注意点は1つ目の検索条件のインスタンスを作成した際と同じです。
split()
した検索条件をor()
メソッドでつないでいきます。
where()の検索条件とする
これでSQLの条件式は作成できました。
後はwhere()
に突っ込んでSQL文を発行するだけです。
Seminar.where(seminars_sel)
終わりに
他の条件と合わせて今回のOR LIKE条件を利用する場合、AND条件ならばwhere()
をメソッドチェーンする、ORならば、またseminars_sel
に条件を含めることで解決できます。
参考文献
今回は以下の内容を参考にさせていただきました!
ArelによるOR条件のメソッドチェイン(Rails3.2.3)
http://quotto.hatenablog.com/entry/20131024/p1
Arelで色んなSQLを組み立ててみる
http://ryopeko.hatenablog.com/entry/20101215/1292373612
gsub, gsub! (String)
http://ref.xaio.jp/ruby/classes/string/gsub
Rubyのnilガードとは
http://programming-10000.hatenadiary.jp/entry/20140713/1405177974