spring icon indicating copy to clipboard operation
spring copied to clipboard

rails + rspec + spring + eager_load = uninitialized constant

Open radarek opened this issue 8 years ago • 6 comments
trafficstars

When I set eager_load = true in config/environments/test.rb then after spring is loaded and any file is changed then running specs again will fail with a message like "uninitialized constant User (NameError)".

I created simple rails project repository to reproduce it: https://github.com/radarek/rails_rspec_spring_eager_load_bug. It's pure rails project. I added only gems rspec-rails and spring-commands-rspec and generated User model. When I run bin/rspec specs pass, running it again still gives success. But after modifying app/models/user.rb file and running it again it will fail with a message user_spec.rb:3:in <top (required)>': uninitialized constant User (NameError)`.

I know that doesn't have to be a problem with spring itself but it's hard to guess what it causes.

Steps to reproduce:

$ git clone [email protected]:radarek/rails_rspec_spring_eager_load_bug.git
$ cd rails_rspec_spring_eager_load_bug
$ bin/rails db:migrate RAILS_ENV=test
$ bin/rspec
Running via Spring preloader in process 81040
.

Finished in 0.00345 seconds (files took 0.38525 seconds to load)
1 example, 0 failures
$ bin/rspec
Running via Spring preloader in process 81140
.

Finished in 0.00326 seconds (files took 0.3545 seconds to load)
1 example, 0 failures
$ echo "# comment" >> app/models/user.rb
$ bin/rspec
Running via Spring preloader in process 81305
/Users/radarek/programming/github/radarek/rails_rspec_spring_eager_load_bug/spec/models/user_spec.rb:3:in `<top (required)>': uninitialized constant User (NameError)
	from /Users/radarek/.rbenv/gems/2.4.0/gems/rspec-core-3.5.4/lib/rspec/core/configuration.rb:1435:in `load'
	from /Users/radarek/.rbenv/gems/2.4.0/gems/rspec-core-3.5.4/lib/rspec/core/configuration.rb:1435:in `block in load_spec_files'
	from /Users/radarek/.rbenv/gems/2.4.0/gems/rspec-core-3.5.4/lib/rspec/core/configuration.rb:1433:in `each'
	from /Users/radarek/.rbenv/gems/2.4.0/gems/rspec-core-3.5.4/lib/rspec/core/configuration.rb:1433:in `load_spec_files'
	from /Users/radarek/.rbenv/gems/2.4.0/gems/rspec-core-3.5.4/lib/rspec/core/runner.rb:100:in `setup'
	from /Users/radarek/.rbenv/gems/2.4.0/gems/rspec-core-3.5.4/lib/rspec/core/runner.rb:86:in `run'
	from /Users/radarek/.rbenv/gems/2.4.0/gems/rspec-core-3.5.4/lib/rspec/core/runner.rb:71:in `run'
	from /Users/radarek/.rbenv/gems/2.4.0/gems/rspec-core-3.5.4/lib/rspec/core/runner.rb:45:in `invoke'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rspec-core-3.5.4/exe/rspec:4:in `<top (required)>'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-commands-rspec-1.0.4/lib/spring/commands/rspec.rb:18:in `load'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-commands-rspec-1.0.4/lib/spring/commands/rspec.rb:18:in `call'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-2.0.1/lib/spring/command_wrapper.rb:38:in `call'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-2.0.1/lib/spring/application.rb:191:in `block in serve'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-2.0.1/lib/spring/application.rb:161:in `fork'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-2.0.1/lib/spring/application.rb:161:in `serve'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-2.0.1/lib/spring/application.rb:131:in `block in run'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-2.0.1/lib/spring/application.rb:125:in `loop'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-2.0.1/lib/spring/application.rb:125:in `run'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/spring-2.0.1/lib/spring/application/boot.rb:19:in `<top (required)>'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/2.4.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Users/radarek/.rbenv/versions/2.4.0/lib/ruby/2.4.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from -e:1:in `<main>'

radarek avatar Apr 04 '17 09:04 radarek

I have almost same problem, but rspec throws error even on second run without modifying any files. I faced with this problem after enabling eager_loading for test environment

drakmail avatar Jul 10 '17 10:07 drakmail

Apparently this is due to Rails 5 disabling autoloading (and thus auto-reloading) of constants when config.eager_load = true.

However, thanks to this article I learned that it's possible to re-enable that behaviour using config.enable_dependency_loading = true. From the Rails guides:

config.enable_dependency_loading: when true, enables autoloading, even if the application is eager loaded and config.cache_classes is set as true. Defaults to false.

With that line added to test.rb, your reproduction steps start working again. :) Thanks for creating the sample app!

lime avatar Nov 30 '17 21:11 lime

@radarek @lime An alternative to enable_dependency_loading is to just turn eager_load off: https://github.com/timdiggins/rails_rspec_spring_eager_load_bug/tree/no-eager-loading.

It doesn't AFAICT gain anything to turn eager_load on (despite the very old (2012) message about turning it on when using a pre-loader (spring) in the generated config: https://github.com/rails/rails/blame/master/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt#L12 https://github.com/rails/rails/commit/e6747d87f3a061d153215715d56acbb0be20191f). Reading the history of https://github.com/rails/spring/blame/master/lib/spring/application.rb has helped, but this is mostly very old (older than the changes to eager_load)

However I'm still working through this approach in practice. It may mean you need to explicitly spring stop more often. Alternatively it might mean you need to set cache_classes = false (and there might be a performance hit if so).

timdiggins avatar Jan 22 '18 08:01 timdiggins

I find that config.enable_dependency_loading sometimes not work correctly. If autoloading has been working fine for you, then the most straighforward and run in predictable manner is diffing the eager_load_paths and auto_load_paths and add the diff to eager_load_paths. That's what I do in my project with external engines and unconventional code locations.

thaohoangc3 avatar May 19 '20 15:05 thaohoangc3

 load_disparities = (ActiveSupport::Dependencies.autoload_paths - config.eager_load_paths).uniq
 config.eager_load_paths += load_disparities unless Rails.env.development?

I prefer to keep autoload in test to true, to keep cfg between test and prod the same

thaohoangc3 avatar May 19 '20 15:05 thaohoangc3

I'm using minitest in a Rails engine and encountered a similar issue. None of the proposed solutions worked for me. I found that adding my autoload paths to ActiveSupport::Dependencies.autoload_paths did work for me:

ActiveSupport::Dependencies.autoload_paths += [
  File.absolute_path(File.join(__dir__, 'support/'))
]

chriscz avatar Nov 24 '20 11:11 chriscz