Railtie inclusion fails but only from RSpec. (undefined method '+' for class 'Date')
hakunin opened this issue · 9 comments
What Ruby, Rails and RSpec versions are you using?
Ruby version: 2.7.6 (confirmed also fails under 2.7.8)
Rails version: 6.1.5
RSpec version: 3.13.0
Observed behaviour
I just added rspec-rails to an existing project which previously used minitest.
Throws an error during require rails_helper
but deep from within railties.
Expected behaviour
Not an error.
Can you provide an example reproduction?
Going to work on a repro this week.
This is a bit of a shot in the dark, but I wanted to file this as I didn't even find any mention of this error on the web.
The error comes from the second line config/application.rb
, which contains:
require_relative 'boot' # commenting this out does not change anything
require 'rails/all' # this is where it fail from
The error happens before any gems (apart from rspec-core and rspec) are loaded, so no initializers are run either.
I tried the latest ruby 2.7.8 patch version and still getting the same error.
This could potentially be just an issue with the order of files being loaded?
Hoping someone who had a similar issue sees this, I am going to try to reproduce this during this week/end.
➜ DISABLE_SPRING=1 rspec --backtrace
An error occurred while loading ./spec/services/company_queries/earnings_spec.rb.
Failure/Error: require_relative '../config/environment'
NameError:
undefined method `+' for class `Date'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/core_ext/date/calculations.rb:97:in `alias_method'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/core_ext/date/calculations.rb:97:in `<class:Date>'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/core_ext/date/calculations.rb:10:in `<top (required)>'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `block in require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:299:in `load_dependency'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/core_ext/time/calculations.rb:8:in `<top (required)>'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `block in require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:299:in `load_dependency'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/core_ext/numeric/time.rb:4:in `<top (required)>'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `block in require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:299:in `load_dependency'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/core_ext/integer/time.rb:4:in `<top (required)>'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `block in require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:299:in `load_dependency'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/globalid-1.0.0/lib/global_id/railtie.rb:8:in `<top (required)>'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `block in require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:299:in `load_dependency'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activejob-6.1.5/lib/active_job/railtie.rb:3:in `<top (required)>'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `block in require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:299:in `load_dependency'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activestorage-6.1.5/lib/active_storage/engine.rb:5:in `<top (required)>'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `block in require'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:299:in `load_dependency'
# ./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/dependencies.rb:332:in `require'
# ./.bundle/ruby/2.7.0/gems/railties-6.1.5/lib/rails/all.rb:21:in `block in <top (required)>'
# ./.bundle/ruby/2.7.0/gems/railties-6.1.5/lib/rails/all.rb:19:in `each'
# ./.bundle/ruby/2.7.0/gems/railties-6.1.5/lib/rails/all.rb:19:in `<top (required)>'
# ./config/application.rb:2:in `require'
# ./config/application.rb:2:in `<top (required)>'
# ./config/environment.rb:2:in `require_relative'
# ./config/environment.rb:2:in `<top (required)>'
# ./spec/rails_helper.rb:5:in `require_relative'
# ./spec/rails_helper.rb:5:in `<top (required)>'
# ./spec/services/company_queries/earnings_spec.rb:16:in `require'
# ./spec/services/company_queries/earnings_spec.rb:16:in `<top (required)>'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb:2138:in `load'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb:2138:in `load_file_handling_errors'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb:1638:in `block in load_spec_files'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb:1636:in `each'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb:1636:in `load_spec_files'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:102:in `setup'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:86:in `run'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:71:in `run'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:45:in `invoke'
# ./.bundle/ruby/2.7.0/gems/rspec-core-3.13.0/exe/rspec:4:in `<top (required)>'
# ./bin/rspec:27:in `load'
# ./bin/rspec:27:in `<main>'
No examples found.
Finished in 0.00002 seconds (files took 0.57334 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples
That’s weird. On 2.7.8 in irb -f
after require 'date'
I can Date.today + 1
. Can you?
Wondering what you’ll be able to find.
irb -f
Yep, works fine in irb:
➜ irb -f
irb(main):001:0> require 'date'
=> true
irb(main):002:0> Date.today + 1
=> #<Date: 2024-07-02 ((2460494j,0s,0n),+0s,2299161j)>
The error happens at:
alias_method :plus_without_duration, :+ # <-- here
alias_method :+, :plus_with_duration
Which is strange.
Grepped through our gems, disabled all the gems that extend the Date class, but didn't help.
Checked if the file where it fails is being loaded twice - and it isn't 🙃
Can you set a binding.irb breakpoint just before that alias_method to see if new + 1
works?
Can you set a binding.irb breakpoint just before that alias_method to see if
new + 1
works?
Yes, it fails on the missing plus.
I grepped the whole codebase and gems for the alias_method
with a :+
and the only thing I am getting is:
grep -r "alias_method :+" . -n
./.bundle/ruby/2.7.0/gems/nokogiri-1.15.5-x86_64-linux/lib/nokogiri/xml/node_set.rb:431: alias_method :+, :|
./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/core_ext/time/calculations.rb:289: alias_method :+, :plus_with_duration
./.bundle/ruby/2.7.0/gems/activesupport-6.1.5/lib/active_support/core_ext/date/calculations.rb:101: alias_method :+, :plus_with_duration
./.bundle/ruby/2.7.0/gems/xpath-3.2.0/lib/xpath/dsl.rb:65: alias_method :+, :union
./.bundle/ruby/2.7.0/gems/addressable-2.8.5/lib/addressable/uri.rb:1971: alias_method :+, :join
So what's happening doesn't make a lot of sense.
I checked RUBY_VERSION => "2.7.6"
Solution
Finally, I found out that we had a lib/date.rb
file, which somehow loaded the first time require 'date'
was run. (even before railties?)
But this only happens when running rspec, not when the rails app is run. Thank you so much for helping me figure this out!
Turns out the project I work on has lib/<gem>/<ext-file-with-same-name-as-original>.rb
in a few places even.
And Rails has no problem with it, because it's only loaded explicitly from initializers.
Makes me wonder, what is the difference that makes RSpec load these files specifically from require statements?
Got it. Happy to have helped.
Is there anything actionable left?
Makes me wonder, what is the difference that makes RSpec load these files specifically from require statements?
RSpec adds ./lib
and (a configurable) spec path to the load path, this allows people working on normal ruby project to do require 'name'
in specs, and similarly allows require 'spec_helper'
to work.
This convention is sometimes also used for monkey patching as you say like lib/<gem>/<ext-file-with-same-name-as-original>.rb
to override or backport a fix, which typically isn't an issue either, as these files usually have the same contents as the original.
If it works from Rails, I take it you were requiring relatively or with full path names? As e.g. require 'date'
will only work once. Personally I would avoid shaddowing file names unless you are doing a full monkey patch with original contents, to ensure it will work reliably across tools.