Reload時のスコープリークを修正 – all_queries デフォルトスコープの正しい適用
Reload時に all_queries デフォルトスコープが意図せず現在のスコープを混入し、余計な条件がクエリに付与されていた問題を解消します。all から default_scoped への切り替えで、スコープの境界が明確になり、期待通りのSQLが生成されます。
背景
reload が内部で self.class.all(all_queries: ...) を呼び出す設計のため、current_scope に含まれる非 all_queries スコープがリロードクエリに流出していました。問題は PR #57123 で導入された耐久性のある名前付きデフォルトスコープと併用した際に顕在化し、DeveloperWithDefaultMentorScopeAllQueries.reload が余計な salary 条件を付加していました。
このバグは「all が current_scope を包含する」ことが前提となっていた実装に起因し、all_queries だけを対象にした再読み込みが不可能になるという実害をもたらしていました。修正は all の呼び出しを排除し、default_scoped に委譲することで根本的にスコープの合成ロジックを分離します。
技術的な変更
activerecord/lib/active_record/persistence.rb の _find_record メソッドで、all から default_scoped への置換が行われました。これにより all_queries: true が指定された場合は self.class.default_scoped(all_queries: true) が使用され、そうでない場合は従来通り self.class.all が呼び出されます。
@@
- base = self.class.all(all_queries: all_queries).preload(strict_loaded_associations)
+ base = if all_queries
+ self.class.default_scoped(all_queries: true)
+ else
+ self.class.all
+ end
+
+ if all_queries && (current_scope = self.class.global_current_scope)
+ base = base.merge!(current_scope)
+ end
+
+ base = base.preload(strict_loaded_associations)
all_queries が有効なときは global_current_scope を明示的に取得し、merge! によって現在のグローバルスコープだけを追加します。これにより all が暗黙的に current_scope を巻き込むことはなくなり、all_queries が要求する純粋なデフォルトスコープだけが適用されます。
テストコードも同様に拡張され、test_default_scope_with_all_queries_does_not_apply_current_scope_on_reload と test_default_scope_with_all_queries_applies_all_queries_current_scope_on_reload の2ケースで期待通りの SQL が生成されることを検証しています。これらのテストは scoping ヘルパーで all_queries フラグを操作し、リロード時のクエリ構造を直接比較しています。
以上の変更は既存の reload 挙動に最小限の侵入で済むよう設計されており、all_queries を使用しないコードパスは従来通り self.class.all が呼び出され続けます。
設計判断
default_scoped を利用して all_queries を処理する方針は、スコープ生成ロジックを一元化し可読性を向上させる意図があります。all が内部で current_scope を自動的にマージする複雑さを排除し、default_scoped が明示的に all_queries オプションを受け取る形に統一しました。
この選択は後方互換性を保つ点でも評価されます。self.class.all を呼び出す既存コードは変更されず、all_queries が真の場合だけ新しい経路に分岐するため、既存アプリケーションへの影響はなく安全にデプロイできます。さらに、global_current_scope の取得とマージは条件付きで実行されるため、不要な結合が発生しない点も設計上のメリットです。
結果として、スコープの境界が明確化されたことで、デフォルトスコープとリロードロジックの相互作用が予測可能になり、バグ再発防止に寄与します。
まとめ
reload が all_queries デフォルトスコープを正しく扱わなかったバグは、self.class.all から self.class.default_scoped への置換と、global_current_scope の条件付きマージにより解消されました。変更は既存挙動を壊さず、テストでカバーされた新しいスコープ適用ルールが導入されたことで、Rails のクエリ生成がより堅牢になりました。