Description
Observation:
When using Spring v4.3.0 with Rails v7.1.5.1, the following error is encountered during preloading when starting the rails console:
$ bin/spring stop && rails c
Spring is not running
/usr/share/rvm/gems/ruby-3.0.5@dmp/gems/railties-7.1.5.1/lib/rails/railtie.rb:228:in `method_missing': undefined method `root' for Rails::Application:Class (NoMethodError)
from /usr/share/rvm/gems/ruby-3.0.5@dmp/gems/spring-4.3.0/lib/spring/application.rb:128:in `block in preload'
from /usr/share/rvm/gems/ruby-3.0.5@dmp/gems/spring-4.3.0/lib/spring/application.rb:127:in `each'
from /usr/share/rvm/gems/ruby-3.0.5@dmp/gems/spring-4.3.0/lib/spring/application.rb:127:in `preload'
from /usr/share/rvm/gems/ruby-3.0.5@dmp/gems/spring-4.3.0/lib/spring/application.rb:176:in `serve'
from /usr/share/rvm/gems/ruby-3.0.5@dmp/gems/spring-4.3.0/lib/spring/application.rb:158:in `block in run'
from /usr/share/rvm/gems/ruby-3.0.5@dmp/gems/spring-4.3.0/lib/spring/application.rb:152:in `loop'
from /usr/share/rvm/gems/ruby-3.0.5@dmp/gems/spring-4.3.0/lib/spring/application.rb:152:in `run'
from /usr/share/rvm/gems/ruby-3.0.5@dmp/gems/spring-4.3.0/lib/spring/application/boot.rb:25:in `<top (required)>'
from <internal:/usr/share/rvm/rubies/ruby-3.0.5/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
from <internal:/usr/share/rvm/rubies/ruby-3.0.5/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
from -e:1:in `<main>'
The error occurs in the Spring preloader, specifically when iterating through Rails::Engine.descendants
in the Spring application boot process (see line 127 in the Spring code).
Debugging
The following modification to the Spring preloader code reveals which engine(s) do not respond to the root method:
if defined?(Rails) && Rails.application
watcher.add Rails.application.paths["config/initializers"]
Rails::Engine.descendants.each do |engine|
if !engine.respond_to?(:root)
puts "#{engine} does not respond to root"
elsif engine.root.to_s.start_with?(Rails.root.to_s)
watcher.add engine.paths["config/initializers"].expanded
end
end
watcher.add Rails.application.paths["config/database"]
if secrets_path = Rails.application.paths["config/secrets"]
watcher.add secrets_path
end
end
Running this revealed the following output:
$ bin/spring stop && rails c
Spring is not running
Rails::Application does not respond to root
Running via Spring preloader in process 2199565
Loading development environment (Rails 7.1.5.1)
3.0.5 :001 >
Cause
The issue arises because Rails::Application
is part of Rails::Engine.descendants
, but it does not respond to the root method as other Rails engines do. This results in the NoMethodError
when Spring tries to call root on it.
Suggested Solution
Modify the Spring preloader code to check if the engine responds to root before calling it. Specifically, update the block that iterates through Rails::Engine.descendants
as follows:
Rails::Engine.descendants.each do |engine|
if engine.respond_to?(:root) && engine.root.to_s.start_with?(Rails.root.to_s)
watcher.add engine.paths["config/initializers"].expanded
end
end
This ensures that Rails::Application
(and any other engines that do not respond to root) are safely excluded from the root method call.