Steps to reproduce
- Add
gem "view_component", "4.9.0" to a Rails 8 application.
- Ensure
config.eager_load = true in the test environment used for booting
# config/environments/test.rb
# Eager loading loads your entire application. When running a single test locally,
# this is usually not necessary, and can slow down your test suite. However, it's
# recommended that you enable it in continuous integration systems to ensure eager
# loading is working properly before deploying your code.
config.eager_load = ENV["CI"].present?
- run
RAILS_ENV=test bin/rails zeitwerk:check or RAILS_ENV=test CI=1 bin/rails test
# RAILS_ENV=test bin/rails zeitwerk:check
Hold on, I am eager loading the application.
bin/rails aborted!
NameError: uninitialized constant ViewComponent::SystemTestControllerNefariousPathError (NameError)
rescue_from ViewComponent::SystemTestControllerNefariousPathError, with: :render_not_found
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expected behavior
Rails.application.initialize! succeeds and the ViewComponentsSystemTestController class body loads cleanly during eager loading.
Actual behavior
Booting the application raises NameError: uninitialized constant ViewComponent::SystemTestControllerNefariousPathError from inside the gem itself, before any host application code executes.
The 4.9.0 changes to app/controllers/view_components_system_test_controller.rb ) added a class-body reference:
rescue_from ViewComponent::SystemTestControllerNefariousPathError, with: :render_not_found
commit: da99314
That constant is defined in lib/view_component/errors.rb, which is required only via lib/view_component/base.rb:9. ViewComponent::Base itself is registered as autoload in lib/view_component.rb, so errors.rb is not loaded eagerly at gem-require time. When Zeitwerk eager-loads the host application, it descends into the engine's app/controllers/ and loads view_components_system_test_controller.rb before anything has triggered loading ViewComponent::Base — so the constant lookup at the rescue_from line fails.
In 4.8.0 the same error class was only referenced inside a method body (raise ViewComponent::SystemTestControllerNefariousPathError unless ...), so resolution was deferred until request time. Moving the reference into the class body in 4.9.0 introduced the regression.
The smallest, most local fix is to declare the dependency at the top of the controller, mirroring the existing pattern in lib/view_component/base.rb:9:
--- a/app/controllers/view_components_system_test_controller.rb
+++ b/app/controllers/view_components_system_test_controller.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "view_component/errors"
+
class ViewComponentsSystemTestController < ActionController::Base # :nodoc:
if Rails.env.test?
before_action :validate_file_path
I verified locally that this clears the boot failure. A more structural alternative would be to require "view_component/errors" from lib/view_component/engine.rb so that all ViewComponent::*Error constants are resolvable from any class body in the gem. Happy to send a PR for whichever approach you prefer! 👍🏻
Backtrace
view_component-4.9.0/app/controllers/view_components_system_test_controller.rb:11:in '<class:ViewComponentsSystemTestController>': uninitialized constant ViewComponent::SystemTestControllerNefariousPathError (NameError)
rescue_from ViewComponent::SystemTestControllerNefariousPathError, with: :render_not_found
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
from view_component-4.9.0/app/controllers/view_components_system_test_controller.rb:3:in '<main>'
from ruby/3.4.0/bundled_gems.rb:82:in 'Kernel.require'
from bootsnap-1.23.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:33:in 'Kernel#require'
from zeitwerk-2.7.5/lib/zeitwerk/core_ext/kernel.rb:26:in 'Kernel#require'
from zeitwerk-2.7.5/lib/zeitwerk/cref.rb:62:in 'Module#const_get'
from zeitwerk-2.7.5/lib/zeitwerk/cref.rb:62:in 'Zeitwerk::Cref#get'
from zeitwerk-2.7.5/lib/zeitwerk/loader/eager_load.rb:171:in 'block in Zeitwerk::Loader::EagerLoad#actual_eager_load_dir'
from zeitwerk-2.7.5/lib/zeitwerk/loader/file_system.rb:32:in 'block in Zeitwerk::Loader::FileSystem#ls'
from zeitwerk-2.7.5/lib/zeitwerk/loader/file_system.rb:26:in 'Array#each'
from zeitwerk-2.7.5/lib/zeitwerk/loader/file_system.rb:26:in 'Zeitwerk::Loader::FileSystem#ls'
from zeitwerk-2.7.5/lib/zeitwerk/loader/eager_load.rb:166:in 'Zeitwerk::Loader::EagerLoad#actual_eager_load_dir'
from zeitwerk-2.7.5/lib/zeitwerk/loader/eager_load.rb:17:in 'block (2 levels) in Zeitwerk::Loader::EagerLoad#eager_load'
...
from railties-8.1.3/lib/rails/application/finisher.rb:79:in 'block in <module:Finisher>'
from railties-8.1.3/lib/rails/initializable.rb:24:in 'BasicObject#instance_exec'
from railties-8.1.3/lib/rails/initializable.rb:24:in 'Rails::Initializable::Initializer#run'
from railties-8.1.3/lib/rails/application.rb:442:in 'Rails::Application#initialize!'
from config/environment.rb:5:in '<main>'
System configuration
Rails version: 8.1.3
Ruby version: 3.4.4
Gem version: view_component 4.9.0 (zeitwerk 2.7.5, bootsnap 1.23.0)
Steps to reproduce
gem "view_component", "4.9.0"to a Rails 8 application.config.eager_load = truein the test environment used for bootingRAILS_ENV=test bin/rails zeitwerk:checkorRAILS_ENV=test CI=1 bin/rails testExpected behavior
Rails.application.initialize!succeeds and theViewComponentsSystemTestControllerclass body loads cleanly during eager loading.Actual behavior
Booting the application raises
NameError: uninitialized constant ViewComponent::SystemTestControllerNefariousPathErrorfrom inside the gem itself, before any host application code executes.The 4.9.0 changes to
app/controllers/view_components_system_test_controller.rb) added a class-body reference:commit: da99314
That constant is defined in
lib/view_component/errors.rb, which is required only vialib/view_component/base.rb:9.ViewComponent::Baseitself is registered asautoloadinlib/view_component.rb, soerrors.rbis not loaded eagerly at gem-require time. When Zeitwerk eager-loads the host application, it descends into the engine'sapp/controllers/and loadsview_components_system_test_controller.rbbefore anything has triggered loadingViewComponent::Base— so the constant lookup at therescue_fromline fails.In 4.8.0 the same error class was only referenced inside a method body (
raise ViewComponent::SystemTestControllerNefariousPathError unless ...), so resolution was deferred until request time. Moving the reference into the class body in 4.9.0 introduced the regression.The smallest, most local fix is to declare the dependency at the top of the controller, mirroring the existing pattern in
lib/view_component/base.rb:9:I verified locally that this clears the boot failure. A more structural alternative would be to
require "view_component/errors"fromlib/view_component/engine.rbso that allViewComponent::*Errorconstants are resolvable from any class body in the gem. Happy to send a PR for whichever approach you prefer! 👍🏻Backtrace
System configuration
Rails version: 8.1.3
Ruby version: 3.4.4
Gem version: view_component 4.9.0 (zeitwerk 2.7.5, bootsnap 1.23.0)