aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/railties/routes_helpers.rb7
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb7
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb2
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/content_disposition.rb45
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb22
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_locks.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb21
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb44
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb5
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb42
22 files changed, 179 insertions, 60 deletions
diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
index b6e5631a4e..c97be074c8 100644
--- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb
+++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
@@ -7,11 +7,8 @@ module AbstractController
Module.new do
define_method(:inherited) do |klass|
super(klass)
- if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
- klass.include(namespace.railtie_routes_url_helpers(include_path_helpers))
- else
- klass.include(routes.url_helpers(include_path_helpers))
- end
+
+ routes.include_helpers klass, include_path_helpers
end
end
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 14f41eb55f..203653354a 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -26,7 +26,7 @@ module ActionController
exception_class_name = payload[:exception].first
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
- message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms".dup
+ message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
message << " (#{additions.join(" | ".freeze)})" unless additions.empty?
message << "\n\n" if defined?(Rails.env) && Rails.env.development?
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 5a82ccf668..5140a667de 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "action_controller/metal/exceptions"
+require "action_dispatch/http/content_disposition"
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
@@ -132,10 +133,8 @@ module ActionController #:nodoc:
end
disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
- unless disposition.nil?
- disposition = disposition.to_s
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
- headers["Content-Disposition"] = disposition
+ if disposition
+ headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: options[:filename])
end
headers["Content-Transfer-Encoding"] = "binary"
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 5115c2fadf..380f2e9591 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -36,7 +36,7 @@ module ActionController #:nodoc:
define_method(type) do
request.flash[type]
end
- helper_method type
+ helper_method(type) if respond_to?(:helper_method)
self._flash_types += [type]
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index a871ccd533..5794e0fb97 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -474,7 +474,7 @@ module ActionController
# This removes the <tt>"</tt> characters wrapping the value.
def rewrite_param_values(array_params)
- array_params.each { |param| (param[1] || "".dup).gsub! %r/^"|"$/, "" }
+ array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
end
# This method takes an authorization body and splits up the key-value
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index b1c2391afe..5680ef08a1 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -297,7 +297,7 @@ module ActionController
return unless logger
logger.fatal do
- message = "\n#{exception.class} (#{exception.message}):\n".dup
+ message = +"\n#{exception.class} (#{exception.message}):\n"
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << exception.backtrace.join("\n ")
"#{message}\n\n"
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 6d181e6456..7d0a944381 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -40,7 +40,7 @@ module ActionController
def render_to_string(*)
result = super
if result.respond_to?(:each)
- string = "".dup
+ string = +""
result.each { |r| string << r }
string
else
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index a37f08d944..c1272ce667 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -348,6 +348,14 @@ module ActionController
end
alias_method :each, :each_pair
+ # Convert all hashes in values into parameters, then yield each value in
+ # the same way as <tt>Hash#each_value</tt>.
+ def each_value(&block)
+ @parameters.each_pair do |key, value|
+ yield [convert_hashes_to_parameters(key, value)]
+ end
+ end
+
# Attribute that keeps track of converted arrays, if any, to avoid double
# looping in the common use case permit + mass-assignment. Defined in a
# method to instantiate it only if needed.
diff --git a/actionpack/lib/action_dispatch/http/content_disposition.rb b/actionpack/lib/action_dispatch/http/content_disposition.rb
new file mode 100644
index 0000000000..58164c1522
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/content_disposition.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module ActionDispatch
+ module Http
+ class ContentDisposition # :nodoc:
+ def self.format(disposition:, filename:)
+ new(disposition: disposition, filename: filename).to_s
+ end
+
+ attr_reader :disposition, :filename
+
+ def initialize(disposition:, filename:)
+ @disposition = disposition
+ @filename = filename
+ end
+
+ TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/
+
+ def ascii_filename
+ 'filename="' + percent_escape(I18n.transliterate(filename), TRADITIONAL_ESCAPED_CHAR) + '"'
+ end
+
+ RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/
+
+ def utf8_filename
+ "filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR)
+ end
+
+ def to_s
+ if filename
+ "#{disposition}; #{ascii_filename}; #{utf8_filename}"
+ else
+ "#{disposition}"
+ end
+ end
+
+ private
+ def percent_escape(string, pattern)
+ string.gsub(pattern) do |char|
+ char.bytes.map { |byte| "%%%02X" % byte }.join
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index 35041fd072..855be5ce2e 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -132,7 +132,7 @@ module ActionDispatch #:nodoc:
worker_src: "worker-src"
}.freeze
- NONCE_DIRECTIVES = %w[script-src].freeze
+ NONCE_DIRECTIVES = %w[script-src style-src].freeze
private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 7e50cb6d23..f07be831d4 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -105,7 +105,7 @@ module ActionDispatch # :nodoc:
def body
@str_body ||= begin
- buf = "".dup
+ buf = +""
each { |chunk| buf << chunk }
buf
end
@@ -224,16 +224,6 @@ module ActionDispatch # :nodoc:
@status = Rack::Utils.status_code(status)
end
- # Sets the HTTP content type.
- def content_type=(content_type)
- return unless content_type
- new_header_info = parse_content_type(content_type.to_s)
- prev_header_info = parsed_content_type_header
- charset = new_header_info.charset || prev_header_info.charset
- charset ||= self.class.default_charset unless prev_header_info.mime_type
- set_content_type new_header_info.mime_type, charset
- end
-
# Sets the HTTP response's content MIME type. For example, in the controller
# you could write this:
#
@@ -242,7 +232,17 @@ module ActionDispatch # :nodoc:
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# information.
+ def content_type=(content_type)
+ return unless content_type
+ new_header_info = parse_content_type(content_type.to_s)
+ prev_header_info = parsed_content_type_header
+ charset = new_header_info.charset || prev_header_info.charset
+ charset ||= self.class.default_charset unless prev_header_info.mime_type
+ set_content_type new_header_info.mime_type, charset
+ end
+ # Content type of response.
+ # It returns just MIME type and does NOT contain charset part.
def content_type
parsed_content_type_header.mime_type
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 35ba44005a..db6d8188d3 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -157,7 +157,7 @@ module ActionDispatch
subdomain = options.fetch :subdomain, true
domain = options[:domain]
- host = "".dup
+ host = +""
if subdomain == true
return _host if domain.nil?
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 0f04839d9b..52396ec901 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -50,7 +50,7 @@ module ActionDispatch
unmatched_keys = (missing_keys || []) & constraints.keys
missing_keys = (missing_keys || []) - unmatched_keys
- message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}".dup
+ message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index df3f79a407..3bbb187f5c 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -17,11 +17,11 @@ module ActionDispatch
def self.normalize_path(path)
path ||= ""
encoding = path.encoding
- path = "/#{path}".dup
+ path = +"/#{path}"
path.squeeze!("/".freeze)
path.sub!(%r{/+\Z}, "".freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
- path = "/".dup if path == "".freeze
+ path = +"/" if path == "".freeze
path.force_encoding(encoding)
path
end
@@ -32,7 +32,7 @@ module ActionDispatch
ENCODE = "%%%02X".freeze
US_ASCII = Encoding::US_ASCII
UTF_8 = Encoding::UTF_8
- EMPTY = "".dup.force_encoding(US_ASCII).freeze
+ EMPTY = (+"").force_encoding(US_ASCII).freeze
DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
ALPHA = "a-zA-Z".freeze
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 077a83b112..5f5fdbc66a 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -23,7 +23,7 @@ module ActionDispatch
if clean_params.empty?
"None"
else
- PP.pp(clean_params, "".dup, 200)
+ PP.pp(clean_params, +"", 200)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb
index 03760438f7..d39377f174 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_locks.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb
@@ -63,19 +63,19 @@ module ActionDispatch
str = threads.map do |thread, info|
if info[:exclusive]
- lock_state = "Exclusive".dup
+ lock_state = +"Exclusive"
elsif info[:sharing] > 0
- lock_state = "Sharing".dup
+ lock_state = +"Sharing"
lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
else
- lock_state = "No lock".dup
+ lock_state = +"No lock"
end
if info[:waiting]
lock_state << " (yielded share)"
end
- msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup
+ msg = +"Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
if info[:sleeper]
msg << " Waiting in #{info[:sleeper]}"
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 190e54223e..9c9ccfa16f 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -102,7 +102,7 @@ module ActionDispatch
# https://tools.ietf.org/html/rfc6797#section-6.1
def build_hsts_header(hsts)
- value = "max-age=#{hsts[:expires].to_i}".dup
+ value = +"max-age=#{hsts[:expires].to_i}"
value << "; includeSubDomains" if hsts[:subdomains]
value << "; preload" if hsts[:preload]
value
@@ -141,7 +141,7 @@ module ActionDispatch
host = @redirect[:host] || request.host
port = @redirect[:port] || request.port
- location = "https://#{host}".dup
+ location = +"https://#{host}"
location << ":#{port}" if port != 80 && port != 443
location << request.fullpath
location
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 07e3be4db8..8b5a39dcaf 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -50,7 +50,19 @@ module ActionDispatch
private
def constraint_args(constraint, request)
- constraint.arity == 1 ? [request] : [request.path_parameters, request]
+ arity = if constraint.respond_to?(:arity)
+ constraint.arity
+ else
+ constraint.method(:call).arity
+ end
+
+ if arity < 1
+ []
+ elsif arity == 1
+ [request]
+ else
+ [request.path_parameters, request]
+ end
end
end
@@ -308,7 +320,7 @@ module ActionDispatch
def check_controller_and_action(path_params, controller, action)
hash = check_part(:controller, controller, path_params, {}) do |part|
translate_controller(part) {
- message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup
+ message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems."
message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
raise ArgumentError, message
@@ -390,7 +402,7 @@ module ActionDispatch
# for root cases, where the latter is the correct one.
def self.normalize_path(path)
path = Journey::Router::Utils.normalize_path(path)
- path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^)]+\)$}
+ path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/(\(+[^)]+\)){1,}$}
path
end
@@ -664,7 +676,6 @@ module ActionDispatch
def define_generate_prefix(app, name)
_route = @set.named_routes.get name
_routes = @set
- _url_helpers = @set.url_helpers
script_namer = ->(options) do
prefix_options = options.slice(*_route.segment_keys)
@@ -676,7 +687,7 @@ module ActionDispatch
# We must actually delete prefix segment keys to avoid passing them to next url_for.
_route.segment_keys.each { |k| options.delete(k) }
- _url_helpers.send("#{name}_path", prefix_options)
+ @set.url_helpers.send("#{name}_path", prefix_options)
end
app.routes.define_mounted_helper(name, script_namer)
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 07d3a41173..da4f285f61 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -245,7 +245,7 @@ module ActionDispatch
missing_keys << missing_key
}
constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }]
- message = "No route matches #{constraints.inspect}".dup
+ message = +"No route matches #{constraints.inspect}"
message << ", missing required keys: #{missing_keys.sort.inspect}"
raise ActionController::UrlGenerationError, message
@@ -378,6 +378,8 @@ module ActionDispatch
@disable_clear_and_finalize = false
@finalized = false
@env_key = "ROUTES_#{object_id}_SCRIPT_NAME".freeze
+ @url_helpers = nil
+ @deferred_classes = []
@set = Journey::Routes.new
@router = Journey::Router.new @set
@@ -433,10 +435,34 @@ module ActionDispatch
end
private :eval_block
+ def include_helpers(klass, include_path_helpers)
+ if @finalized
+ include_helpers_now klass, include_path_helpers
+ else
+ @deferred_classes << [klass, include_path_helpers]
+ end
+ end
+
+ def include_helpers_now(klass, include_path_helpers)
+ namespace = klass.parents.detect { |m| m.respond_to?(:railtie_include_helpers) }
+
+ if namespace && namespace.railtie_namespace.routes != self
+ namespace.railtie_include_helpers(klass, include_path_helpers)
+ else
+ klass.include(url_helpers(include_path_helpers))
+ end
+ end
+ private :include_helpers_now
+
def finalize!
return if @finalized
@append.each { |blk| eval_block(blk) }
@finalized = true
+ @url_helpers = build_url_helper_module true
+ @deferred_classes.each { |klass, include_path_helpers|
+ include_helpers klass, include_path_helpers
+ }
+ @deferred_classes.clear
end
def clear!
@@ -465,11 +491,10 @@ module ActionDispatch
return if MountedHelpers.method_defined?(name)
routes = self
- helpers = routes.url_helpers
MountedHelpers.class_eval do
define_method "_#{name}" do
- RoutesProxy.new(routes, _routes_context, helpers, script_namer)
+ RoutesProxy.new(routes, _routes_context, routes.url_helpers, script_namer)
end
end
@@ -480,7 +505,20 @@ module ActionDispatch
RUBY
end
+ class UnfinalizedRouteSet < StandardError
+ end
+
def url_helpers(supports_path = true)
+ raise UnfinalizedRouteSet, "routes have not been finalized. Please call `finalize!` or use `draw(&block)`" unless @finalized
+
+ if supports_path
+ @url_helpers
+ else
+ build_url_helper_module false
+ end
+ end
+
+ def build_url_helper_module(supports_path)
routes = self
Module.new do
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
index d2685e0452..884fb51d18 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
@@ -65,7 +65,7 @@ module ActionDispatch
end
def display_image
- message = "[Screenshot]: #{image_path}\n".dup
+ message = +"[Screenshot]: #{image_path}\n"
case output_type
when "artifact"
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 98b1965d22..8595ea03cf 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -79,9 +79,8 @@ module ActionDispatch
end
def generate_response_message(expected, actual = @response.response_code)
- "Expected response to be a <#{code_with_name(expected)}>,"\
- " but was a <#{code_with_name(actual)}>"
- .dup.concat(location_if_redirected).concat(response_body_if_short)
+ (+"Expected response to be a <#{code_with_name(expected)}>,"\
+ " but was a <#{code_with_name(actual)}>").concat(location_if_redirected).concat(response_body_if_short)
end
def response_body_if_short
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 77cb311630..0e8712f8d9 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -9,6 +9,11 @@ module ActionDispatch
module Assertions
# Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
module RoutingAssertions
+ def setup # :nodoc:
+ @routes ||= nil
+ super
+ end
+
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
# match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
#
@@ -133,6 +138,20 @@ module ActionDispatch
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
end
+ # Provides a hook on `finalize!` so we can mutate a controller after the
+ # route set has been drawn.
+ class WithRouting < ActionDispatch::Routing::RouteSet # :nodoc:
+ def initialize(&block)
+ super()
+ @block = block
+ end
+
+ def finalize!
+ super
+ @block.call self
+ end
+ end
+
# A helper to make it easier to test different route configurations.
# This method temporarily replaces @routes with a new RouteSet instance.
#
@@ -147,16 +166,19 @@ module ActionDispatch
# end
#
def with_routing
- old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
- if defined?(@controller) && @controller
- old_controller, @controller = @controller, @controller.clone
- _routes = @routes
-
- @controller.singleton_class.include(_routes.url_helpers)
-
- if @controller.respond_to? :view_context_class
- @controller.view_context_class = Class.new(@controller.view_context_class) do
- include _routes.url_helpers
+ old_routes = @routes
+ old_controller = nil
+ @routes = WithRouting.new do |_routes|
+ if defined?(@controller) && @controller
+ old_controller, @controller = @controller, @controller.clone
+ _routes = @routes
+
+ @controller.singleton_class.include(_routes.url_helpers)
+
+ if @controller.respond_to? :view_context_class
+ @controller.view_context_class = Class.new(@controller.view_context_class) do
+ include _routes.url_helpers
+ end
end
end
end