開発環境のウェルカムルートがリロードのたびに重複する問題を修正

rails/rails

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 フラグ付きで登録されるため実際のルーティングへの影響は限定的ですが、ルートセットが肥大化するという問題がありました。

技術的な変更

RoutesReloaderrun_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 の永続的なブロック保持という挙動はそのままに、フックの呼び出し制御を変えるだけという局所的な修正で、影響範囲を最小に抑えた設計です。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
c7cfabed

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→セクション群(各論)→まとめ(結論)の3部構成が明確で、理想的な記事構成です。「背景」「技術的な変更」「設計判断」の各セクションが論理的に配置されています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのシンタックスハイライト(```言語:ファイルパス)と、PR番号のGitHubリンク記法([#...](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Railsの内部実装に関する専門用語が適切に使用されており、専門知識を持つエンジニアという対象読者に適合した内容です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、かつ各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ⚠ WARNING

コードブロックとDiff内容の一致

主要なコード変更は正確に引用されていますが、「技術的な変更」セクションの変更前コードブロックで、Diffに存在する`run_after_load_paths`メソッドの初期化処理が省略されています。ただし、変更の核心部分ではないため、技術的な理解を大きく妨げるものではありません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`RoutesReloader`, `RouteSet#append` など、PRの文脈で使われている技術用語を正確に使用できています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

ルートが重複する原因と、それを解決するための「一度だけ実行するコールバック」というアプローチについての説明は、Diffの実装と完全に一致しており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張は、PRのタイトル、Description、およびDiff内のコードやテストコードによって裏付けられています。特に「設計判断」はコードの意図を深く読み取っており、質の高い解説となっています。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#57367)が正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、元のPRのタイトル「Prevent development welcome route from duplicating on route reload」の内容を日本語で正確に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事の内容はすべて提供されたPR情報に基づいており、LTSやEOL、リリース日程といったPR外の外部知識の捏造はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「既に」「まもなく」といった時間表現の誤用はなく、事実を客観的に記述しています。