`scope` の deprecated `:except` オプションが `only` に割り当てられるバグを修正
Rails のルーティング DSL では、scope メソッドにハッシュ形式のオプションを渡す旧式の書き方が未だにサポートされています。この PR は、Deprecated な :except キーワードが誤って only ローカル変数に代入され、期待とは逆のルートが生成されていた問題を正しく except に割り当て直します。結果として、マイグレーション途中のアプリケーションでも安全に scope を利用できるようになります。
背景
scope のハッシュ形式は、キーワード引数への移行期に残された互換レイヤー です。従来、scope({ except: :destroy }) と記述すると、本来は except が除外対象となりますが、実装上のミスにより except の値が only に流れ、逆にそのアクションだけが残るという振る舞いになっていました。PR の説明では、この挙動が「only: :destroy」として扱われ、意図しないルート生成が静かに起きると指摘されています。問題は actionpack/lib/action_dispatch/routing/mapper.rb の scope メソッド内で only ||= assign_deprecated_option(..., :except, :scope) と書かれていたことに起因します。修正はこの行を except ローカル変数へ代入し直すシンプルな置換で、他の Deprecated オプションと同様のパターンに揃えることです。
技術的な変更
変更前のコードでは、except が only に代入されていました。
only ||= assign_deprecated_option(deprecated_options, :only, :scope)
only ||= assign_deprecated_option(deprecated_options, :except, :scope) # <- 誤り
変更後のコードでは、except ローカル変数に正しく代入され、only への二重代入が防がれます。
only ||= assign_deprecated_option(deprecated_options, :only, :scope)
except ||= assign_deprecated_option(deprecated_options, :except, :scope)
この差分に加えて、テスト test_scope_with_deprecated_except_hash_option が actionpack/test/dispatch/routing_test.rb に新たに追加され、Deprecated なハッシュ形式で except: :destroy を指定したときに index ルートが残り destroy が除外されることを確認しています。テストはデプリケーション警告を期待しつつ、期待通りの応答とヘッダー (x-cascade) を検証しています。
設計判断
同一ファイル内の他の Deprecated オプション処理と整合性を取る ことが設計上の主眼です。only と except 以外のオプションはすべて assign_deprecated_option の結果を同名ローカル変数に ||= で代入しています。このパターンを崩さずに修正したことで、コードベース全体の一貫性と可読性が保たれます。また、||= を利用する理由は、キーワード引数が既に渡されている場合にハッシュ形式の値で上書きしないようにするためのガードであり、既存の動作に影響を与えません。結果として、scope のハッシュ形式とキーワード形式が同等に機能し、開発者はどちらの書き方でも安全に使用できるようになります。
まとめ
この PR は、scope の Deprecated ハッシュ形式で :except が誤って only に割り当てられるバグを except へ正しく割り当て直すだけのシンプルな修正です。コードパスの一貫性を保ちつつ、ミドルマイグレーション期のアプリでも予期せぬルート生成が起きないようにしました。テスト追加により回帰も防止され、Rails のルーティング DSL の堅牢性が向上しています。