`eager_load=true` 起動時の NameError を修正し、再発防止の Rake タスクを追加

viewcomponent/view_component

eager_load = true でテスト環境を起動すると発生していた NameError: uninitialized constant ViewComponent::SystemTestControllerNefariousPathError を修正し、同種の問題を検出する eager_load_check Rake タスクを追加しました。

背景

この NameError は、Zeitwerk の定数オートロードと Rails エンジンの初期化順序の組み合わせによって引き起こされていました。eager_load = true の環境では、Zeitwerk がファイルをオートロードする前に Rails エンジンのコントローラーが処理される必要がありますが、ViewComponent::Base が先にオートロードされると、view_components_system_test_controller.rb 内で参照される ViewComponent::SystemTestControllerNefariousPathError が未定義となっていました。

ViewComponent::SystemTestControllerNefariousPathError はエラークラス群をまとめた view_component/errors で定義されていますが、view_components_system_test_controller.rb はそのファイルを明示的に require していませんでした。開発環境ではオートロードが逐次行われるため問題が顕在化せず、eager_load = true を有効にした環境でのみ再現する典型的な「本番環境だけで起きるバグ」でした。

技術的な変更

修正は app/controllers/view_components_system_test_controller.rb への require 追加という最小限の変更です。

変更前:

# frozen_string_literal: true

class ViewComponentsSystemTestController < ActionController::Base # :nodoc:
  if Rails.env.test?
    before_action :validate_file_path

変更後:

# frozen_string_literal: true

require "view_component/errors"

class ViewComponentsSystemTestController < ActionController::Base # :nodoc:
  if Rails.env.test?
    before_action :validate_file_path

require "view_component/errors" を明示的に追加することで、コントローラーが読み込まれる時点でエラークラスが確実に定義済みとなり、ロード順序に依存しなくなります。

再発防止として、Rakefileeager_load_check タスクが追加されました。このタスクは、コンポーネントを持たない最小限の Rails アプリを別プロセスで起動し、EagerLoadCheckApp.initialize! が正常完了するかを検証します。

desc "Verify the app boots with eager_load=true (catches missing requires)"
task :eager_load_check do
  puts "Checking eager loading..."
  result = system(
    {"RAILS_ENV" => "test"},
    "bundle", "exec", "ruby", "-e", <<~RUBY
      require "rails"
      require "action_controller/railtie"
      require "view_component"

      class EagerLoadCheckApp < Rails::Application
        config.eager_load = true
        config.secret_key_base = "test"
        config.hosts.clear
      end

      EagerLoadCheckApp.initialize!
    RUBY
  )
  abort("Eager loading check failed!") unless result
  puts "Eager loading check passed"
end

コメントにも記されているとおり、意図的に「コンポーネントなし」の素の Rails アプリを使用しています。これにより、Zeitwerk が ViewComponent::Base を事前にオートロードしない状態、つまり実際のホストアプリケーションで問題が発生するシナリオを正確に再現します。このタスクは all_tests タスクにも組み込まれ、CI で継続的に実行されます。

設計判断

別プロセスで検証アプリを起動する方式が採用されています。

system() を使って Ruby インタープリタを別プロセスとして起動しているのは、現在のテストプロセスが既に view_component をロード済みであるためです。同一プロセス内でアプリを初期化しても、定数は既に定義済みとなるため問題を再現できません。別プロセスで「まっさらな状態」から起動することで、実際のホストアプリケーションが経験するロード順序を忠実に再現しています。また、config.hosts.clear によって ActionDispatch::HostAuthorization が検証をスキップするよう設定されており、検証に不要な設定を排除した最小構成になっています。

まとめ

require 1行の追加という最小限の修正でロード順序への依存を排除しつつ、再発を防ぐための CI チェックをセットで導入した変更です。eager_load_check タスクのアプローチ、すなわち「別プロセスで最小構成のアプリを起動して初期化を検証する」という手法は、Rails エンジンを提供するライブラリが eager_load 起因の問題を継続的に検出するための汎用的なパターンとして参考になります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
5a64ad03

この記事は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番号のリンク記法([#2630](URL))が正しく使用されており、技術記事としての体裁が整っています。

対象読者への適合性 ✓ PASS

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

Zeitwerk、eager_load、Railsエンジンといった専門用語を前提としており、対象読者であるエンジニアに適した技術レベルと表現で書かれています。

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

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

各段落の冒頭にトピックセンテンスが置かれており、非常に読みやすい構造です。1段落1トピックの原則も守られており、内容が整理されています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロック(`Rakefile`と`view_components_system_test_controller.rb`)は、提供されたDiffの内容と完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Zeitwerk」「eager_load」「Rakeタスク」などの技術用語が正確かつ適切な文脈で使用されています。

説明の技術的正確性 ✓ PASS

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

`eager_load`時のロード順序に起因する`NameError`の発生メカニズムや、`require`追加による解決策、別プロセスでテストを実行する意図など、技術的な説明はすべて正確で論理的です。

事実の突合 ✓ PASS

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

記事の内容はすべてPRのDescription、Diff、ファイル名から裏付け可能であり、根拠のない主張や憶測(ハルシネーション)は一切見られません。

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

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

PR番号(#2630)やエラークラス名(ViewComponent::SystemTestControllerNefariousPathError)などの固有名詞はすべて正確に記載されています。

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

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

記事のタイトルはPRの主題(NameErrorの修正と再発防止タスクの追加)を的確に要約しており、PR内容との整合性が取れています。

外部知識の正確性 ✓ PASS

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

PR情報に含まれない外部知識(バージョンのサポート状況やリリース日程など)の追記はなく、信頼性の高い内容です。

時間表現の正確性 ✓ PASS

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

「発生していた」という過去形の表現など、時間に関する表現がPRの文脈と一致しており、事実関係を正確に伝えています。