Open jeromedalbert opened 3 months ago
Closing, as the original issue is not an issue any more.
Reopening, as the issue reappeared on the latest Rails main.
Sorry for the ping @gmcgibbon, mentioning you just in case you recommend a better fix.
Ideally, we shouldn't need to load routes manually with the latest approach. There's no stacktrace in the issue. Please provide one and I'll see what I can do.
Ok, I have updated the issue with the fullest backtrace I could produce.
Here is my understanding of what the problem is.
With deferred route drawing, routes are lazy loaded for Rails environments that have config.eager_load
disabled, which is the case by default for the development and test environments.
Basically by default for the test environment, whenever a route is visited, routes get lazy loaded. Devise's mappings aren't loaded when the test starts, they are only loaded when the test visits a route, because Devise's entry point is defined in the route files with devise_for or devise_scope.
But in integration and controller tests you typically call the Devise sign_in
helper before visiting the route. At this point Devise mappings and scopes aren't loaded yet, so Devise errors out.
This seems like a particular case that is very specific to Devise. I don't think many gems define their helper methods on route load.
Yeah it is basically that https://github.com/heartcombo/devise/blob/a259ff3c28912a27329727f4a3c2623d3f5cb6f2/lib/devise/rails/routes.rb#L243 happens when routes are loaded, and mappings are global. We only really need to consider controller tests because https://github.com/heartcombo/devise/blob/a259ff3c28912a27329727f4a3c2623d3f5cb6f2/lib/devise/test/controller_helpers.rb#L7-L8 integration tests aren't meant to use this API.
I think it makes sense to move the mapping management to the route set, or to add a route load line to https://github.com/heartcombo/devise/blob/a259ff3c28912a27329727f4a3c2623d3f5cb6f2/lib/devise/mapping.rb#L35.
We only really need to consider controller tests
I think we need to consider both controller tests and integration tests because sign_in
is defined similarly in integration_helpers.rb
:
And as shown in the attached issue, the test fails for an ActionDispatch::IntegrationTest
integration test (I also separately checked that it fails for controller tests).
I think it makes sense to move the mapping management to the route set, or to add a route load line
This PR implements your second suggestion as a quick/easy fix ~by doing Rails.application.try(:reload_routes_unless_loaded)
. Although it is marked as :nodoc:
in the Rails source code, which suggests that it is a private API and that it is not OK to use it outside of Rails. I guess this is on purpose? If so I can update the PR to use the slightly longer Rails.application.routes_reloader.try(:execute_unless_loaded)
instead, which is a public API. I guess Rails doesn't need too many public APIs for this anyways, one is good enough, if at all.~ _(Edit: this PR now uses execute_unless_loaded
from the public API to be on the safe side)_
Otherwise your first suggestion is to move the mapping management to the route set, but I am not familiar with this part and it seems like a more involved fix/refactor for Devise. But if this is the preferred way, it's all good if someone else wants to make a new PR.
Update: I have changed this PR to use the public execute_unless_loaded
API to be on the safe side.
Fixes https://github.com/heartcombo/devise/issues/5705.
Notes:
sign_in
andsign_out
controller and integration test helpers all callDevise::Mapping.find_scope!
, so that method seemed like a good place to lazy load routes if they were not already loaded.try
to keep backwards compatibility sincereload_routes_unless_loaded
only exists in Rails 8.