Bring back feature that allows loading external route iles:
= This feature existed back in 2012 5e7d6bba79
but got reverted with the incentive that there was a better approach.
After discussions, we agreed that it's a useful feature for apps
that have a really large set of routes.
Co-authored-by: Yehuda Katz <wycats@gmail.com>
This commit is contained in:
parent
66197a3a40
commit
33bf253282
|
@ -1,3 +1,26 @@
|
|||
* Bring back the feature that allows loading external route files from the router.
|
||||
|
||||
This feature existed back in 2012 but got reverted with the incentive that
|
||||
https://github.com/rails/routing_concerns was a better approach. Turned out
|
||||
that this wasn't fully the case and loading external route files from the router
|
||||
can be helpful for applications with a really large set of routes.
|
||||
Without this feature, application needs to implement routes reloading
|
||||
themselves and it's not straightforward.
|
||||
|
||||
```ruby
|
||||
# config/routes.rb
|
||||
|
||||
Rails.application.routes.draw do
|
||||
draw(:admin)
|
||||
end
|
||||
|
||||
# config/routes/admin.rb
|
||||
|
||||
get :foo, to: 'foo#bar'
|
||||
```
|
||||
|
||||
*Yehuda Katz*, *Edouard Chin*
|
||||
|
||||
* Fix system test driver option initialization for non-headless browsers.
|
||||
|
||||
*glaszig*
|
||||
|
|
|
@ -1607,6 +1607,22 @@ module ActionDispatch
|
|||
!parent_resource.singleton? && @scope[:shallow]
|
||||
end
|
||||
|
||||
def draw(name)
|
||||
path = @draw_paths.find do |_path|
|
||||
File.exist? "#{_path}/#{name}.rb"
|
||||
end
|
||||
|
||||
unless path
|
||||
msg = "Your router tried to #draw the external file #{name}.rb,\n" \
|
||||
"but the file was not found in:\n\n"
|
||||
msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
|
||||
route_path = "#{path}/#{name}.rb"
|
||||
instance_eval(File.read(route_path), route_path.to_s)
|
||||
end
|
||||
|
||||
# Matches a URL pattern to one or more routes.
|
||||
# For more information, see match[rdoc-ref:Base#match].
|
||||
#
|
||||
|
@ -2285,6 +2301,7 @@ module ActionDispatch
|
|||
|
||||
def initialize(set) #:nodoc:
|
||||
@set = set
|
||||
@draw_paths = set.draw_paths
|
||||
@scope = Scope.new(path_names: @set.resources_path_names)
|
||||
@concerns = {}
|
||||
end
|
||||
|
|
|
@ -334,7 +334,7 @@ module ActionDispatch
|
|||
|
||||
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
|
||||
attr_accessor :disable_clear_and_finalize, :resources_path_names
|
||||
attr_accessor :default_url_options
|
||||
attr_accessor :default_url_options, :draw_paths
|
||||
attr_reader :env_key, :polymorphic_mappings
|
||||
|
||||
alias :routes :set
|
||||
|
@ -366,6 +366,7 @@ module ActionDispatch
|
|||
self.named_routes = NamedRouteCollection.new
|
||||
self.resources_path_names = self.class.default_resources_path_names
|
||||
self.default_url_options = {}
|
||||
self.draw_paths = []
|
||||
|
||||
@config = config
|
||||
@append = []
|
||||
|
|
|
@ -1165,6 +1165,43 @@ video = Video.find_by(identifier: "Roman-Holiday")
|
|||
edit_video_path(video) # => "/videos/Roman-Holiday/edit"
|
||||
```
|
||||
|
||||
Breaking up *very* large route file into multiple small ones:
|
||||
-------------------------------------------------------
|
||||
|
||||
If you work in a large application with thousands of routes,
|
||||
a single `config/routes.rb` file can become cumbersome and hard to read.
|
||||
|
||||
Rails offers a way to break a gigantic single `routes.rb` file into multiple small ones using the `draw` macro.
|
||||
|
||||
```ruby
|
||||
# config/routes.rb
|
||||
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#bar'
|
||||
|
||||
draw(:admin) # Will load another route file located in `config/routes/admin.rb`
|
||||
end
|
||||
|
||||
# config/routes/admin.rb
|
||||
|
||||
namespace :admin do
|
||||
resources :comments
|
||||
end
|
||||
```
|
||||
|
||||
Calling `draw(:admin)` inside the `Rails.application.routes.draw` block itself will try to load a route
|
||||
file that has the same name as the argument given (`admin.rb` in this case).
|
||||
The file need to be located inside the `config/routes` directory or any sub-directory (i.e. `config/routes/admin.rb` , `config/routes/external/admin.rb`).
|
||||
|
||||
You can use the normal routing DSL inside the `admin.rb` routing file, **however** you shouldn't surround it with the `Rails.application.routes.draw` block like you did in the main `config/routes.rb` file.
|
||||
|
||||
### When to use and not use this feature
|
||||
|
||||
Drawing routes from external files can be very useful to organise a large set of routes into multiple organised ones. You could have a `admin.rb` route that contains all the routes for the admin area, another `api.rb` file to route API related resources etc...
|
||||
|
||||
However, you shouldn't abuse this feature as having too many route files make discoverability and understandability more difficult. Depending on the application, it might be easier for developers to have a single routing file even if you have few hundreds routes. You shouldn't try to create a new routing file for each category (admin, api ...) at all cost; the Rails routing DSL already offers a way to break routes in a organised manner with `namespaces` and `scopes`.
|
||||
|
||||
|
||||
Inspecting and Testing Routes
|
||||
-----------------------------
|
||||
|
||||
|
|
|
@ -5,13 +5,14 @@ require "active_support/core_ext/module/delegation"
|
|||
module Rails
|
||||
class Application
|
||||
class RoutesReloader
|
||||
attr_reader :route_sets, :paths
|
||||
attr_reader :route_sets, :paths, :external_routes
|
||||
attr_accessor :eager_load
|
||||
delegate :execute_if_updated, :execute, :updated?, to: :updater
|
||||
|
||||
def initialize
|
||||
@paths = []
|
||||
@route_sets = []
|
||||
@external_routes = []
|
||||
@eager_load = false
|
||||
end
|
||||
|
||||
|
@ -26,7 +27,13 @@ module Rails
|
|||
|
||||
private
|
||||
def updater
|
||||
@updater ||= ActiveSupport::FileUpdateChecker.new(paths) { reload! }
|
||||
@updater ||= begin
|
||||
dirs = @external_routes.each_with_object({}) do |dir, hash|
|
||||
hash[dir.to_s] = %w(rb)
|
||||
end
|
||||
|
||||
ActiveSupport::FileUpdateChecker.new(paths, dirs) { reload! }
|
||||
end
|
||||
end
|
||||
|
||||
def clear!
|
||||
|
|
|
@ -593,10 +593,13 @@ module Rails
|
|||
|
||||
initializer :add_routing_paths do |app|
|
||||
routing_paths = paths["config/routes.rb"].existent
|
||||
external_paths = self.paths["config/routes"].paths
|
||||
routes.draw_paths.concat(external_paths)
|
||||
|
||||
if routes? || routing_paths.any?
|
||||
app.routes_reloader.paths.unshift(*routing_paths)
|
||||
app.routes_reloader.route_sets << routes
|
||||
app.routes_reloader.external_routes.unshift(*external_paths)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ module Rails
|
|||
paths.add "config/initializers", glob: "**/*.rb"
|
||||
paths.add "config/locales", glob: "*.{rb,yml}"
|
||||
paths.add "config/routes.rb"
|
||||
paths.add "config/routes", glob: "**/*.rb"
|
||||
|
||||
paths.add "db"
|
||||
paths.add "db/migrate"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "pathname"
|
||||
|
||||
module Rails
|
||||
module Paths
|
||||
# This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system.
|
||||
|
@ -180,6 +182,14 @@ module Rails
|
|||
@paths
|
||||
end
|
||||
|
||||
def paths
|
||||
raise "You need to set a path root" unless @root.path
|
||||
|
||||
map do |p|
|
||||
Pathname.new(@root.path).join(p)
|
||||
end
|
||||
end
|
||||
|
||||
def extensions # :nodoc:
|
||||
$1.split(",") if @glob =~ /\{([\S]+)\}/
|
||||
end
|
||||
|
|
|
@ -51,6 +51,8 @@ module ApplicationTests
|
|||
assert_path @paths["config/locales"], "config/locales/en.yml"
|
||||
assert_path @paths["config/environment"], "config/environment.rb"
|
||||
assert_path @paths["config/environments"], "config/environments/development.rb"
|
||||
assert_path @paths["config/routes.rb"], "config/routes.rb"
|
||||
assert_path @paths["config/routes"], "config/routes"
|
||||
|
||||
assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first
|
||||
end
|
||||
|
|
|
@ -265,6 +265,30 @@ module ApplicationTests
|
|||
assert_equal "WIN", last_response.body
|
||||
end
|
||||
|
||||
test "routes drawing from config/routes" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
AppTemplate::Application.routes.draw do
|
||||
draw :external
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes/external.rb", <<-RUBY
|
||||
get ':controller/:action'
|
||||
RUBY
|
||||
|
||||
controller :success, <<-RUBY
|
||||
class SuccessController < ActionController::Base
|
||||
def index
|
||||
render plain: "success!"
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app "development"
|
||||
get "/success/index"
|
||||
assert_equal "success!", last_response.body
|
||||
end
|
||||
|
||||
{
|
||||
"development" => ["baz", "http://www.apple.com", "/dashboard"],
|
||||
"production" => ["bar", "http://www.microsoft.com", "/profile"]
|
||||
|
@ -307,7 +331,7 @@ module ApplicationTests
|
|||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#bar'
|
||||
draw :external
|
||||
get 'custom', to: 'foo#custom'
|
||||
get 'mapping', to: 'foo#mapping'
|
||||
|
||||
|
@ -316,6 +340,10 @@ module ApplicationTests
|
|||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes/external.rb", <<-RUBY
|
||||
get 'foo', to: 'foo#bar'
|
||||
RUBY
|
||||
|
||||
app(mode)
|
||||
|
||||
get "/foo"
|
||||
|
@ -329,7 +357,7 @@ module ApplicationTests
|
|||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#baz'
|
||||
draw :another_external
|
||||
get 'custom', to: 'foo#custom'
|
||||
get 'mapping', to: 'foo#mapping'
|
||||
|
||||
|
@ -338,6 +366,10 @@ module ApplicationTests
|
|||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes/another_external.rb", <<-RUBY
|
||||
get 'foo', to: 'foo#baz'
|
||||
RUBY
|
||||
|
||||
sleep 0.1
|
||||
|
||||
get "/foo"
|
||||
|
|
Loading…
Reference in New Issue