# Rails5.2で解決済み【追記】
Avoid duplicate clauses when using #or #29950
で修正されており、取り込まれた5.2で解決しました
# 概要
自身参照scopeの場合、Relationの繋ぎ方(順序)によってはSQLの重複が発生した
意図しない挙動になるのでなるべく避けたい
具体的な挙動
実際に自身参照scopeが必要な場合は複雑なものになるが検証のため以下を用いる
class User
scope :fuga, -> { where(id: where(type: 'fuga')) }
end
scope前に条件がある場合はscope内の自己参照部分にもその条件が入る(id=1の重複)
User.where(id: 1).fuga.to_sql
=> "SELECT `users`.* FROM `users`
WHERE `users`.`id` = 1
AND `users`.`id` IN (
SELECT `users`.`id` FROM `users`
WHERE `users`.`id` = 1 AND `users`.`type` = 'fuga'
)"
scope後に条件がある場合はscope内の自己参照部分にはその条件が入らない
User.fuga.where(id: 1).to_sql
=> "SELECT `users`.* FROM `users`
WHERE `users`.`id` IN (
SELECT `users`.`id` FROM `users`
WHERE `users`.`type` = 'fuga'
) AND `users`.`id` = 1"
条件が重なるだけで出力結果に違いはないが順序依存で重複の有無が変動する
問題か否か
例えば重いサブクエリの場合は重複が問題になるかもしれない
User.large_subquery.fuga.to_sql #=> 遅くなるかも
User.fuga.large_subquery.to_sql #=> 通常
例えば大量データのテーブルならindex次第で重複が有効かもしれない
User.cutdown_subquery.fuga.to_sql #=> 速くなるかも
User.fuga.cutdown_subquery.to_sql #=> 通常
ただ、上記のような順序依存があるのが好ましくないと感じる
作った当時は調整できても後で修正する時に事故になる危険性があり保守性が落ちる
調整が必要ならその調整が崩れないように宣言しておくほうが安心する
# データが多く高速化のためサブクエリにも適用しておく必要がある
scope :cutdowned_fuga, -> { where(id: cutdown_subquery.fuga) }
順序依存を無くす方法
引き継がないことを定義すれば良い
class User
scope :fuga, -> { where(id: unscoped.where(type: 'fuga')) }
end
そうすれば順序を問わず重複しない
User.where(id: 1).fuga.to_sql
=> "SELECT `users`.* FROM `users`
WHERE `users`.`id` = 1 AND `users`.`id` IN (
SELECT `users`.`id` FROM `users` WHERE `users`.`type` = 'fuga'
)"
User.fuga.where(id: 1).to_sql
=> "SELECT `users`.* FROM `users`
WHERE `users`.`id` IN (
SELECT `users`.`id` FROM `users` WHERE `users`.`type` = 'fuga'
) AND `users`.`id` = 1"
注意: 間違えやすい方法
クラスから呼び新しいRelationにすれば良いと考えやすい
class User
scope :fuga, -> { where(id: User.where(type: 'fuga')) }
end
実際には前条件が付いてしまう、同じRelationキャッシュを使っている?
User.where(id: 1).fuga.to_sql
=> "SELECT `users`.* FROM `users`
WHERE `users`.`id` = 1 AND `users`.`id` IN (
SELECT `users`.`id` FROM `users`
WHERE `users`.`id` = 1 AND `users`.`type` = 'fuga'
)"
参考
残念ながら見つかりませんでした
unscoped
で調べても default_scope
の解除方法しか出てこない...