LoginSignup
92
90

More than 5 years have passed since last update.

Ruby on Rails4で複数キーワードのOR検索をしたいときはArelを使う。

Posted at

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カラムを検索対象のフィールドとします。

db/schema.rb
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のフォームから以下のようなテキストボックスでユーザの入力を受け取ります。

app/views/seminars/index.html.erb
    <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条件で検索するように実装していきます。
実装の全体は以下のようになります。

app/models/seminar.rb
# 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で結んでいく

add_more_keyword
    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

92
90
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
92
90