Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit loading of ActiveStorage routes #51641

Closed
wants to merge 4 commits into from

Conversation

justinko
Copy link
Contributor

@justinko justinko commented Apr 23, 2024

Fixes #31228

Motivation / Background

Users are creating a "catchall" route in routes.rb that will always precede the ActiveStorage routes. Railtie routes that are loaded after the app's routes are getting caught in it.

Detail

Moving the ActiveStorage routes from Rails.applications.routes to ActiveStorage::Engine.routes ensures they precede any route defined in Rails.application.routes.

If you a user wants explicit loading of the routes, they can do the following:

config.active_storage.draw_routes = false

# in routes.rb
mount ActiveStorage::Engine => "/my_storage"

Additional information

Should we do this for other engines as well?

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

@zzak
Copy link
Member

zzak commented Apr 23, 2024

I've been thinking about this, and I'd rather extract it to a method and call it, than use the file path. Since that also makes it easier to test 🙏

@justinko
Copy link
Contributor Author

@zzak I pushed a concept.

Were you thinking more along the lines of:

module ActiveStorage
  module Routes
    delf self.apply(router)
      router.instance_eval do
        # add the routes here
      end
    end
  end
end

# from routes.rb
ActiveStorage::Routes.apply(self) # or could have ActiveStorage.routes call this

@zzak
Copy link
Member

zzak commented Apr 24, 2024

You shouldn't need instance_eval, since you can do router.draw { scope ActiveStorage.routes_prefix { ... } }.

I've only tested this in very limited context though, so some diligence is required 🙏

@justinko
Copy link
Contributor Author

@justinko
Copy link
Contributor Author

Another option is router.scope ActiveStorage.routes_prefix do

@zzak
Copy link
Member

zzak commented Apr 24, 2024

Can you also update the source_location for these tests?
https://github.com/rails/rails/blob/main/railties/test/commands/routes_test.rb#L341-L391

That should make the build green.

@zzak
Copy link
Member

zzak commented Apr 24, 2024

If it doesn't make a difference maybe router.scope <prefix> {} does look nicer

@zzak
Copy link
Member

zzak commented Apr 24, 2024

Can you squash your commits please?

@justinko justinko force-pushed the activestorage-routes branch 2 times, most recently from 7cf7776 to 78dd8dc Compare April 24, 2024 20:15
@zzak
Copy link
Member

zzak commented Apr 24, 2024

Can you update the PR description to reflect the current state too, please?

@justinko
Copy link
Contributor Author

Done. Thanks for your patience! What's left? Change log?

@zzak
Copy link
Member

zzak commented Apr 24, 2024

Yeah, I'm on the fence whether it should be documented at all.

Let's see what others think if we can get a review 🙏

@zzak
Copy link
Member

zzak commented Apr 24, 2024

Saw this suggested in the other ticket, but I wonder if we should instead follow these conventions:
https://edgeguides.rubyonrails.org/engines.html#generating-an-engine

Mainly:

ActiveStorage::Engine.routes.draw do
  # routes
end

@justinko
Copy link
Contributor Author

Won't work because the routes use the direct method. You'll get this error:

The direct method can't be used inside a routes scope block

You could get around that error by not isolating the engine ... but that'll open a whole other can of worms.

@justinko
Copy link
Contributor Author

Looks like I got it to work ... stay tuned.

@justinko
Copy link
Contributor Author

What do you think of this approach? https://github.com/rails/rails/compare/main...justinko:mount-activestorage?expand=1

The routes then look like this:

image

Seems cleaner ... and I think direct route helpers such as rails_blob_url could be deprecated in favor of active_storage.blob_url ... but that's a different subject.

@zzak
Copy link
Member

zzak commented Apr 26, 2024

After a cursory review, but I think that might be a breaking change. Though possibly having them isolated like that is semantically better.

Just based on having to add mounted_helpers to the tests raised a flag but that might be unavoidable.

I also noticed routes_prefix and draw_routes are missing from your implementation, unless I missed something.

Last thing, for me, I don't want to put any logic inside config/routes.rb except maybe the if draw_routes. My reasoning is that I want to be able to call ActiveStorage.draw_routes (or whatever) anywhere I want and get access to the helpers without having to initialize an app and call Rails.application.routes.url_helpers. 🙏

@rails-bot rails-bot bot added the actiontext label Apr 27, 2024
@justinko
Copy link
Contributor Author

Got some tests to fix.

@justinko
Copy link
Contributor Author

This approach isn't going to work because there's a hard dependency on rails_direct_uploads_url: https://github.com/rails/rails/blob/main/actiontext/app/helpers/action_text/tag_helper.rb#L38

I'm going to go with a different approach. What do you think of something like:

# in routes.rb
Rails.application.routes.draw do
  scope "/rails/active_storage" do
   import ActiveStorage::Engine.routes, as: "rails"
  end
end

This would just be another way to integrate an engine's routes without mounting.

If that's not a good idea then I'm going to move on from this issue.

@zzak
Copy link
Member

zzak commented Apr 30, 2024

I assume you mean scope ActiveStorage.routes_prefix { }, but I'm not sure what import .. as: provides over your initial instance_eval implementation.

If we're trying to get the routes to show up isolated in the routes command, or apply other Engine-y conventions, I think we should be cautious about what it is we're looking for and what problem it's solving.

I appreciate you taking the time to investigate this, but if all we solve is the open ticket and move the routes into a proper method, then I am happy and will help get a review as I'm very interested in this.

Thank you!

@justinko
Copy link
Contributor Author

justinko commented May 1, 2024

Sorry, let me be more clear.

The core problem is route load order, which is caused by an engine using Rails.application.routes.draw. Engines do this because they want their routes to exist in the main_app (e.g. rails_blob_url). If the routes are drawn in the engine, then users are forced to use engine_name.foo_url.

It would be great for an engine to draw its routes within the engine, and let the user decide whether to import those routes into main app or mount them.

So a user that wants to have control of when Active Storage routes are loaded, would do this:

Rails.application.config.active_storage.draw_routes = false

Rails.application.routes.draw do
  import ActiveStorage::Engine.routes
end

I haven't looked into feasibility, but in theory it's just merging RouteSet's.

@justinko
Copy link
Contributor Author

justinko commented May 2, 2024

I spiked import and it's doable but not worth the complexity.

I just thought of another potential solution. Another spike commencing.

@justinko
Copy link
Contributor Author

justinko commented May 2, 2024

Okay, I really like this solution: justinko@180a8cf

No breaking changes and it allows mount ActiveStorage::Engine => "/foo"

All it does is create 2 separate sets of routes, one for Rails.application.routes.draw and the other under ActiveStorage::Engine.routes.draw.

If they choose to mount, then they'll need to add this to their controller:

def rails_direct_uploads_url
  active_storage.direct_uploads_url
end

If you prefer it, then I'll clean it up and get a pull ready.

@justinko
Copy link
Contributor Author

justinko commented May 2, 2024

If you prefer ActiveStorage::Routes.apply(self) then we can go back to that, but we won't be able to use ActiveStorage::Engine.routes.draw.

@justinko
Copy link
Contributor Author

justinko commented May 2, 2024

Starting over with a fresh ActiveStorage::Routes.apply(self)

@justinko justinko closed this May 2, 2024
@justinko justinko deleted the activestorage-routes branch May 2, 2024 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rails Router Catchall Picks Up ActiveStorage Routes
2 participants