開発環境のウェルカムルートがリロードのたびに重複する問題を修正
reload_routes! を呼び出すたびに内部の GET / ウェルカムルートが重複登録される問題が修正されました。run_after_load_paths を「毎回実行」から「初回のみ実行」に変更することで、アプリケーション起動後のルートリロードでの二重登録を防ぎます。
背景
Railsの開発環境は、routes_reloader.run_after_load_paths フックから内部ウェルカムルートを登録する設計になっていました。このフックは load_paths が呼ばれるたびに実行されるため、ルートリロードのたびにウェルカムルートが追加される問題がありました。
RouteSet#append はappendブロックを永続的に保持します。一方、ルートリロード時は実体化されたルートがクリアされ再構築されます。これにより、reload_routes! を3回呼ぶと GET / が3つ登録されるという状態になっていました。
この問題は開発環境固有のものです。ウェルカムルートは internal: true フラグ付きで登録されるため実際のルーティングへの影響は限定的ですが、ルートセットが肥大化するという問題がありました。
技術的な変更
RoutesReloader に run_once_after_load_paths を導入し、コールバックを「一度だけ実行して破棄する」セマンティクスを実装しました。
railties/lib/rails/application/routes_reloader.rb では、既存の run_after_load_paths アクセサを廃止し、初回実行後に nil をセットする run_after_load_paths_callback メソッドに置き換えています:
変更前:
def load_paths
paths.each { |path| load(path) }
run_after_load_paths.call
end
def run_after_load_paths
@run_after_load_paths ||= -> { }
end
変更後:
def load_paths
paths.each { |path| load(path) }
run_after_load_paths_callback
end
def run_after_load_paths_callback
if @run_once_after_load_paths
@run_once_after_load_paths.call
@run_once_after_load_paths = nil
end
end
railties/lib/rails/application/finisher.rb 側では、フックの設定先を run_after_load_paths= から run_once_after_load_paths= に切り替えるだけで対応しています:
# 変更前
routes_reloader.run_after_load_paths = -> do
app.routes.append do
get "/" => "rails/welcome#index", internal: true
end
end
# 変更後
routes_reloader.run_once_after_load_paths = -> do
app.routes.append do
get "/" => "rails/welcome#index", internal: true
end
end
RouteSet#append がブロックを永続保持する挙動はそのままで、登録処理を一度だけ呼ぶことで重複を防ぎます。アプリのルートに root ルートを定義した場合にウェルカムルートより優先される既存の動作も維持されます。
設計判断
既存の append セマンティクスを変更せず、フックの呼び出し回数を制御するアプローチが採用されました。
RouteSet#append の動作を変えることも選択肢として考えられますが、それはappend挙動に依存する他のコードへの影響範囲が広くなります。今回は RoutesReloader 内に「一度きりのコールバック」という概念を導入することで、変更を最小限の範囲に収めています。コールバックを実行後すぐに nil に設定するシンプルな実装で、再度 run_once_after_load_paths= がセットされない限り再実行されません。
テストでは reload_routes! を3回連続で呼び出したあとにウェルカムルートの件数を検証しており、リグレッションを防ぐ明確な回帰テストが追加されています。
まとめ
本PRは RoutesReloader に「一度だけ実行するコールバック」のセマンティクスを導入することで、開発環境のルートリロード時のウェルカムルート重複問題を解決しました。RouteSet#append の永続的なブロック保持という挙動はそのままに、フックの呼び出し制御を変えるだけという局所的な修正で、影響範囲を最小に抑えた設計です。