# Rails5.2で解決済み【追記】
Avoid duplicate clauses when using #or #29950
で修正されており、取り込まれた5.2で解決しました
前提
Rails5で順序依存なくSQLを組み立てる_scope編
本記事では 上記記事にある scope 内の unscoped
に関する説明は省略します
概要
Rails5 で ActiveRecord に or クエリが導入されましたが適当に作ってると危ない
定義順に生成されるため「前条件はOR内」「後条件はOR外」になります
User.where(id: 1).where(id: 2).or(User.where(id: 3)).where(id: 4).to_sql
=> "SELECT `users`.* FROM `users`
WHERE (`users`.`id` = 1 AND `users`.`id` = 2 OR `users`.`id` = 3) AND `users`.`id` = 4"
これを 「2 or 3」 に限定したい場合はどうすれば良いでしょうか
後方定義
ORの挙動が周知されていればこれでも大丈夫でしょう
User.where(id: 2).or(User.where(id: 3)).where(id: 1).where(id: 4).to_sql
=> "SELECT `users`.* FROM `users`
WHERE (`users`.`id` = 2 OR `users`.`id` = 3) AND `users`.`id` = 1 AND `users`.`id` = 4"
しかし順序依存はスコープで発生すると大変です
せっかくスコープで定義したのに、使う時に内部実装を考慮しなくてはいけません
class User
scope :hoge1, -> { where(id: 1) }
scope :hoge23, -> { where(id: 2).or(unscoped.where(id: 3)) }
scope :hoge4, -> { where(id: 4) }
end
User.hoge23.hoge1.hoge4.to_sql
=> "SELECT `users`.* FROM `users`
WHERE (`users`.`id` = 2 OR `users`.`id` = 3) AND `users`.`id` = 1 AND `users`.`id` = 4"
User.hoge1.hoge23.hoge4.to_sql
=> "SELECT `users`.* FROM `users`
WHERE (`users`.`id` = 1 AND `users`.`id` = 2 OR `users`.`id` = 3) AND `users`.`id` = 4"
merge
merge で範囲を限定させるとSQLが括弧で括られ独立します
User.where(id: 1).merge(User.where(id: 2).or(User.where(id: 3))).where(id: 4).to_sql
=> "SELECT `users`.* FROM `users`
WHERE `users`.`id` = 1 AND (`users`.`id` = 2 OR `users`.`id` = 3) AND `users`.`id` = 4"
順序依存がなくスコープを利用する際に、順番を意識する必要がありません
class User
scope :hoge1, -> { where(id: 1) }
scope :hoge23, -> { merge(unscoped.where(id: 2).or(unscoped.where(id: 3))) }
scope :hoge4, -> { where(id: 4) }
end
User.hoge1.hoge23.hoge4.to_sql
=> "SELECT `users`.* FROM `users`
WHERE `users`.`id` = 1 AND (`users`.`id` = 2 OR `users`.`id` = 3) AND `users`.`id` = 4"
User.hoge23.hoge1.hoge4.to_sql
=> "SELECT `users`.* FROM `users`
WHERE (`users`.`id` = 2 OR `users`.`id` = 3) AND `users`.`id` = 1 AND `users`.`id` = 4"