Steps to reproduce
-
Check out a Rails app that has spring inserted in the binstubs — for example, this example app:
https://github.com/TylerRick/exception_notification-rake_example_with_spring/tree/workaround_using_load_in_bin_rake
-
Run bin/rake check:exception
-
Observe that no email is sent
How it's supposed to work
Normally (without spring), what happens is:
- the
bin/rake binstub gets loaded first, and
- that is what triggers
rake.rb and rake/rake_module.rb (which defines Rake.application) to get loaded.
- Then
Bundler.require loads exception_notification-rake gem, which checks that Rake.application is defined and patches Rake::Application#display_error_message
This is evident from the following backtraces taken at those 2 points:
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/rake_module.rb:2:in `<top (required)>'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake.rb:44:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake.rb:44:in `<top (required)>'",
require "rake/rake_module"
"bin/rake:8:in `require'",
"bin/rake:8:in `<main>'"]
require 'rake'
(or if using global rake instead of bin/rake binstub:
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/rake_module.rb:4:in `<top (required)>'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake.rb:44:in `<top (required)>'",
require "rake/rake_module"
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/exe/rake:25:in `<top (required)>'",
"~/.gem/ruby/2.6.0/bin/rake:23:in `load'",
"~/.gem/ruby/2.6.0/bin/rake:23:in `<main>'"]
load Gem.activate_bin_path('rake', 'rake', version)
)
"exception_notification-rake/lib/exception_notifier/rake/rake_patch.rb:5:in `<top (required)>'",
"exception_notification-rake/lib/exception_notifier/rake.rb:2:in `require'",
"exception_notification-rake/lib/exception_notifier/rake.rb:2:in `<top (required)>'",
"exception_notification-rake/lib/exception_notification/rake.rb:3:in `require'",
"exception_notification-rake/lib/exception_notification/rake.rb:3:in `<top (required)>'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:95:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:95:in `rescue in block in require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:72:in `block in require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:65:in `each'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:65:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler.rb:114:in `require'",
"example_app/config/application.rb:7:in `<top (required)>'",
Bundler.require(*Rails.groups)
"example_app/Rakefile:4:in `require_relative'",
"example_app/Rakefile:4:in `<top (required)>'",
require_relative 'config/application'
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/rake_module.rb:31:in `load'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/rake_module.rb:31:in `load_rakefile'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/application.rb:703:in `raw_load_rakefile'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/application.rb:104:in `block in load_rakefile'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/application.rb:186:in `standard_exception_handling'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/application.rb:103:in `load_rakefile'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/application.rb:82:in `block in run'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/application.rb:186:in `standard_exception_handling'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/application.rb:80:in `run'",
"bin/rake:10:in `<main>'"]
How it works instead when using spring
But when using spring:
- spring's
preload loads exception_notifier-rake gem before bin/rake is loaded
exception_notification-rake checks if Rake.application is defined; since it is not, it doesn't patch Rake::Application#display_error_message and so this gem has no effect
- Then the
bin/rake binstub gets loaded,
- which triggers
rake.rb and rake/rake_module.rb (which defines Rake.application) to get loaded (too late).
Again, you can see this from the backtraces if you wish:
"exception_notification-rake/lib/exception_notifier/rake/rake_patch.rb:5:in `<top (required)>'",
"exception_notification-rake/lib/exception_notifier/rake.rb:2:in `require'",
"exception_notification-rake/lib/exception_notifier/rake.rb:2:in `<top (required)>'",
"exception_notification-rake/lib/exception_notification/rake.rb:3:in `require'",
"exception_notification-rake/lib/exception_notification/rake.rb:3:in `<top (required)>'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:95:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:95:in `rescue in block in require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:72:in `block in require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:65:in `each'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler/runtime.rb:65:in `require'",
"~/.rubies/ruby-2.6.0/lib/ruby/2.6.0/bundler.rb:114:in `require'",
"example_app/config/application.rb:7:in `<top (required)>'",
Bundler.require(*Rails.groups)
"ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:92:in `require'",
"ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:92:in `preload'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/rake_module.rb:2:in `<top (required)>'",
"~/.rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake.rb:44:in `<top (required)>'",
"example_app/bin/rake:8:in `<top (required)>'",
require 'rake'
"~/.gem/ruby/2.6.0/gems/spring-2.0.2/lib/spring/command_wrapper.rb:40:in `call'",
"~/.gem/ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:201:in `block in serve'",
Workaround
One workaround is to add this line to bin/rake:
require 'rake'
+load 'exception_notifier/rake/rake_patch.rb'
Rake.application.run
But a better solution is needed that doesn't require modifying the binstubs that spring automatically generated...
Steps to reproduce
Check out a Rails app that has spring inserted in the binstubs — for example, this example app:
https://github.com/TylerRick/exception_notification-rake_example_with_spring/tree/workaround_using_load_in_bin_rake
Run
bin/rake check:exceptionObserve that no email is sent
How it's supposed to work
Normally (without spring), what happens is:
bin/rakebinstub gets loaded first, andrake.rbandrake/rake_module.rb(which definesRake.application) to get loaded.Bundler.requireloadsexception_notification-rakegem, which checks thatRake.applicationis defined and patchesRake::Application#display_error_messageThis is evident from the following backtraces taken at those 2 points:
(or if using global
rakeinstead ofbin/rakebinstub:)
How it works instead when using spring
But when using spring:
preloadloadsexception_notifier-rakegem beforebin/rakeis loadedexception_notification-rakechecks ifRake.applicationis defined; since it is not, it doesn't patchRake::Application#display_error_messageand so this gem has no effectbin/rakebinstub gets loaded,rake.rbandrake/rake_module.rb(which definesRake.application) to get loaded (too late).Again, you can see this from the backtraces if you wish:
Workaround
One workaround is to add this line to
bin/rake:But a better solution is needed that doesn't require modifying the binstubs that spring automatically generated...