Routingのdeprecated positional-hashでfalseが無視されていた問題を修正
Rails 8.1のキーワード移行期間中、ルーティングDSLのdeprecated positional-hash形式とkeyword形式は同等であることが期待されます。しかし、format: false や anchor: false といったブール値オプションが positional‑hash で無視され、ルートが変化し、しかもデプリケーション警告が出ないため、マイグレーション時に予期せぬ挙動が発生していました。
背景
このセクションでは、問題が発生した経緯とその影響を概観します。
問題の根本は、assign_deprecated_option が真偽判定で false を除外していたことです。 具体例として、get "/*path", to: "pages#show", format: false はフォーマットセグメントを除去しますが、同等と見なされる positional‑hash 形式 get "/*path", { to: "pages#show", format: false } では format: false が捨てられ、結果は /*path(.:format) となります。これにより、キーワード形式へ移行した際にルート動作が変わり、デプリケーション警告も発生しないため、ユーザーは違いに気付けません。
この不整合は、マイグレーションガイドが前提とする「同等動作」を壊すため、実運用上のリグレッションリスクを増大させます。 影響は、フォーマットやアンカーを無効化したいルート全般に及び、テストスイートでも失敗が報告されました。
技術的な変更
このセクションでは、実装上の修正内容とテスト追加を詳述します。
assign_deprecated_option が false を正しく扱うようロジックを変更しました。 変更前は if (deprecated_value = deprecated_options.delete(key)) という真偽判定で false が除外されていましたが、修正後は if deprecated_options.key?(key) でキーの有無を確認し、続いて deprecated_options.delete(key) で値を返すようになりました。これにより false も保持され、デプリケーション警告が出ます。
@@
- if (deprecated_value = deprecated_options.delete(key))
+ if deprecated_options.key?(key)
ActionDispatch.deprecator.warn(<<~MSG.squish)
#{method_name} received a hash argument #{key}. Please use a keyword instead. Support to hash argument will be removed in Rails 8.2.
MSG
- deprecated_value
+ deprecated_options.delete(key)
end
テストが追加され、format: false が positional‑hash でも正しく処理されることを検証しました。 新しいテスト test_deprecated_hash_honors_format_false は、デプリケーション警告が出ることと、生成されたルート文字列が /*path になることをアサートします。
@@
- def test_deprecated_hash_honors_format_false
- fakeset = FakeSet.new
- mapper = Mapper.new fakeset
-
- assert_deprecated(ActionDispatch.deprecator) do
- mapper.get "/*path", { to: "pages#show", format: false }
- end
-
- assert_equal "/*path", fakeset.asts.first.to_s
- end
+ def test_deprecated_hash_honors_format_false
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+
+ assert_deprecated(ActionDispatch.deprecator) do
+ mapper.get "/*path", { to: "pages#show", format: false }
+ end
+
+ assert_equal "/*path", fakeset.asts.first.to_s
+ end
修正は assign_deprecated_option のロジックとテスト追加だけに留まり、既存の呼び出し側コードは変更不要です。 これにより、既存アプリケーションへの影響は最小限に抑えられます。
設計判断
このセクションでは、採られた設計方針とその妥当性を考察します。
最小侵入的な修正で、既存APIと互換性を保ちつつバグを解消しました。 assign_deprecated_option の内部だけを変更し、外部インターフェースや呼び出し側ロジックはそのままです。これにより、format: false や anchor: false が正しく扱われ、同時にデプリケーション警告も発生するため、将来的な削除に備えた移行が容易になります。
デプリケーション警告のロジックは保持し、キーが存在すれば必ず警告を出す方針です。 以前は false が除外されて警告が出なかった点を修正し、ユーザーが意図的に false を渡したことを明示的に通知します。これにより、ドキュメント通りの挙動が保証され、移行ガイドの前提が崩れません。
まとめ
assign_deprecated_option の真偽判定を key? へ置き換え、false も正しく処理できるようにしたことで、deprecated positional‑hash と keyword 形式のルーティングが等価になりました。影響範囲はメソッド内部に留まり、既存コードへの破壊的変更はありません。これにより、Rails 8.1 のキーワード移行が安全に進められ、将来的な hash 形式の廃止に向けた一貫したデプリケーション体験が提供されます。