aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb53
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/scanner.rb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/endpoint.rb15
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb147
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb15
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb20
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_testing/browser.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb1
22 files changed, 250 insertions, 102 deletions
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index 4883e23d24..17e72b46ff 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -21,7 +21,8 @@ module ActionDispatch #:nodoc:
return response if policy_present?(headers)
if policy = request.content_security_policy
- headers[header_name(request)] = policy.build(request.controller_instance)
+ nonce = request.content_security_policy_nonce
+ headers[header_name(request)] = policy.build(request.controller_instance, nonce)
end
response
@@ -51,6 +52,8 @@ module ActionDispatch #:nodoc:
module Request
POLICY = "action_dispatch.content_security_policy".freeze
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
+ NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator".freeze
+ NONCE = "action_dispatch.content_security_policy_nonce".freeze
def content_security_policy
get_header(POLICY)
@@ -67,6 +70,30 @@ module ActionDispatch #:nodoc:
def content_security_policy_report_only=(value)
set_header(POLICY_REPORT_ONLY, value)
end
+
+ def content_security_policy_nonce_generator
+ get_header(NONCE_GENERATOR)
+ end
+
+ def content_security_policy_nonce_generator=(generator)
+ set_header(NONCE_GENERATOR, generator)
+ end
+
+ def content_security_policy_nonce
+ if content_security_policy_nonce_generator
+ if nonce = get_header(NONCE)
+ nonce
+ else
+ set_header(NONCE, generate_content_security_policy_nonce)
+ end
+ end
+ end
+
+ private
+
+ def generate_content_security_policy_nonce
+ content_security_policy_nonce_generator.call(self)
+ end
end
MAPPINGS = {
@@ -81,7 +108,9 @@ module ActionDispatch #:nodoc:
blob: "blob:",
filesystem: "filesystem:",
report_sample: "'report-sample'",
- strict_dynamic: "'strict-dynamic'"
+ strict_dynamic: "'strict-dynamic'",
+ ws: "ws:",
+ wss: "wss:"
}.freeze
DIRECTIVES = {
@@ -102,7 +131,9 @@ module ActionDispatch #:nodoc:
worker_src: "worker-src"
}.freeze
- private_constant :MAPPINGS, :DIRECTIVES
+ NONCE_DIRECTIVES = %w[script-src].freeze
+
+ private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
attr_reader :directives
@@ -171,8 +202,8 @@ module ActionDispatch #:nodoc:
end
end
- def build(context = nil)
- build_directives(context).compact.join("; ") + ";"
+ def build(context = nil, nonce = nil)
+ build_directives(context, nonce).compact.join("; ")
end
private
@@ -195,10 +226,14 @@ module ActionDispatch #:nodoc:
end
end
- def build_directives(context)
+ def build_directives(context, nonce)
@directives.map do |directive, sources|
if sources.is_a?(Array)
- "#{directive} #{build_directive(sources, context).join(' ')}"
+ if nonce && nonce_directive?(directive)
+ "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
+ else
+ "#{directive} #{build_directive(sources, context).join(' ')}"
+ end
elsif sources
directive
else
@@ -227,5 +262,9 @@ module ActionDispatch #:nodoc:
raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
end
end
+
+ def nonce_directive?(directive)
+ NONCE_DIRECTIVES.include?(directive)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index ec86b8bc47..ec012ad02d 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -9,8 +9,8 @@ module ActionDispatch
# sub-hashes of the params hash to filter. Filtering only certain sub-keys
# from a hash is possible by using the dot notation: 'credit_card.number'.
# If a block is given, each key and value of the params hash and all
- # sub-hashes is passed to it, where the value or the key can be replaced using
- # String#replace or similar method.
+ # sub-hashes are passed to it, where the value or the key can be replaced using
+ # String#replace or similar methods.
#
# env["action_dispatch.parameter_filter"] = [:password]
# => replaces the value to all keys matching /password/i with "[FILTERED]"
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index d2b2106845..295539281f 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -279,8 +279,6 @@ module Mime
def all?; false; end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :string, :synonyms
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index 08b931a3cd..32f632800c 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -32,7 +32,7 @@ module ActionDispatch
end
def name
- left.tr "*:".freeze, "".freeze
+ -left.tr("*:", "")
end
def type
@@ -82,7 +82,7 @@ module ActionDispatch
def initialize(left)
super
@regexp = DEFAULT_EXP
- @name = left.tr "*:".freeze, "".freeze
+ @name = -left.tr("*:", "")
end
def default_regexp?
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 2d85a89a56..537f479ee5 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -90,7 +90,7 @@ module ActionDispatch
return @separator_re unless @matchers.key?(node)
re = @matchers[node]
- "(#{re})"
+ "(#{Regexp.union(re)})"
end
def visit_GROUP(node)
@@ -183,7 +183,7 @@ module ActionDispatch
node = node.to_sym
if @requirements.key?(node)
- re = /#{@requirements[node]}|/
+ re = /#{Regexp.union(@requirements[node])}|/
@offsets.push((re.match("").length - 1) + @offsets.last)
else
@offsets << @offsets.last
diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb
index 4ae77903fa..2a075862e9 100644
--- a/actionpack/lib/action_dispatch/journey/scanner.rb
+++ b/actionpack/lib/action_dispatch/journey/scanner.rb
@@ -34,6 +34,13 @@ module ActionDispatch
private
+ # takes advantage of String @- deduping capabilities in Ruby 2.5 upwards
+ # see: https://bugs.ruby-lang.org/issues/13077
+ def dedup_scan(regex)
+ r = @ss.scan(regex)
+ r ? -r : nil
+ end
+
def scan
case
# /
@@ -47,15 +54,15 @@ module ActionDispatch
[:OR, "|"]
when @ss.skip(/\./)
[:DOT, "."]
- when text = @ss.scan(/:\w+/)
+ when text = dedup_scan(/:\w+/)
[:SYMBOL, text]
- when text = @ss.scan(/\*\w+/)
+ when text = dedup_scan(/\*\w+/)
[:STAR, text]
when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
text.tr! "\\", ""
- [:LITERAL, text]
+ [:LITERAL, -text]
# any char
- when text = @ss.scan(/./)
+ when text = dedup_scan(/./)
[:LITERAL, text]
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 511306eb0e..33edad8bd9 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -50,10 +50,18 @@ module ActionDispatch
end
end
- def initialize(app, routes_app = nil, response_format = :default)
+ cattr_reader :interceptors, instance_accessor: false, default: []
+
+ def self.register_interceptor(object = nil, &block)
+ interceptor = object || block
+ interceptors << interceptor
+ end
+
+ def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
@app = app
@routes_app = routes_app
@response_format = response_format
+ @interceptors = interceptors
end
def call(env)
@@ -67,12 +75,26 @@ module ActionDispatch
response
rescue Exception => exception
+ invoke_interceptors(request, exception)
raise exception unless request.show_exceptions?
render_exception(request, exception)
end
private
+ def invoke_interceptors(request, exception)
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
+
+ @interceptors.each do |interceptor|
+ begin
+ interceptor.call(request, exception)
+ rescue Exception
+ log_error(request, wrapper)
+ end
+ end
+ end
+
def render_exception(request, exception)
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 3e11846778..fd05eec172 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -73,7 +73,7 @@ module ActionDispatch
end
end
- def reset_session # :nodoc
+ def reset_session # :nodoc:
super
self.flash = nil
end
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index ef633aadc6..240269d1c7 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -15,6 +15,8 @@ module ActionDispatch
#
# config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
#
+ # Cookies will not be flagged as secure for excluded requests.
+ #
# 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they
# must not be sent along with +http://+ requests. Enabled by default. Set
# +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature.
@@ -26,8 +28,8 @@ module ActionDispatch
# Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
#
# * +expires+: How long, in seconds, these settings will stick. The minimum
- # required to qualify for browser preload lists is 18 weeks. Defaults to
- # 180 days (recommended).
+ # required to qualify for browser preload lists is 1 year. Defaults to
+ # 1 year (recommended).
#
# * +subdomains+: Set to +true+ to tell the browser to apply these settings
# to all subdomains. This protects your cookies from interception by a
@@ -47,9 +49,8 @@ module ActionDispatch
class SSL
# :stopdoc:
- # Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
- # and greater than the 18-week requirement for browser preload lists.
- HSTS_EXPIRES_IN = 15552000
+ # Default to 1 year, the minimum for browser preload lists.
+ HSTS_EXPIRES_IN = 31536000
def self.default_hsts_options
{ expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
@@ -72,7 +73,7 @@ module ActionDispatch
if request.ssl?
@app.call(env).tap do |status, headers, body|
set_hsts_header! headers
- flag_cookies_as_secure! headers if @secure_cookies
+ flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request)
end
else
return redirect_to_https request unless @exclude.call(request)
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 23492e14eb..acd999444a 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -16,7 +16,7 @@ module ActionDispatch
# does not exist, a 404 "File not Found" response will be returned.
class FileHandler
def initialize(root, index: "index", headers: {})
- @root = root.chomp("/")
+ @root = root.chomp("/").b
@file_server = ::Rack::File.new(@root, headers)
@index = index
end
@@ -35,7 +35,7 @@ module ActionDispatch
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
- path = File.join(@root, p.dup.force_encoding(Encoding::UTF_8))
+ path = File.join(@root, p.b)
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
@@ -43,7 +43,7 @@ module ActionDispatch
end
}
- return ::Rack::Utils.escape_path(match)
+ return ::Rack::Utils.escape_path(match).b
end
end
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 000847e193..bc5e0670e0 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -93,6 +93,14 @@ module ActionDispatch
@delegate[key.to_s]
end
+ # Returns the nested value specified by the sequence of keys, returning
+ # +nil+ if any intermediate step is +nil+.
+ def dig(*keys)
+ load_for_read!
+ keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key }
+ @delegate.dig(*keys)
+ end
+
# Returns true if the session has the given key or false.
def has_key?(key)
load_for_read!
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 72f7407c6e..5cde677051 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -243,8 +243,9 @@ module ActionDispatch
#
# rails routes
#
- # Target specific controllers by prefixing the command with <tt>-c</tt> option.
- #
+ # Target a specific controller with <tt>-c</tt>, or grep routes
+ # using <tt>-g</tt>. Useful in conjunction with <tt>--expanded</tt>
+ # which displays routes vertically.
module Routing
extend ActiveSupport::Autoload
diff --git a/actionpack/lib/action_dispatch/routing/endpoint.rb b/actionpack/lib/action_dispatch/routing/endpoint.rb
index 24dced1efd..28bb20d688 100644
--- a/actionpack/lib/action_dispatch/routing/endpoint.rb
+++ b/actionpack/lib/action_dispatch/routing/endpoint.rb
@@ -3,12 +3,15 @@
module ActionDispatch
module Routing
class Endpoint # :nodoc:
- def dispatcher?; false; end
- def redirect?; false; end
- def engine?; rack_app.respond_to?(:routes); end
- def matches?(req); true; end
- def app; self; end
- def rack_app; app; end
+ def dispatcher?; false; end
+ def redirect?; false; end
+ def matches?(req); true; end
+ def app; self; end
+ def rack_app; app; end
+
+ def engine?
+ rack_app.is_a?(Class) && rack_app < Rails::Engine
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index a2205569b4..bae50f6a43 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "delegate"
-require "active_support/core_ext/string/strip"
+require "io/console/size"
module ActionDispatch
module Routing
@@ -61,11 +61,11 @@ module ActionDispatch
@routes = routes
end
- def format(formatter, filter = nil)
+ def format(formatter, filter = {})
routes_to_display = filter_routes(normalize_filter(filter))
routes = collect_routes(routes_to_display)
if routes.none?
- formatter.no_routes(collect_routes(@routes))
+ formatter.no_routes(collect_routes(@routes), filter)
return formatter.result
end
@@ -81,12 +81,12 @@ module ActionDispatch
end
private
-
def normalize_filter(filter)
- if filter.is_a?(Hash) && filter[:controller]
+ if filter[:controller]
{ controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
- elsif filter
- { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ }
+ elsif filter[:grep]
+ { controller: /#{filter[:grep]}/, action: /#{filter[:grep]}/,
+ verb: /#{filter[:grep]}/, name: /#{filter[:grep]}/, path: /#{filter[:grep]}/ }
end
end
@@ -126,62 +126,111 @@ module ActionDispatch
end
end
- class ConsoleFormatter
- def initialize
- @buffer = []
- end
+ module ConsoleFormatter
+ class Base
+ def initialize
+ @buffer = []
+ end
- def result
- @buffer.join("\n")
- end
+ def result
+ @buffer.join("\n")
+ end
- def section_title(title)
- @buffer << "\n#{title}:"
- end
+ def section_title(title)
+ end
- def section(routes)
- @buffer << draw_section(routes)
- end
+ def section(routes)
+ end
- def header(routes)
- @buffer << draw_header(routes)
- end
+ def header(routes)
+ end
- def no_routes(routes)
- @buffer <<
- if routes.none?
- <<-MESSAGE.strip_heredoc
- You don't have any routes defined!
+ def no_routes(routes, filter)
+ @buffer <<
+ if routes.none?
+ <<~MESSAGE
+ You don't have any routes defined!
+
+ Please add some routes in config/routes.rb.
+ MESSAGE
+ elsif filter.key?(:controller)
+ "No routes were found for this controller."
+ elsif filter.key?(:grep)
+ "No routes were found for this grep pattern."
+ end
- Please add some routes in config/routes.rb.
- MESSAGE
- else
- "No routes were found for this controller"
+ @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
end
- @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
end
- private
- def draw_section(routes)
- header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
- name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
+ class Sheet < Base
+ def section_title(title)
+ @buffer << "\n#{title}:"
+ end
- routes.map do |r|
- "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
- end
+ def section(routes)
+ @buffer << draw_section(routes)
+ end
+
+ def header(routes)
+ @buffer << draw_header(routes)
end
- def draw_header(routes)
- name_width, verb_width, path_width = widths(routes)
+ private
+
+ def draw_section(routes)
+ header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
+ name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
+
+ routes.map do |r|
+ "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
+ end
+ end
+
+ def draw_header(routes)
+ name_width, verb_width, path_width = widths(routes)
+
+ "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
+ end
+
+ def widths(routes)
+ [routes.map { |r| r[:name].length }.max || 0,
+ routes.map { |r| r[:verb].length }.max || 0,
+ routes.map { |r| r[:path].length }.max || 0]
+ end
+ end
- "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
+ class Expanded < Base
+ def section_title(title)
+ @buffer << "\n#{"[ #{title} ]"}"
end
- def widths(routes)
- [routes.map { |r| r[:name].length }.max || 0,
- routes.map { |r| r[:verb].length }.max || 0,
- routes.map { |r| r[:path].length }.max || 0]
+ def section(routes)
+ @buffer << draw_expanded_section(routes)
end
+
+ private
+
+ def draw_expanded_section(routes)
+ routes.map.each_with_index do |r, i|
+ <<~MESSAGE.chomp
+ #{route_header(index: i + 1)}
+ Prefix | #{r[:name]}
+ Verb | #{r[:verb]}
+ URI | #{r[:path]}
+ Controller#Action | #{r[:reqs]}
+ MESSAGE
+ end
+ end
+
+ def route_header(index:)
+ console_width = IO.console_size.second
+ header_prefix = "--[ Route #{index} ]"
+ dash_remainder = [console_width - header_prefix.size, 0].max
+
+ "#{header_prefix}#{'-' * dash_remainder}"
+ end
+ end
end
class HtmlTableFormatter
@@ -203,7 +252,7 @@ module ActionDispatch
end
def no_routes(*)
- @buffer << <<-MESSAGE.strip_heredoc
+ @buffer << <<~MESSAGE
<p>You don't have any routes defined!</p>
<ul>
<li>Please add some routes in <tt>config/routes.rb</tt>.</li>
@@ -212,7 +261,7 @@ module ActionDispatch
<a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
</li>
</ul>
- MESSAGE
+ MESSAGE
end
def result
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 31eb6104fe..d9dd24935b 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -611,7 +611,7 @@ module ActionDispatch
end
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
- raise ArgumentError, <<-MSG.strip_heredoc unless path
+ raise ArgumentError, <<~MSG unless path
Must be called with mount point
mount SomeRackApp, at: "some_route"
@@ -664,6 +664,7 @@ 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)
@@ -675,7 +676,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) }
- _routes.url_helpers.send("#{name}_path", prefix_options)
+ _url_helpers.send("#{name}_path", prefix_options)
end
app.routes.define_mounted_helper(name, script_namer)
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 6da869c0c2..e17ccaf986 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -120,8 +120,7 @@ module ActionDispatch
opts
end
- # Returns the path component of a URL for the given record. It uses
- # <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
+ # Returns the path component of a URL for the given record.
def polymorphic_path(record_or_hash_or_array, options = {})
if Hash === record_or_hash_or_array
options = record_or_hash_or_array.merge(options)
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 9eff30fa53..a29a5a04ef 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -2,7 +2,6 @@
require "action_dispatch/journey"
require "active_support/core_ext/object/to_query"
-require "active_support/core_ext/hash/slice"
require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/array/extract_options"
@@ -154,13 +153,13 @@ module ActionDispatch
url_name = :"#{name}_url"
@path_helpers_module.module_eval do
- define_method(path_name) do |*args|
+ redefine_method(path_name) do |*args|
helper.call(self, args, true)
end
end
@url_helpers_module.module_eval do
- define_method(url_name) do |*args|
+ redefine_method(url_name) do |*args|
helper.call(self, args, false)
end
end
@@ -855,7 +854,7 @@ module ActionDispatch
recognize_path_with_request(req, path, extras)
end
- def recognize_path_with_request(req, path, extras)
+ def recognize_path_with_request(req, path, extras, raise_on_missing: true)
@router.recognize(req) do |route, params|
params.merge!(extras)
params.each do |key, value|
@@ -875,12 +874,14 @@ module ActionDispatch
return req.path_parameters
elsif app.matches?(req) && app.engine?
- path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras)
- return path_parameters
+ path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras, raise_on_missing: false)
+ return path_parameters if path_parameters
end
end
- raise ActionController::RoutingError, "No route matches #{path.inspect}"
+ if raise_on_missing
+ raise ActionController::RoutingError, "No route matches #{path.inspect}"
+ end
end
end
# :startdoc:
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index fa345dccdf..1a31c7dbb8 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -191,7 +191,25 @@ module ActionDispatch
end
end
- def route_for(name, *args) # :nodoc:
+ # Allows calling direct or regular named route.
+ #
+ # resources :buckets
+ #
+ # direct :recordable do |recording|
+ # route_for(:bucket, recording.bucket)
+ # end
+ #
+ # direct :threadable do |threadable|
+ # route_for(:recordable, threadable.parent)
+ # end
+ #
+ # This maintains the context of the original caller on
+ # whether to return a path or full URL, e.g:
+ #
+ # threadable_path(threadable) # => "/buckets/1"
+ # threadable_url(threadable) # => "http://example.com/buckets/1"
+ #
+ def route_for(name, *args)
public_send(:"#{name}_url", *args)
end
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index f85f816bb9..c74c0ccced 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-gem "capybara", "~> 2.15"
+gem "capybara", ">= 2.15"
require "capybara/dsl"
require "capybara/minitest"
diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb
index 10e6888ab3..1b0bce6b9e 100644
--- a/actionpack/lib/action_dispatch/system_testing/browser.rb
+++ b/actionpack/lib/action_dispatch/system_testing/browser.rb
@@ -33,7 +33,7 @@ module ActionDispatch
def headless_chrome_browser_options
options = Selenium::WebDriver::Chrome::Options.new
options.args << "--headless"
- options.args << "--disable-gpu"
+ options.args << "--disable-gpu" if Gem.win_platform?
options
end
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 df0c5d3f0e..2c8cee3a9b 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
@@ -43,7 +43,7 @@ module ActionDispatch
end
def image_path
- @image_path ||= absolute_image_path.relative_path_from(Pathname.pwd).to_s
+ @image_path ||= absolute_image_path.to_s
end
def absolute_image_path
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
index ffa85f4e14..e47d5020f4 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
@@ -19,6 +19,7 @@ module ActionDispatch
def after_teardown
take_failed_screenshot
Capybara.reset_sessions!
+ ensure
super
end
end