Skip to content

Commit 7ac3338

Browse files
committed
Defer route drawing to the first request, or when url_helpers called.
Executes the first routes reload in middleware, or when the route set url_helpers is called. Previously, this was executed unconditionally on boot, which can slow down boot time unnecessarily for larger apps with lots of routes.
1 parent 060d6ca commit 7ac3338

24 files changed

+374
-47
lines changed

actionpack/lib/action_dispatch/testing/assertions/routing.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ def with_routing(&block)
4646
def create_routes
4747
app = self.app
4848
routes = ActionDispatch::Routing::RouteSet.new
49-
rack_app = app.config.middleware.build(routes)
49+
middleware = app.config.middleware.dup
50+
middleware.delete(Rails::Rack::LoadRoutes) if defined?(Rails::Rack::LoadRoutes)
51+
rack_app = middleware.build(routes)
5052
https = integration_session.https?
5153
host = integration_session.host
5254

actionpack/test/abstract_unit.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ def env
3434
@_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test")
3535
end
3636

37+
def application; end
38+
3739
def root; end
3840
end
3941
end

actionpack/test/dispatch/routing_assertions_test.rb

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
require "rails/engine"
55
require "controller/fake_controllers"
66

7-
class SecureArticlesController < ArticlesController; end
7+
class SecureArticlesController < ArticlesController
8+
def index
9+
render(inline: "")
10+
end
11+
end
812
class BlockArticlesController < ArticlesController; end
913
class QueryArticlesController < ArticlesController; end
1014

@@ -276,6 +280,20 @@ class RoutingAssertionsControllerTest < ActionController::TestCase
276280

277281
class WithRoutingTest < ActionController::TestCase
278282
include RoutingAssertionsSharedTests::WithRoutingSharedTests
283+
284+
test "with_routing routes are reachable" do
285+
@controller = SecureArticlesController.new
286+
287+
with_routing do |routes|
288+
routes.draw do
289+
get :new_route, to: "secure_articles#index"
290+
end
291+
292+
get :index
293+
294+
assert_predicate(response, :ok?)
295+
end
296+
end
279297
end
280298
end
281299

@@ -295,6 +313,18 @@ class RoutingAssertionsIntegrationTest < ActionDispatch::IntegrationTest
295313

296314
class WithRoutingTest < ActionDispatch::IntegrationTest
297315
include RoutingAssertionsSharedTests::WithRoutingSharedTests
316+
317+
test "with_routing routes are reachable" do
318+
with_routing do |routes|
319+
routes.draw do
320+
get :new_route, to: "secure_articles#index"
321+
end
322+
323+
get "/new_route"
324+
325+
assert_predicate(response, :ok?)
326+
end
327+
end
298328
end
299329

300330
class WithRoutingSettingsTest < ActionDispatch::IntegrationTest

railties/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
* Defer route drawing to the first request, or when url_helpers are called
2+
3+
Executes the first routes reload in middleware, or when a route set's
4+
url_helpers receives a route call / asked if it responds to a route.
5+
Previously, this was executed unconditionally on boot, which can
6+
slow down boot time unnecessarily for larger apps with lots of routes.
7+
8+
*Gannon McGibbon*
9+
110
* Add Rubocop and GitHub Actions to plugin generator.
211
This can be skipped using --skip-rubocop and --skip-ci.
312

railties/lib/rails/application.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require "active_support/encrypted_configuration"
1010
require "active_support/hash_with_indifferent_access"
1111
require "active_support/configuration_file"
12+
require "active_support/parameter_filter"
1213
require "rails/engine"
1314
require "rails/autoloaders"
1415

@@ -153,6 +154,10 @@ def reload_routes!
153154
routes_reloader.reload!
154155
end
155156

157+
def reload_routes_unless_loaded # :nodoc:
158+
initialized? && routes_reloader.execute_unless_loaded
159+
end
160+
156161
# Returns a key generator (ActiveSupport::CachingKeyGenerator) for a
157162
# specified +secret_key_base+. The return value is memoized, so additional
158163
# calls with the same +secret_key_base+ will return the same key generator

railties/lib/rails/application/default_middleware_stack.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ def build_stack
6262
middleware.use ::ActionDispatch::ActionableExceptions
6363
end
6464

65+
middleware.use ::Rails::Rack::LoadRoutes unless app.config.eager_load
66+
6567
if config.reloading_enabled?
6668
middleware.use ::ActionDispatch::Reloader, app.reloader
6769
end

railties/lib/rails/application/finisher.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ def self.complete(_state)
159159
initializer :set_routes_reloader_hook do |app|
160160
reloader = routes_reloader
161161
reloader.eager_load = app.config.eager_load
162-
reloader.execute
163162
reloaders << reloader
164163

165164
app.reloader.to_run do
@@ -177,7 +176,12 @@ def self.complete(_state)
177176
ActiveSupport.run_load_hooks(:after_routes_loaded, self)
178177
end
179178

180-
ActiveSupport.run_load_hooks(:after_routes_loaded, self)
179+
if reloader.eager_load
180+
reloader.execute
181+
ActiveSupport.run_load_hooks(:after_routes_loaded, self)
182+
elsif reloader.loaded
183+
ActiveSupport.run_load_hooks(:after_routes_loaded, self)
184+
end
181185
end
182186

183187
# Set clearing dependencies after the finisher hook to ensure paths

railties/lib/rails/application/routes_reloader.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Application
77
class RoutesReloader
88
include ActiveSupport::Callbacks
99

10-
attr_reader :route_sets, :paths, :external_routes
10+
attr_reader :route_sets, :paths, :external_routes, :loaded
1111
attr_accessor :eager_load
1212
attr_writer :run_after_load_paths # :nodoc:
1313
delegate :execute_if_updated, :execute, :updated?, to: :updater
@@ -17,9 +17,11 @@ def initialize
1717
@route_sets = []
1818
@external_routes = []
1919
@eager_load = false
20+
@loaded = false
2021
end
2122

2223
def reload!
24+
@loaded = true
2325
clear!
2426
load_paths
2527
finalize!
@@ -28,6 +30,14 @@ def reload!
2830
revert
2931
end
3032

33+
def execute_unless_loaded
34+
unless @loaded
35+
execute
36+
ActiveSupport.run_load_hooks(:after_routes_loaded, Rails.application)
37+
true
38+
end
39+
end
40+
3141
private
3242
def updater
3343
@updater ||= begin

railties/lib/rails/commands/routes/routes_command.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def invoke_command(*)
2323
desc "routes", "List all the defined routes"
2424
def perform(*)
2525
boot_application!
26+
Rails.application.reload_routes_unless_loaded
2627
require "action_dispatch/routing/inspector"
2728

2829
say inspector.format(formatter, routes_filter)

railties/lib/rails/commands/unused_routes/unused_routes_command.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def action_missing?
4141

4242
def perform(*)
4343
boot_application!
44+
Rails.application.reload_routes_unless_loaded
4445
require "action_dispatch/routing/inspector"
4546

4647
say(inspector.format(formatter, routes_filter))

0 commit comments

Comments
 (0)