Skip to content

Rails::Application in Rails::Engine.descendants causing undefined method 'root' error during Spring v4.3.0 preloading #737

Open
@aaronskiba

Description

@aaronskiba

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions