ArelによるOR条件のメソッドチェイン(Rails3.2.3)

Rails3以降、ActiveRecordによるDB検索は「Arel」という中間ライブラリを利用して行われています。

・・・最近知りました。
以前と同じインタフェースで利用していました。

ということで現在はArelによるメソッドチェインを利用した形でコーディングしているのですが、
タイトルにも挙げた通り、OR条件のつなげ方で悩んだので解決法を載せておきます。
※今回はRails3.2.3を利用しました。ネット上で見かけるRails4の実装では上手くいかないものがあったのでご注意を。
以下のサイトでは(たぶん)Rails4での使い方の例が載っています。

第43回 Rails 3を支える名脇役たち その1 - Arel -:Ruby Freaks Lounge|gihyo.jp … 技術評論社
Rails 3&4: Arel::Tableを使ってなるべく生のSQLを書かずに済ます方法
観察から知る Rails3 ActiveRecord + Arel の特徴と使い方 - tashenの日記

悩んだ経緯

例えば「複数のキーワドのいずれかに該当するデータ」を取りたいとなったら、
配列に格納した文字列を繰り返し処理で、メソッドチェインしたいわけです。
その時にANDは簡単に出来たのですが、ORが上手くいかなかった・・・

AND条件のチェイン

まずは簡単にできたAND条件のチェインの例を載せます。

#ActiveRecordからArel::Tableインスタンスを取得
users = User.arel_table

#キーワード配列
keyword_arr = {'a','b',.....'z'}

#1つ目を条件に格納して最初のActiveRecord::Relationを取得
users_rel = users.where(users[:name].eq(keyword_arr[0]))
#あとはこれに好きなだけつなげていきます
for i in 1..users_rel.length-1
   users_rel = users_rel.where(users[:name].eq(keyword_arr[i]))
end

#出来上がったSQL(実際にこんな条件成立しませんが。)
puts users_rel.to_sql 
#=> SELECT `users`.* FROM `users`  WHERE `users`.`name` = 'a' AND `users`.`name` = 'b' OR ... AND `users`.`name` = 'z'

基本的にwhereメソッドはActiveRecord::Relationインスタンスを返し、そのインスタンスからさらにwhereメソッドを呼び出すことでAND条件がチェインされます。

OR条件のチェイン

ではorの場合どうするか。

#ActiveRecordからArel::Tableインスタンスを取得
users = User.arel_table

#キーワード配列
keyword_arr = {'a','b',.....'z'}

#最初のArel::SelectManager取得
users_sel = users[:name].eq(keyword_arr[0])
#このusers_selにorメソッドで条件をチェインしていく
for i in 1..users_sel.length
   users_sel = users_sel.or(users[:name].eq(keyword_arr[i]))
end

#この時点でSQLの「条件部」だけが完成
puts users_sel.to_s
#=> `users`.`name` = 'a' OR `users`.`name` = 'b' OR ...  `users`.`name` = 'z'

#whereメソッドへ完成した条件を渡してActiveRecord::Relationが完成
users_rel = users.where(users_sel)

#出来上がったSQL
puts users_rel.to_sql
#=> SELECT `users`.* FROM `users`  WHERE `users`.`name` = 'a' OR `users`.`name` = 'b' OR ... OR `users`.`name` = 'z'


わかってしまえば簡単です。
Relation(users)のwhereメソッドは、
引数に条件を示すオブジェクトであるSelectManager(users[:name].eq()とかの戻り値)が渡ったとき、
それが複数あるとANDでチェインします。

それと同等のOR機能は残念ながらないため、SelectManagerのor(andもある)メソッドを使って、
条件だけを先に組み立てて最後にRelationを生成してみました。
※すべて動作を見ただけで書いています。ソースコードを読んでないので正確ではありません。


whereとorの戻り値について理解が足りずに苦しんだのでした。


Ruby on Rails 4 アプリケーションプログラミング

Ruby on Rails 4 アプリケーションプログラミング