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


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

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

```html: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条件で検索するように実装していきます。
実装の全体は以下のようになります。

````ruby: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で結んでいく

```ruby: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