ActiveRecord::QueryMethods.#orの使い方が理解しにくいので、整理してみました。
ActiveRecordのwhere
の引数に文字列でWHERE句を渡せば、なんでも表現できますが、シンタックスハイライトが効かなかったり、静的解析を行えないので、できるだけSQLを文字列で記述したくないです。
where.or 基本形
Model.where(a).or(Model.where(b))
# SELECT * FROM "users" WHERE (a OR b)
実際に動かしてみると、こんな感じ
User.where(name: "taro").or(User.where(email: "taro@example.com"))
# SELECT "users".* FROM "users" WHERE ("users"."name" = $1 OR "users"."email" = $2)
以降では、わかりやすくするために前者の表記で記載するので、適宜読み替えてください。
チェーン形式
Model.where(a).or(Model.where(b)).or(Model.where(c))
# SELECT * FROM models WHERE ((a OR b) OR c)
# つまり、 SELECT * FROM models WHERE (a OR b OR c)
単純に全ての条件をor
なら、チェーンで繋いでいけばいいです。
andと併用
(a && b) || c
Model.where(a).where(b).or(Model.where(c))
# SELECT * FROM models WHERE (a AND b OR c)
and
とor
が混在すると、演算子の優先度と同じように&&
が優先されるため、単純に繋げると、or
は後から評価されます。
a && (b || c)
Model.where(b).or(Model.where(c)).where(a)
# SELECT * FROM models WHERE (b OR c) AND a
or
を優先したい場合、ActiveRecordでは、評価の優先順をチェーンメソッドの並び順で表現するようです。
自分は、これをよく使う場面があります。例えば、公開終了日を表すend_at
がnull
を許可する場合、公開中のデータを取得するにはWHERE start_at <= :time AND (:time <= end_at OR end_at IS NULL);
といったことが行いたくなります。
(a && b) || (c && d) || (e && f)
最後に、こんなことがしたい場合は、以下のようになります。
ここまでくると、SQL直書きの方がマシですね。。。
Model.where(a).where(b).or(User.where(c).where(d).or(User.where(e).where(f)))
# SELECT "users".* FROM "users" WHERE (a AND b OR (c AND d OR e AND f))
他にもor
の表現パターンが見つかったら追記していきたいと思います。