devise icon indicating copy to clipboard operation
devise copied to clipboard

Rails 8: route initialization messed up

Open miharekar opened this issue 1 year ago • 3 comments

Environment

  • Ruby 3.3.5
  • Rails Rails 8.0.0.beta1
  • Devise 4.9.4

Current behavior

A bit of story/context to help with discoverability of this issue if anyone is googling with similar symptoms.

When I upgraded my app to Rails 8 beta ActionCable stopped working with a peculiar message in web console:

connection.js:39 WebSocket connection to 'wss://visualizer.coffee/cable' failed

And indeed I saw a lot of ActionController::RoutingError (No route matches [GET] "/cable") in the logs.

At first I thought it's an issue with my hosting provider Fly, but then I was able to replicate this locally. Not in development environment though, but with RAILS_ENV=production PORT=3001 rails s I got those same errors.

I assumed something in Rails broke (it is beta after all) so I made a quick rails new app, and, damn it, it worked just fine in production env.

A couple of bundle opens later and pokings around I found this block which prepends the mounting of "/cable" or whatever the ActionCable mount_path is set to. With some further puts debugging I found that in the brand new app the block registers and executes while in my app it registers but never executes.

I added some puts debugs inside ActionDispatch#clear! and found that in the new app the ActionCable initializer is registered before first clear call, but in my app it happened after. So it has no chance to run the block. Culprit found.

Now I needed to know why this happens and I put some puts caller in there. The only diff was that one included devise-4.9.4/lib/devise/rails.rb:17 which is this piece of code with comment # Force routes to be loaded if we are doing any eager load.

I lack the deep knowledge of Devise to know how to proceed or what to do now, but here's how you can replicate it:

  1. rails new name_of_app with Rails version 8.0.0.beta1
  2. add devise to Gemfile and bundle
  3. run Rails in production with RAILS_ENV=production PORT=3001 rails s
  4. curl localhost:3001/cable

Without devise you get Page not found% response with these logs:

[0812aa3e-bfcd-41a5-958c-c27ab4b2a982] Started GET "/cable" for 127.0.0.1 at 2024-09-27 13:45:09 +0200
[0812aa3e-bfcd-41a5-958c-c27ab4b2a982] Started GET "/cable"[non-WebSocket] for 127.0.0.1 at 2024-09-27 13:45:09 +0200
[0812aa3e-bfcd-41a5-958c-c27ab4b2a982] Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: , HTTP_UPGRADE: )
[0812aa3e-bfcd-41a5-958c-c27ab4b2a982] Finished "/cable"[non-WebSocket] for 127.0.0.1 at 2024-09-27 13:45:09 +0200

as expected.

With devise you get 404 page with these logs:

[6795276c-a698-4bf8-bf9f-d4394a41e648] Started GET "/cable" for 127.0.0.1 at 2024-09-27 13:44:46 +0200
[6795276c-a698-4bf8-bf9f-d4394a41e648]
[6795276c-a698-4bf8-bf9f-d4394a41e648] ActionController::RoutingError (No route matches [GET] "/cable"):
[6795276c-a698-4bf8-bf9f-d4394a41e648]

With devise and reload_routes set to false in the initializer it also works, but probably it should work out of the box? 😅

Let me know if something isn't clear and/or how I can help out.

miharekar avatar Sep 27 '24 11:09 miharekar

Got bit by this one today. Thanks for this.

Can confirm, config.reload_routes = false in config/initializers/devise.rb fixed it for me.

excid3 avatar Oct 04 '24 23:10 excid3

Doing some digging, I think this is a regression in Rails related to this refactoring in Rails 8, not so much a bug in Devise. https://github.com/rails/rails/commit/cdb283566c5d5251e2f7e663e655e57e8e10abbc

@gmcgibbon would probably know best, but my hunch is that since the routes are already loaded due to this Devise flag, the after_initialize prepend saves the block to be used, but it should actually run it immediately since it's already loaded.

excid3 avatar Oct 05 '24 12:10 excid3

Wow you saved me @miharekar! Thank you.

nwarwick avatar Oct 10 '24 14:10 nwarwick

Setting config.reload_routes = false didn't fix it for me unfortunately, which indicates that we probably have another gem that is eager loading routes causing the changes that Chris pointed out to see that routes have already been loaded and therefore not reloading them

Forcing the routes to be reloaded after initialize is the only fix I've found so far. Specifically in config/environment.rb adding Rails.application.reload_routes! after Rails.application.initialize!

I was also unable to access /rails/info/routes in the browser.

Any suggestions for better fixes appreciated!

phil-6 avatar Oct 24 '24 07:10 phil-6

Hey all!

I've opened https://github.com/rails/rails/pull/53522 in Rails that fixes this issue as I believe it's Rails's bug, not Devise (even though I think reloading routes during before_eager_load feels a bit wrong. I think we need it for only small percent of users that needs Devise.mapping to be available during eager load process, so potentially we can look into removing it altogether in future)

nashby avatar Nov 02 '24 10:11 nashby

Closing this since https://github.com/rails/rails/pull/53522 was merged. Please let me know if it's still an issue with latest Rails's master.

nashby avatar Nov 06 '24 10:11 nashby

I just upgraded to d12a42d and it appears to be fixed for me.

kylekeesling avatar Nov 06 '24 18:11 kylekeesling

What's the release cycle on this project?

jasonperrone avatar Jan 10 '25 19:01 jasonperrone

I upgraded to Rails 8.0.2 and reload_routes = false actually broke the app in production. Undid that change and now it works ok.

jasonperrone avatar Apr 18 '25 14:04 jasonperrone