While working on a Rails app recently, I stumbled across a frustrating bug that only appeared in the test environment. Everything worked perfectly in the development console. Models loaded fine. Associations were intact. My concern methods behaved exactly as expected. But then my test suite started throwing mysterious NoMethodError
s—complaining that an ActiveRecord model was missing associations it clearly had.
Here’s the story—and how I found the unexpected culprit: a name collision with a Ruby gem.
The Setup
In my application, I created a concern named Addressable
to encapsulate some reusable address logic:
# app/models/concerns/addressable.rb
module Addressable
extend ActiveSupport::Concern
included do
has_many :addresses, as: :addressable, dependent: :destroy
accepts_nested_attributes_for :addresses, allow_destroy: true, reject_if: :all_blank
end
def full_address
addresses.last&.to_s || "No address available"
end
end
Then I included this concern in my Estate
model:
class Estate < ApplicationRecord
include BelongsToOrganisation
include Addressable
include ActiveAccountingPeriod
has_many :blocks
has_many :properties, through: :blocks
end
Everything worked flawlessly in the Rails console. I could create estates, associate addresses, and call full_address
. But in my tests, I started getting this:
NoMethodError: undefined method `addresses' for #<Estate ...>
Debugging the Ghost
At first, I assumed it was a lazy-loading issue in test. Maybe the concern hadn’t loaded yet? I added Rails.application.eager_load!
to my test setup. No change. I tried explicitly requiring all concern files. Still broken.
I added this debug line:
puts Estate.ancestors
To my surprise, the Addressable
module was nowhere in the ancestor chain—even though I had clearly written and included it.
So what was this Addressable
?
The Hidden Culprit: The addressable
Gem
After some head-scratching and a puts Addressable
in the test environment, I finally saw the issue:
Addressable # => Addressable::URI
Bingo.
Rails (and Ruby in general) had loaded the addressable
gem before loading my concern. That gem defines a top-level Addressable
module for URI parsing. Because of the name conflict, my local Addressable
concern was being silently overshadowed in the test environment—likely due to eager loading or load order quirks.
That’s why the console (where code loads lazily) worked fine, but the test suite (where dependencies load eagerly or in different order) didn’t.
The Fix: Rename Your Concern
Once I renamed my concern to avoid the collision, everything started working again:
# app/models/concerns/has_addresses.rb
module HasAddresses
extend ActiveSupport::Concern
included do
has_many :addresses, as: :addressable
end
end
And in the model:
class Estate < ApplicationRecord
include HasAddresses
end
Alternatively, I could’ve namespaced it to avoid polluting the top-level constant space:
# app/models/concerns/concerns/addressable.rb
module Concerns
module Addressable
extend ActiveSupport::Concern
...
end
end
And then:
include Concerns::Addressable
Takeaways
- Avoid using concern names that match top-level gem constants. Even common-sounding names like
Addressable
,JSON
, orURI
can create conflicts. - Be explicit with module namespaces. It prevents accidental collisions and clarifies intent.
- Test environment bugs often stem from autoloading differences. Development is lazy. Test is eager. Order matters.
- Use reflection and debugging tools.
Object.const_source_location(:Addressable)
andModel.ancestors
are your friends.
Final Thoughts
This was a subtle bug that cost me more time than I’d like to admit. But it’s also a great reminder: in Ruby and Rails, naming matters. A lot.
If you ever hit a situation where your code works in development but fails in test, and nothing makes sense—check your constants. You might not be loading what you think you are.
Happy coding!