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/mime_negotiation.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb18
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb22
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb28
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb69
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb54
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y22
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb40
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb12
-rw-r--r--actionpack/lib/action_dispatch/journey/scanner.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb44
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb17
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb60
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb90
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb93
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb46
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb25
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb1
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb245
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb40
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb270
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb7
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb18
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/dom.rb28
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb18
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb431
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/tag.rb136
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb12
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
44 files changed, 853 insertions, 1140 deletions
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 0b2b60d2e4..9c8f65deac 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -54,8 +54,14 @@ module ActionDispatch
end
def formats
- @env["action_dispatch.request.formats"] ||=
- if parameters[:format]
+ @env["action_dispatch.request.formats"] ||= begin
+ params_readable = begin
+ parameters[:format]
+ rescue ActionController::BadRequest
+ false
+ end
+
+ if params_readable
Array(Mime[parameters[:format]])
elsif use_accept_header && valid_accept_header
accepts
@@ -64,8 +70,8 @@ module ActionDispatch
else
[Mime::HTML]
end
+ end
end
-
# Sets the \variant for template.
def variant=(variant)
if variant.is_a?(Symbol)
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 9450be838c..b9d5009683 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -45,8 +45,8 @@ module Mime
#
# respond_to do |format|
# format.html
- # format.ics { render text: post.to_ics, mime_type: Mime::Type["text/calendar"] }
- # format.xml { render xml: @people }
+ # format.ics { render text: @post.to_ics, mime_type: Mime::Type["text/calendar"] }
+ # format.xml { render xml: @post }
# end
# end
# end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 5f7627cf96..a5cd26a3c1 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/deprecation'
module ActionDispatch
module Http
@@ -24,8 +25,10 @@ module ActionDispatch
@env[PARAMETERS_KEY] = parameters
end
- # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
def symbolized_path_parameters
+ ActiveSupport::Deprecation.warn(
+ '`symbolized_path_parameters` is deprecated. Please use `path_parameters`.'
+ )
path_parameters
end
@@ -33,31 +36,22 @@ module ActionDispatch
# Returned hash keys are strings:
#
# {'action' => 'my_action', 'controller' => 'my_controller'}
- #
- # See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
@env[PARAMETERS_KEY] ||= {}
end
private
- # Convert nested Hash to HashWithIndifferentAccess
- # and UTF-8 encode both keys and values in nested Hash.
+ # Convert nested Hash to HashWithIndifferentAccess.
#
- # TODO: Validate that the characters are UTF-8. If they aren't,
- # you'll get a weird error down the road, but our form handling
- # should really prevent that from happening
def normalize_encode_params(params)
case params
- when String
- params.force_encoding(Encoding::UTF_8).encode!
when Hash
if params.has_key?(:tempfile)
UploadedFile.new(params)
else
params.each_with_object({}) do |(key, val), new_hash|
- new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key
- new_hash[new_key] = if val.is_a?(Array)
+ new_hash[key] = if val.is_a?(Array)
val.map! { |el| normalize_encode_params(el) }
else
normalize_encode_params(val)
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 4d4b443fb4..2a7bb374a5 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -23,7 +23,7 @@ module ActionDispatch
autoload :Session, 'action_dispatch/request/session'
autoload :Utils, 'action_dispatch/request/utils'
- LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
+ LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
@@ -105,6 +105,12 @@ module ActionDispatch
@request_method ||= check_method(env["REQUEST_METHOD"])
end
+ def request_method=(request_method) #:nodoc:
+ if check_method(request_method)
+ @request_method = env["REQUEST_METHOD"] = request_method
+ end
+ end
+
# Returns a symbol form of the #request_method
def request_method_symbol
HTTP_METHOD_LOOKUP[request_method]
@@ -209,8 +215,8 @@ module ActionDispatch
end
# Returns true if the "X-Requested-With" header contains "XMLHttpRequest"
- # (case-insensitive). All major JavaScript libraries send this header with
- # every Ajax request.
+ # (case-insensitive), which may need to be manually added depending on the
+ # choice of JavaScript libraries and frameworks.
def xml_http_request?
@env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
end
@@ -225,7 +231,7 @@ module ActionDispatch
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
- # Returns the unique request id, which is based off either the X-Request-Id header that can
+ # Returns the unique request id, which is based on either the X-Request-Id header that can
# be generated by a firewall, load balancer, or web server or by the RequestId middleware
# (which sets the action_dispatch.request_id environment variable).
#
@@ -292,7 +298,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
@env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
- rescue TypeError => e
+ rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:query, e)
end
alias :query_parameters :GET
@@ -300,7 +306,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
@env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
- rescue TypeError => e
+ rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:request, e)
end
alias :request_parameters :POST
@@ -322,7 +328,7 @@ module ActionDispatch
# Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility.
def deep_munge(hash)
ActiveSupport::Deprecation.warn(
- "This method has been extracted into ActionDispatch::Request::Utils.deep_munge. Please start using that instead."
+ 'This method has been extracted into `ActionDispatch::Request::Utils.deep_munge`. Please start using that instead.'
)
Utils.deep_munge(hash)
@@ -335,7 +341,7 @@ module ActionDispatch
private
def check_method(name)
- HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
+ HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
name
end
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 2fab6be1a5..33de2f8b5f 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,4 +1,6 @@
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/string/filters'
+require 'active_support/deprecation'
require 'action_dispatch/http/filter_redirect'
require 'monitor'
@@ -274,12 +276,27 @@ module ActionDispatch # :nodoc:
end
# Turns the Response into a Rack-compatible array of the status, headers,
- # and body.
+ # and body. Allows explict splatting:
+ #
+ # status, headers, body = *response
def to_a
rack_response @status, @header.to_hash
end
alias prepare! to_a
- alias to_ary to_a
+
+ # Be super clear that a response object is not an Array. Defining this
+ # would make implicit splatting work, but it also makes adding responses
+ # as arrays work, and "flattening" responses, cascading to the rack body!
+ # Not sensible behavior.
+ def to_ary
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `ActionDispatch::Response#to_ary` no longer performs implicit conversion
+ to an array. Please use `response.to_a` instead, or a splat like `status,
+ headers, body = *response`.
+ MSG
+
+ to_a
+ end
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
@@ -298,9 +315,6 @@ module ActionDispatch # :nodoc:
cookies
end
- def _status_code
- @status
- end
private
def before_committed
@@ -369,6 +383,10 @@ module ActionDispatch # :nodoc:
def to_path
@response.stream.to_path
end
+
+ def to_ary
+ nil
+ end
end
def rack_response(status, header)
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 45bf751d09..540e11a4a0 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -27,7 +27,8 @@ module ActionDispatch
@tempfile = hash[:tempfile]
raise(ArgumentError, ':tempfile is required') unless @tempfile
- @original_filename = encode_filename(hash[:filename])
+ @original_filename = hash[:filename]
+ @original_filename &&= @original_filename.encode "UTF-8"
@content_type = hash[:type]
@headers = hash[:head]
end
@@ -66,13 +67,6 @@ module ActionDispatch
def eof?
@tempfile.eof?
end
-
- private
-
- def encode_filename(filename)
- # Encode the filename in the utf8 encoding, unless it is nil
- filename.force_encoding(Encoding::UTF_8).encode! if filename
- end
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 3997c6ee98..6b8dcaf497 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -29,36 +29,48 @@ module ActionDispatch
end
def url_for(options)
- unless options[:host] || options[:only_path]
- raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
+ if options[:only_path]
+ path_for options
+ else
+ full_url_for options
end
+ end
- path = options[:script_name].to_s.chomp("/")
- path << options[:path].to_s
+ def full_url_for(options)
+ host = options[:host]
+ protocol = options[:protocol]
+ port = options[:port]
- path = add_trailing_slash(path) if options[:trailing_slash]
+ unless host
+ raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
+ end
- result = if options[:only_path]
- path
- else
- build_host_url(options).concat path
- end
+ build_host_url(host, port, protocol, options, path_for(options))
+ end
- if options.key? :params
- params = options[:params].is_a?(Hash) ?
- options[:params] :
- { params: options[:params] }
+ def path_for(options)
+ path = options[:script_name].to_s.chomp("/")
+ path << options[:path] if options.key?(:path)
- params.reject! { |_,v| v.to_param.nil? }
- result << "?#{params.to_query}" unless params.empty?
- end
+ add_trailing_slash(path) if options[:trailing_slash]
+ add_params(path, options[:params]) if options.key?(:params)
+ add_anchor(path, options[:anchor]) if options.key?(:anchor)
- result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
- result
+ path
end
private
+ def add_params(path, params)
+ params = { params: params } unless params.is_a?(Hash)
+ params.reject! { |_,v| v.to_param.nil? }
+ path << "?#{params.to_query}" unless params.empty?
+ end
+
+ def add_anchor(path, anchor)
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param.to_s)}"
+ end
+
def extract_domain_from(host, tld_length)
host.split('.').last(1 + tld_length).join('.')
end
@@ -76,22 +88,17 @@ module ActionDispatch
elsif !path.include?(".")
path.sub!(/[^\/]\z|\A\z/, '\&/')
end
-
- path
end
- def build_host_url(options)
- protocol = options[:protocol]
- host = options[:host]
- port = options[:port]
+ def build_host_url(host, port, protocol, options, path)
if match = host.match(HOST_REGEXP)
- protocol ||= match[1] unless protocol == false
- host = match[2]
- port = match[3] unless options.key? :port
+ protocol ||= match[1] unless protocol == false
+ host = match[2]
+ port = match[3] unless options.key? :port
end
- protocol = normalize_protocol protocol
- host = normalize_host(host, options)
+ protocol = normalize_protocol protocol
+ host = normalize_host(host, options)
result = protocol.dup
@@ -104,7 +111,7 @@ module ActionDispatch
result << ":#{normalized_port}"
}
- result
+ result.concat path
end
def named_host?(host)
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 6d58323789..992c1a9efe 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -12,12 +12,12 @@ module ActionDispatch
@cache = nil
end
- def generate(name, options, recall = {}, parameterize = nil)
- constraints = recall.merge(options)
+ def generate(name, options, path_parameters, parameterize = nil)
+ constraints = path_parameters.merge(options)
missing_keys = []
match_route(name, constraints) do |route|
- parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
+ parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
# Skip this route unless a name has been provided or it is a
# standard Rails route since we can't determine whether an options
@@ -40,7 +40,7 @@ module ActionDispatch
end
message = "No route matches #{Hash[constraints.sort].inspect}"
- message << " missing required keys: #{missing_keys.sort.inspect}" if name
+ message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty?
raise ActionController::UrlGenerationError, message
end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index 990d2127ee..1b914f0637 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -88,13 +88,13 @@ module ActionDispatch
erb = File.read File.join(viz_dir, 'index.html.erb')
states = "function tt() { return #{to_json}; }"
- fun_routes = paths.shuffle.first(3).map do |ast|
+ fun_routes = paths.sample(3).map do |ast|
ast.map { |n|
case n
when Nodes::Symbol
case n.left
when ':id' then rand(100).to_s
- when ':format' then %w{ xml json }.shuffle.first
+ when ':format' then %w{ xml json }.sample
else
'omg'
end
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index d129ba7e16..9012297400 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -86,7 +86,7 @@ racc_token_table = {
racc_nt_base = 10
-racc_use_result_var = true
+racc_use_result_var = false
Racc_arg = [
racc_action_table,
@@ -133,14 +133,12 @@ Racc_debug_parser = false
# reduce 0 omitted
-def _reduce_1(val, _values, result)
- result = Cat.new(val.first, val.last)
- result
+def _reduce_1(val, _values)
+ Cat.new(val.first, val.last)
end
-def _reduce_2(val, _values, result)
- result = val.first
- result
+def _reduce_2(val, _values)
+ val.first
end
# reduce 3 omitted
@@ -151,24 +149,20 @@ end
# reduce 6 omitted
-def _reduce_7(val, _values, result)
- result = Group.new(val[1])
- result
+def _reduce_7(val, _values)
+ Group.new(val[1])
end
-def _reduce_8(val, _values, result)
- result = Or.new([val.first, val.last])
- result
+def _reduce_8(val, _values)
+ Or.new([val.first, val.last])
end
-def _reduce_9(val, _values, result)
- result = Or.new([val.first, val.last])
- result
+def _reduce_9(val, _values)
+ Or.new([val.first, val.last])
end
-def _reduce_10(val, _values, result)
- result = Star.new(Symbol.new(val.last))
- result
+def _reduce_10(val, _values)
+ Star.new(Symbol.new(val.last))
end
# reduce 11 omitted
@@ -179,27 +173,23 @@ end
# reduce 14 omitted
-def _reduce_15(val, _values, result)
- result = Slash.new('/')
- result
+def _reduce_15(val, _values)
+ Slash.new('/')
end
-def _reduce_16(val, _values, result)
- result = Symbol.new(val.first)
- result
+def _reduce_16(val, _values)
+ Symbol.new(val.first)
end
-def _reduce_17(val, _values, result)
- result = Literal.new(val.first)
- result
+def _reduce_17(val, _values)
+ Literal.new(val.first)
end
-def _reduce_18(val, _values, result)
- result = Dot.new(val.first)
- result
+def _reduce_18(val, _values)
+ Dot.new(val.first)
end
-def _reduce_none(val, _values, result)
+def _reduce_none(val, _values)
val[0]
end
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
index 0ead222551..d3f7c4d765 100644
--- a/actionpack/lib/action_dispatch/journey/parser.y
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -1,11 +1,11 @@
class ActionDispatch::Journey::Parser
-
+ options no_result_var
token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
rule
expressions
- : expression expressions { result = Cat.new(val.first, val.last) }
- | expression { result = val.first }
+ : expression expressions { Cat.new(val.first, val.last) }
+ | expression { val.first }
| or
;
expression
@@ -14,14 +14,14 @@ rule
| star
;
group
- : LPAREN expressions RPAREN { result = Group.new(val[1]) }
+ : LPAREN expressions RPAREN { Group.new(val[1]) }
;
or
- : expression OR expression { result = Or.new([val.first, val.last]) }
- | expression OR or { result = Or.new([val.first, val.last]) }
+ : expression OR expression { Or.new([val.first, val.last]) }
+ | expression OR or { Or.new([val.first, val.last]) }
;
star
- : STAR { result = Star.new(Symbol.new(val.last)) }
+ : STAR { Star.new(Symbol.new(val.last)) }
;
terminal
: symbol
@@ -30,16 +30,16 @@ rule
| dot
;
slash
- : SLASH { result = Slash.new('/') }
+ : SLASH { Slash.new('/') }
;
symbol
- : SYMBOL { result = Symbol.new(val.first) }
+ : SYMBOL { Symbol.new(val.first) }
;
literal
- : LITERAL { result = Literal.new(val.first) }
+ : LITERAL { Literal.new(val.first) }
;
dot
- : DOT { result = Dot.new(val.first) }
+ : DOT { Dot.new(val.first) }
;
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 21817b374c..9131b65380 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -101,11 +101,13 @@ module ActionDispatch
r.path.match(req.path_info)
}
- if req.env["REQUEST_METHOD"] === "HEAD"
- routes.concat get_routes_as_head(routes)
- end
+ routes =
+ if req.request_method == "HEAD"
+ match_head_routes(routes, req)
+ else
+ match_routes(routes, req)
+ end
- routes.select! { |r| r.matches?(req) }
routes.sort_by!(&:precedence)
routes.map! { |r|
@@ -118,19 +120,23 @@ module ActionDispatch
}
end
- def get_routes_as_head(routes)
- precedence = (routes.map(&:precedence).max || 0) + 1
- routes.select { |r|
- r.verb === "GET" && !(r.verb === "HEAD")
- }.map! { |r|
- Route.new(r.name,
- r.app,
- r.path,
- r.conditions.merge(request_method: "HEAD"),
- r.defaults).tap do |route|
- route.precedence = r.precedence + precedence
- end
- }
+ def match_head_routes(routes, req)
+ head_routes = match_routes(routes, req)
+
+ if head_routes.empty?
+ begin
+ req.request_method = "GET"
+ match_routes(routes, req)
+ ensure
+ req.request_method = "HEAD"
+ end
+ else
+ head_routes
+ end
+ end
+
+ def match_routes(routes, req)
+ routes.select { |r| r.matches?(req) }
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index ac4ecb1e65..2b0a6575d4 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -25,9 +25,10 @@ module ActionDispatch
# http://tools.ietf.org/html/rfc3986
class UriEncoder # :nodoc:
ENCODE = "%%%02X".freeze
- ENCODING = Encoding::US_ASCII
- EMPTY = "".force_encoding(ENCODING).freeze
- DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(ENCODING) }
+ US_ASCII = Encoding::US_ASCII
+ UTF_8 = Encoding::UTF_8
+ 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
DIGIT = "0-9".freeze
@@ -53,12 +54,13 @@ module ActionDispatch
end
def unescape_uri(uri)
- uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(uri.encoding)
+ encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
+ uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(encoding)
end
protected
def escape(component, pattern)
- component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(ENCODING)
+ component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
end
def percent_encode(unsafe)
diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb
index 633be11a2d..19e0bc03d6 100644
--- a/actionpack/lib/action_dispatch/journey/scanner.rb
+++ b/actionpack/lib/action_dispatch/journey/scanner.rb
@@ -39,18 +39,18 @@ module ActionDispatch
[:SLASH, text]
when text = @ss.scan(/\*\w+/)
[:STAR, text]
- when text = @ss.scan(/\(/)
+ when text = @ss.scan(/(?<!\\)\(/)
[:LPAREN, text]
- when text = @ss.scan(/\)/)
+ when text = @ss.scan(/(?<!\\)\)/)
[:RPAREN, text]
when text = @ss.scan(/\|/)
[:OR, text]
when text = @ss.scan(/\./)
[:DOT, text]
- when text = @ss.scan(/:\w+/)
+ when text = @ss.scan(/(?<!\\):\w+/)
[:SYMBOL, text]
- when text = @ss.scan(/[\w%\-~]+/)
- [:LITERAL, text]
+ when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\:|\\\(|\\\))+/)
+ [:LITERAL, text.tr('\\', '')]
# any char
when text = @ss.scan(/./)
[:LITERAL, text]
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 22b16b628d..83ac62a83d 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
require 'active_support/message_verifier'
+require 'active_support/json'
module ActionDispatch
class Request < Rack::Request
@@ -90,6 +91,7 @@ module ActionDispatch
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
+ COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -173,10 +175,14 @@ module ActionDispatch
end
end
+ # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
+ # to the Message{Encryptor,Verifier} allows us to handle the
+ # (de)serialization step within the cookie jar, which gives us the
+ # opportunity to detect and migrate legacy cookies.
module VerifyAndUpgradeLegacySignedMessage
def initialize(*args)
super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: NullSerializer)
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def verify_and_upgrade_legacy_signed_message(name, signed_message)
@@ -212,7 +218,8 @@ module ActionDispatch
secret_token: env[SECRET_TOKEN],
secret_key_base: env[SECRET_KEY_BASE],
upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
- serializer: env[COOKIES_SERIALIZER]
+ serializer: env[COOKIES_SERIALIZER],
+ digest: env[COOKIES_DIGEST]
}
end
@@ -289,8 +296,8 @@ module ActionDispatch
end
end
- # Sets the cookie named +name+. The second argument may be the very cookie
- # value, or a hash of options as documented above.
+ # Sets the cookie named +name+. The second argument may be the cookie's
+ # value or a hash of options as documented above.
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
@@ -385,24 +392,11 @@ module ActionDispatch
class JsonSerializer
def self.load(value)
- JSON.parse(value, quirks_mode: true)
+ ActiveSupport::JSON.decode(value)
end
def self.dump(value)
- JSON.generate(value, quirks_mode: true)
- end
- end
-
- # Passing the NullSerializer downstream to the Message{Encryptor,Verifier}
- # allows us to handle the (de)serialization step within the cookie jar,
- # which gives us the opportunity to detect and migrate legacy cookies.
- class NullSerializer
- def self.load(value)
- value
- end
-
- def self.dump(value)
- value
+ ActiveSupport::JSON.encode(value)
end
end
@@ -441,6 +435,10 @@ module ActionDispatch
serializer
end
end
+
+ def digest
+ @options[:digest] || 'SHA1'
+ end
end
class SignedCookieJar #:nodoc:
@@ -451,7 +449,7 @@ module ActionDispatch
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:signed_cookie_salt])
- @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def [](name)
@@ -468,7 +466,7 @@ module ActionDispatch
options = { :value => @verifier.generate(serialize(name, options)) }
end
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
+ raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
@parent_jar[name] = options
end
@@ -508,7 +506,7 @@ module ActionDispatch
@options = options
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def [](name)
@@ -526,7 +524,7 @@ module ActionDispatch
options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
+ raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
@parent_jar[name] = options
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 0ca1a87645..798c087d64 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -35,12 +35,23 @@ module ActionDispatch
if env['action_dispatch.show_detailed_exceptions']
request = Request.new(env)
+ traces = wrapper.traces
+
+ trace_to_show = 'Application Trace'
+ if traces[trace_to_show].empty?
+ trace_to_show = 'Full Trace'
+ end
+
+ if source_to_show = traces[trace_to_show].first
+ source_to_show_id = source_to_show[:id]
+ end
+
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
request: request,
exception: wrapper.exception,
- application_trace: wrapper.application_trace,
- framework_trace: wrapper.framework_trace,
- full_trace: wrapper.full_trace,
+ traces: traces,
+ show_source_idx: source_to_show_id,
+ trace_to_show: trace_to_show,
routes_inspector: routes_inspector(exception),
source_extract: wrapper.source_extract,
line_number: wrapper.line_number,
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 2326bb043a..e0140b0692 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -6,16 +6,17 @@ module ActionDispatch
cattr_accessor :rescue_responses
@@rescue_responses = Hash.new(:internal_server_error)
@@rescue_responses.merge!(
- 'ActionController::RoutingError' => :not_found,
- 'AbstractController::ActionNotFound' => :not_found,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::UnknownHttpMethod' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::UnknownFormat' => :not_acceptable,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
- 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
- 'ActionController::BadRequest' => :bad_request,
- 'ActionController::ParameterMissing' => :bad_request
+ 'ActionController::RoutingError' => :not_found,
+ 'AbstractController::ActionNotFound' => :not_found,
+ 'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::UnknownHttpMethod' => :method_not_allowed,
+ 'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::UnknownFormat' => :not_acceptable,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
+ 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity,
+ 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
+ 'ActionController::BadRequest' => :bad_request,
+ 'ActionController::ParameterMissing' => :bad_request
)
cattr_accessor :rescue_templates
@@ -56,17 +57,42 @@ module ActionDispatch
clean_backtrace(:all)
end
+ def traces
+ appplication_trace_with_ids = []
+ framework_trace_with_ids = []
+ full_trace_with_ids = []
+
+ if full_trace
+ full_trace.each_with_index do |trace, idx|
+ trace_with_id = { id: idx, trace: trace }
+
+ appplication_trace_with_ids << trace_with_id if application_trace.include?(trace)
+ framework_trace_with_ids << trace_with_id if framework_trace.include?(trace)
+ full_trace_with_ids << trace_with_id
+ end
+ end
+
+ {
+ "Application Trace" => appplication_trace_with_ids,
+ "Framework Trace" => framework_trace_with_ids,
+ "Full Trace" => full_trace_with_ids
+ }
+ end
+
def self.status_code_for_exception(class_name)
Rack::Utils.status_code(@@rescue_responses[class_name])
end
def source_extract
- if application_trace && trace = application_trace.first
- file, line, _ = trace.split(":")
- @file = file
- @line_number = line.to_i
- source_fragment(@file, @line_number)
- end
+ exception.backtrace.map do |trace|
+ file, line = trace.split(":")
+ line_number = line.to_i
+ {
+ code: source_fragment(file, line_number),
+ file: file,
+ line_number: line_number
+ }
+ end if exception.backtrace
end
private
@@ -110,7 +136,7 @@ module ActionDispatch
def expand_backtrace
@exception.backtrace.unshift(
@exception.to_s.split("\n")
- ).flatten!
+ ).flatten!
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 4821d2a899..e90f8b9ce6 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -10,7 +10,7 @@ module ActionDispatch
end
end
- # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
+ # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
# action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
# then expose the flash to its template. Actually, that exposure is automatically done.
@@ -37,8 +37,11 @@ module ActionDispatch
# flash.alert = "You must be logged in"
# flash.notice = "Post successfully created"
#
- # This example just places a string in the flash, but you can put any object in there. And of course, you can put as
- # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
+ # This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass
+ # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to
+ # use sanitize helper.
+ #
+ # Just remember: They'll be gone by the time the next action has been performed.
#
# See docs on the FlashHash class for more details about the flash.
class Flash
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 6c8944e067..040cb215b7 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -1,4 +1,14 @@
module ActionDispatch
+ # When called, this middleware renders an error page. By default if an HTML
+ # response is expected it will render static error pages from the `/public`
+ # directory. For example when this middleware receives a 500 response it will
+ # render the template found in `/public/500.html`.
+ # If an internationalized locale is set, this middleware will attempt to render
+ # the template in `/public/500.<locale>.html`. If an internationalized template
+ # is not found it will fall back on `/public/500.html`.
+ #
+ # When a request with a content type other than HTML is made, this middleware
+ # will attempt to convert error information into the appropriate response type.
class PublicExceptions
attr_accessor :public_path
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 6a79b4e859..7c4236518d 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -1,3 +1,5 @@
+require 'ipaddr'
+
module ActionDispatch
# This middleware calculates the IP address of the remote client that is
# making the request. It does this by checking various headers that could
@@ -28,14 +30,14 @@ module ActionDispatch
# guaranteed by the IP specification to be private addresses. Those will
# not be the ultimate client IP in production, and so are discarded. See
# http://en.wikipedia.org/wiki/Private_network for details.
- TRUSTED_PROXIES = %r{
- ^127\.0\.0\.1$ | # localhost IPv4
- ^::1$ | # localhost IPv6
- ^[fF][cCdD] | # private IPv6 range fc00::/7
- ^10\. | # private IPv4 range 10.x.x.x
- ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
- ^192\.168\. # private IPv4 range 192.168.x.x
- }x
+ TRUSTED_PROXIES = [
+ "127.0.0.1", # localhost IPv4
+ "::1", # localhost IPv6
+ "fc00::/7", # private IPv6 range fc00::/7
+ "10.0.0.0/8", # private IPv4 range 10.x.x.x
+ "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
+ "192.168.0.0/16", # private IPv4 range 192.168.x.x
+ ].map { |proxy| IPAddr.new(proxy) }
attr_reader :check_ip, :proxies
@@ -47,24 +49,24 @@ module ActionDispatch
# clients (like WAP devices), or behind proxies that set headers in an
# incorrect or confusing way (like AWS ELB).
#
- # The +custom_proxies+ argument can take a regex, which will be used
- # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
- # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
- # middle (or at the beginning) of the X-Forwarded-For list, with your proxy
- # servers after it. If your proxies aren't removed, pass them in via the
- # +custom_proxies+ parameter. That way, the middleware will ignore those
- # IP addresses, and return the one that you want.
+ # The +custom_proxies+ argument can take an Array of string, IPAddr, or
+ # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
+ # single string, IPAddr, or Regexp object is provided, it will be used in
+ # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
+ # want in the middle (or at the beginning) of the X-Forwarded-For list,
+ # with your proxy servers after it. If your proxies aren't removed, pass
+ # them in via the +custom_proxies+ parameter. That way, the middleware will
+ # ignore those IP addresses, and return the one that you want.
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
@check_ip = check_ip_spoofing
- @proxies = case custom_proxies
- when Regexp
- custom_proxies
- when nil
- TRUSTED_PROXIES
- else
- Regexp.union(TRUSTED_PROXIES, custom_proxies)
- end
+ @proxies = if custom_proxies.blank?
+ TRUSTED_PROXIES
+ elsif custom_proxies.respond_to?(:any?)
+ custom_proxies
+ else
+ Array(custom_proxies) + TRUSTED_PROXIES
+ end
end
# Since the IP address may not be needed, we store the object here
@@ -80,32 +82,6 @@ module ActionDispatch
# into an actual IP address. If the ActionDispatch::Request#remote_ip method
# is called, this class will calculate the value and then memoize it.
class GetIp
-
- # This constant contains a regular expression that validates every known
- # form of IP v4 and v6 address, with or without abbreviations, adapted
- # from {this gist}[https://gist.github.com/gazay/1289635].
- VALID_IP = %r{
- (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
- (^(
- (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
- (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
- (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
- (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
- (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
- (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
- (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
- ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
- (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
- (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
- )$)
- }x
-
def initialize(env, middleware)
@env = env
@check_ip = middleware.check_ip
@@ -173,12 +149,22 @@ module ActionDispatch
def ips_from(header)
# Split the comma-separated list into an array of strings
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
- # Only return IPs that are valid according to the regex
- ips.select{ |ip| ip =~ VALID_IP }
+ ips.select do |ip|
+ begin
+ # Only return IPs that are valid according to the IPAddr#new method
+ range = IPAddr.new(ip).to_range
+ # we want to make sure nobody is sneaking a netmask in
+ range.begin == range.end
+ rescue ArgumentError
+ nil
+ end
+ end
end
def filter_proxies(ips)
- ips.reject { |ip| ip =~ @proxies }
+ ips.reject do |ip|
+ @proxies.any? { |proxy| proxy === ip }
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 5d1740d0d4..25658bac3d 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -5,7 +5,7 @@ module ActionDispatch
# Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
# ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
#
- # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
+ # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
# header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
#
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index 1db6194271..625050dc4b 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -16,9 +16,9 @@ module ActionDispatch
# Get a session from the cache.
def get_session(env, sid)
- sid ||= generate_sid
- session = @cache.read(cache_key(sid))
- session ||= {}
+ unless sid and session = @cache.read(cache_key(sid))
+ sid, session = generate_sid, {}
+ end
[sid, session]
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 0864e7ef2a..ed25c67ae5 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -49,7 +49,7 @@ module ActionDispatch
# reasonably sure that your upgrade is otherwise complete. Additionally,
# you should take care to make sure you are not relying on the ability to
# decode signed cookies generated by your app in external applications or
- # Javascript before upgrading.
+ # JavaScript before upgrading.
#
# Note that changing the secret key will invalidate all existing sessions!
class CookieStore < Rack::Session::Abstract::ID
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 1d4f0f89a6..f0779279c1 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -42,6 +42,7 @@ module ActionDispatch
wrapper = ExceptionWrapper.new(env, exception)
status = wrapper.status_code
env["action_dispatch.exception"] = wrapper.exception
+ env["action_dispatch.original_path"] = env["PATH_INFO"]
env["PATH_INFO"] = "/#{status}"
response = @exceptions_app.call(env)
response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 2764584fe9..002bf8b11a 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -2,49 +2,98 @@ require 'rack/utils'
require 'active_support/core_ext/uri'
module ActionDispatch
+ # This middleware returns a file's contents from disk in the body response.
+ # When initialized it can accept an optional 'Cache-Control' header which
+ # will be set when a response containing a file's contents is delivered.
+ #
+ # This middleware will render the file specified in `env["PATH_INFO"]`
+ # where the base path is in the +root+ directory. For example if the +root+
+ # is set to `public/` then a request with `env["PATH_INFO"]` of
+ # `assets/application.js` will return a response with contents of a file
+ # located at `public/assets/application.js` if the file exists. If the file
+ # does not exist a 404 "File not Found" response will be returned.
class FileHandler
def initialize(root, cache_control)
@root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
- headers = cache_control && { 'Cache-Control' => cache_control }
- @file_server = ::Rack::File.new(@root, headers)
+ headers = cache_control && { 'Cache-Control' => cache_control }
+ @file_server = ::Rack::File.new(@root, headers)
end
def match?(path)
- path = unescape_path(path)
+ path = URI.parser.unescape(path)
return false unless path.valid_encoding?
- full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(path))
- paths = "#{full_path}#{ext}"
+ paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"].map { |v|
+ Rack::Utils.clean_path_info v
+ }
- matches = Dir[paths]
- match = matches.detect { |m| File.file?(m) }
- if match
- match.sub!(@compiled_root, '')
- ::Rack::Utils.escape(match)
+ if match = paths.detect { |p|
+ path = File.join(@root, p)
+ begin
+ File.file?(path) && File.readable?(path)
+ rescue SystemCallError
+ false
+ end
+
+ }
+ return ::Rack::Utils.escape(match)
end
end
def call(env)
- @file_server.call(env)
- end
+ path = env['PATH_INFO']
+ gzip_path = gzip_file_path(path)
- def ext
- @ext ||= begin
- ext = ::ActionController::Base.default_static_extension
- "{,#{ext},/index#{ext}}"
+ if gzip_path && gzip_encoding_accepted?(env)
+ env['PATH_INFO'] = gzip_path
+ status, headers, body = @file_server.call(env)
+ headers['Content-Encoding'] = 'gzip'
+ headers['Content-Type'] = content_type(path)
+ else
+ status, headers, body = @file_server.call(env)
end
- end
- def unescape_path(path)
- URI.parser.unescape(path)
- end
+ headers['Vary'] = 'Accept-Encoding' if gzip_path
- def escape_glob_chars(path)
- path.gsub(/[*?{}\[\]]/, "\\\\\\&")
+ return [status, headers, body]
+ ensure
+ env['PATH_INFO'] = path
end
+
+ private
+ def ext
+ ::ActionController::Base.default_static_extension
+ end
+
+ def content_type(path)
+ ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
+ end
+
+ def gzip_encoding_accepted?(env)
+ env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
+ end
+
+ def gzip_file_path(path)
+ can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
+ gzip_path = "#{path}.gz"
+ if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape(gzip_path)))
+ gzip_path
+ else
+ false
+ end
+ end
end
+ # This middleware will attempt to return the contents of a file's body from
+ # disk in the response. If a file is not found on disk, the request will be
+ # delegated to the application stack. This middleware is commonly initialized
+ # to serve assets from a server's `public/` directory.
+ #
+ # This middleware verifies the path to ensure that only files
+ # living in the root directory can be rendered. A request cannot
+ # produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
+ # requests will result in a file being returned.
class Static
def initialize(app, path, cache_control=nil)
@app = app
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
index 38429cb78e..eabac3a9d2 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
@@ -1,25 +1,29 @@
<% if @source_extract %>
-<div class="source">
-<div class="info">
- Extracted source (around line <strong>#<%= @line_number %></strong>):
-</div>
-<div class="data">
- <table cellpadding="0" cellspacing="0" class="lines">
- <tr>
- <td>
- <pre class="line_numbers">
- <% @source_extract.keys.each do |line_number| %>
+ <% @source_extract.each_with_index do |extract_source, index| %>
+ <% if extract_source[:code] %>
+ <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
+ <div class="info">
+ Extracted source (around line <strong>#<%= extract_source[:line_number] %></strong>):
+ </div>
+ <div class="data">
+ <table cellpadding="0" cellspacing="0" class="lines">
+ <tr>
+ <td>
+ <pre class="line_numbers">
+ <% extract_source[:code].each_key do |line_number| %>
<span><%= line_number -%></span>
- <% end %>
- </pre>
- </td>
+ <% end %>
+ </pre>
+ </td>
<td width="100%">
<pre>
-<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @line_number -%>"><%= source -%></div><% end -%>
+<% extract_source[:code].each do |line, source| -%><div class="line<%= " active" if line == extract_source[:line_number] -%>"><%= source -%></div><% end -%>
</pre>
</td>
- </tr>
- </table>
-</div>
-</div>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <% end %>
+ <% end %>
<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
index b181909bff..ab57b11c7d 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
@@ -1,9 +1,4 @@
-<%
- traces = { "Application Trace" => @application_trace,
- "Framework Trace" => @framework_trace,
- "Full Trace" => @full_trace }
- names = traces.keys
-%>
+<% names = @traces.keys %>
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
@@ -16,9 +11,42 @@
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
<% end %>
- <% traces.each do |name, trace| %>
- <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;">
- <pre><code><%= trace.join "\n" %></code></pre>
+ <% @traces.each do |name, trace| %>
+ <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;">
+ <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
</div>
<% end %>
+
+ <script type="text/javascript">
+ var traceFrames = document.getElementsByClassName('trace-frames');
+ var selectedFrame, currentSource = document.getElementById('frame-source-0');
+
+ // Add click listeners for all stack frames
+ for (var i = 0; i < traceFrames.length; i++) {
+ traceFrames[i].addEventListener('click', function(e) {
+ e.preventDefault();
+ var target = e.target;
+ var frame_id = target.dataset.frameId;
+
+ if (selectedFrame) {
+ selectedFrame.className = selectedFrame.className.replace("selected", "");
+ }
+
+ target.className += " selected";
+ selectedFrame = target;
+
+ // Change the extracted source code
+ changeSourceExtract(frame_id);
+ });
+
+ function changeSourceExtract(frame_id) {
+ var el = document.getElementById('frame-source-' + frame_id);
+ if (currentSource && el) {
+ currentSource.className += " hidden";
+ el.className = el.className.replace(" hidden", "");
+ currentSource = el;
+ }
+ }
+ }
+ </script>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
index d4af5c9b06..c0b53068f7 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
@@ -1,15 +1,9 @@
-<%
- traces = { "Application Trace" => @application_trace,
- "Framework Trace" => @framework_trace,
- "Full Trace" => @full_trace }
-%>
-
Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>
-<% traces.each do |name, trace| %>
+<% @traces.each do |name, trace| %>
<% if trace.any? %>
<%= name %>
-<%= trace.join("\n") %>
+<%= trace.map { |t| t[:trace] }.join("\n") %>
<% end %>
<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index bc5d03dc10..e0509f56f4 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -116,9 +116,15 @@
background-color: #FFCCCC;
}
+ .hidden {
+ display: none;
+ }
+
a { color: #980905; }
a:visited { color: #666; }
+ a.trace-frames { color: #666; }
a:hover { color: #C52F24; }
+ a.trace-frames.selected { color: #C52F24 }
<%= yield :style %>
</style>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
index 027a0f5b3e..c1e8b6cae3 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
@@ -1,4 +1,3 @@
-<% @source_extract = @exception.source_extract(0, :html) %>
<header>
<h1>
<%= @exception.original_exception.class.to_s %> in
@@ -12,29 +11,7 @@
</p>
<pre><code><%= h @exception.message %></code></pre>
- <div class="source">
- <div class="info">
- <p>Extracted source (around line <strong>#<%= @exception.line_number %></strong>):</p>
- </div>
- <div class="data">
- <table cellpadding="0" cellspacing="0" class="lines">
- <tr>
- <td>
- <pre class="line_numbers">
- <% @source_extract.keys.each do |line_number| %>
-<span><%= line_number -%></span>
- <% end %>
- </pre>
- </td>
-<td width="100%">
-<pre>
-<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @exception.line_number -%>"><%= source -%></div><% end -%>
-</pre>
-</td>
- </tr>
- </table>
-</div>
-</div>
+ <%= render template: "rescues/_source" %>
<p><%= @exception.sub_template_message %></p>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
index 5da21d9784..77bcd26726 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
@@ -1,4 +1,3 @@
-<% @source_extract = @exception.source_extract(0, :html) %>
<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised:
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index ea3b2f419d..cfe2237512 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -48,7 +48,7 @@ module ActionDispatch
def reqs
@reqs ||= begin
reqs = endpoint
- reqs += " #{constraints.to_s}" unless constraints.empty?
+ reqs += " #{constraints}" unless constraints.empty?
reqs
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index aac5546aa1..ac03ecb2c8 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/core_ext/string/filters'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
require 'action_dispatch/routing/endpoint'
@@ -13,9 +14,6 @@ module ActionDispatch
module Routing
class Mapper
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
- SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
- :controller, :action, :path_names, :constraints,
- :shallow, :blocks, :defaults, :options]
class Constraints < Endpoint #:nodoc:
attr_reader :app, :constraints
@@ -66,7 +64,7 @@ module ActionDispatch
attr_reader :requirements, :conditions, :defaults
attr_reader :to, :default_controller, :default_action, :as, :anchor
- def self.build(scope, path, options)
+ def self.build(scope, set, path, as, options)
options = scope[:options].merge(options) if scope[:options]
options.delete :only
@@ -77,17 +75,18 @@ module ActionDispatch
defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
- new scope, path, defaults, options
+ new scope, set, path, defaults, as, options
end
- def initialize(scope, path, defaults, options)
+ def initialize(scope, set, path, defaults, as, options)
@requirements, @conditions = {}, {}
@defaults = defaults
+ @set = set
@to = options.delete :to
@default_controller = options.delete(:controller) || scope[:controller]
@default_action = options.delete(:action) || scope[:action]
- @as = options.delete :as
+ @as = as
@anchor = options.delete :anchor
formatted = options.delete :format
@@ -243,15 +242,13 @@ module ActionDispatch
end
def app(blocks)
- return to if Redirect === to
-
if to.respond_to?(:call)
Constraints.new(to, blocks, false)
else
if blocks.any?
- Constraints.new(dispatcher, blocks, true)
+ Constraints.new(dispatcher(defaults), blocks, true)
else
- dispatcher
+ dispatcher(defaults)
end
end
end
@@ -286,11 +283,19 @@ module ActionDispatch
def split_to(to)
case to
when Symbol
- ActiveSupport::Deprecation.warn "defining a route where `to` is a symbol is deprecated. Please change \"to: :#{to}\" to \"action: :#{to}\""
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Defining a route where `to` is a symbol is deprecated.
+ Please change `to: :#{to}` to `action: :#{to}`.
+ MSG
+
[nil, to.to_s]
when /#/ then to.split('#')
when String
- ActiveSupport::Deprecation.warn "defining a route where `to` is a controller without an action is deprecated. Please change \"to: :#{to}\" to \"controller: :#{to}\""
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Defining a route where `to` is a controller without an action is deprecated.
+ Please change `to: :#{to}` to `controller: :#{to}`.
+ MSG
+
[to, nil]
else
[]
@@ -348,8 +353,8 @@ module ActionDispatch
parser.parse path
end
- def dispatcher
- Routing::RouteSet::Dispatcher.new(defaults)
+ def dispatcher(defaults)
+ @set.dispatcher defaults
end
end
@@ -438,7 +443,7 @@ module ActionDispatch
#
# Because requesting various HTTP verbs with a single action has security
# implications, you must either specify the actions in
- # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers]
+ # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
# instead +match+
#
# === Options
@@ -576,13 +581,21 @@ module ActionDispatch
raise "A rack application must be specified" unless path
- options[:as] ||= app_name(app)
+ rails_app = rails_app? app
+
+ if rails_app
+ options[:as] ||= app.railtie_name
+ else
+ # non rails apps can't have an :as
+ options[:as] = nil
+ end
+
target_as = name_for_action(options[:as], path)
options[:via] ||= :all
match(path, options.merge(:to => app, :anchor => false, :format => false))
- define_generate_prefix(app, target_as)
+ define_generate_prefix(app, target_as) if rails_app
self
end
@@ -603,31 +616,24 @@ module ActionDispatch
end
private
- def app_name(app)
- return unless app.respond_to?(:routes)
-
- if app.respond_to?(:railtie_name)
- app.railtie_name
- else
- class_name = app.class.is_a?(Class) ? app.name : app.class.name
- ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
- end
+ def rails_app?(app)
+ app.is_a?(Class) && app < Rails::Railtie
end
def define_generate_prefix(app, name)
- return unless app.respond_to?(:routes) && app.routes.respond_to?(:define_mounted_helper)
-
- _route = @set.named_routes.routes[name.to_sym]
+ _route = @set.named_routes.get name
_routes = @set
app.routes.define_mounted_helper(name)
app.routes.extend Module.new {
- def mounted?; true; end
+ def optimize_routes_generation?; false; end
define_method :find_script_name do |options|
- super(options) || begin
- prefix_options = options.slice(*_route.segment_keys)
- # 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)
+ if options.key? :script_name
+ super(options)
+ else
+ prefix_options = options.slice(*_route.segment_keys)
+ # 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)
end
end
}
@@ -717,7 +723,7 @@ module ActionDispatch
# resources :posts, module: "admin"
#
# If you want to route /admin/posts to +PostsController+
- # (without the Admin:: module prefix), you could use
+ # (without the <tt>Admin::</tt> module prefix), you could use
#
# scope "/admin" do
# resources :posts
@@ -771,7 +777,7 @@ module ActionDispatch
# end
def scope(*args)
options = args.extract_options!.dup
- recover = {}
+ scope = {}
options[:path] = args.flatten.join('/') if args.any?
options[:constraints] ||= {}
@@ -791,7 +797,7 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
- SCOPE_OPTIONS.each do |option|
+ @scope.options.each do |option|
if option == :blocks
value = block
elsif option == :options
@@ -801,15 +807,15 @@ module ActionDispatch
end
if value
- recover[option] = @scope[option]
- @scope[option] = send("merge_#{option}_scope", @scope[option], value)
+ scope[option] = send("merge_#{option}_scope", @scope[option], value)
end
end
+ @scope = @scope.new scope
yield
self
ensure
- @scope.merge!(recover)
+ @scope = @scope.parent
end
# Scopes routes to a specific controller
@@ -1047,8 +1053,6 @@ module ActionDispatch
VALID_ON_OPTIONS = [:new, :collection, :member]
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
CANONICAL_ACTIONS = %w(index create new show update destroy)
- RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
- RESOURCE_SCOPES = [:resource, :resources]
class Resource #:nodoc:
attr_reader :controller, :path, :options, :param
@@ -1434,7 +1438,7 @@ module ActionDispatch
end
with_scope_level(:nested) do
- if shallow? && shallow_nesting_depth > 1
+ if shallow? && shallow_nesting_depth >= 1
shallow_scope(parent_resource.nested_scope, nested_options) { yield }
else
scope(parent_resource.nested_scope, nested_options) { yield }
@@ -1522,7 +1526,7 @@ module ActionDispatch
if on = options.delete(:on)
send(on) { decomposed_match(path, options) }
else
- case @scope[:scope_level]
+ case @scope.scope_level
when :resources
nested { decomposed_match(path, options) }
when :resource
@@ -1545,13 +1549,13 @@ module ActionDispatch
action = nil
end
- if !options.fetch(:as, true)
- options.delete(:as)
- else
- options[:as] = name_for_action(options[:as], action)
- end
+ as = if !options.fetch(:as, true) # if it's set to nil or false
+ options.delete(:as)
+ else
+ name_for_action(options.delete(:as), action)
+ end
- mapping = Mapping.build(@scope, URI.parser.escape(path), options)
+ mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
@@ -1565,7 +1569,7 @@ module ActionDispatch
raise ArgumentError, "must be called with a path and/or options"
end
- if @scope[:scope_level] == :resources
+ if @scope.resources?
with_scope_level(:root) do
scope(parent_resource.path) do
super(options)
@@ -1632,40 +1636,39 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- RESOURCE_SCOPES.include? @scope[:scope_level]
+ @scope.resource_scope?
end
def resource_method_scope? #:nodoc:
- RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
+ @scope.resource_method_scope?
end
def nested_scope? #:nodoc:
- @scope[:scope_level] == :nested
+ @scope.nested?
end
def with_exclusive_scope
begin
- old_name_prefix, old_path = @scope[:as], @scope[:path]
- @scope[:as], @scope[:path] = nil, nil
+ @scope = @scope.new(:as => nil, :path => nil)
with_scope_level(:exclusive) do
yield
end
ensure
- @scope[:as], @scope[:path] = old_name_prefix, old_path
+ @scope = @scope.parent
end
end
def with_scope_level(kind)
- old, @scope[:scope_level] = @scope[:scope_level], kind
+ @scope = @scope.new_level(kind)
yield
ensure
- @scope[:scope_level] = old
+ @scope = @scope.parent
end
def resource_scope(kind, resource) #:nodoc:
resource.shallow = @scope[:shallow]
- old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
+ @scope = @scope.new(:scope_level_resource => resource)
@nesting.push(resource)
with_scope_level(kind) do
@@ -1673,7 +1676,7 @@ module ActionDispatch
end
ensure
@nesting.pop
- @scope[:scope_level_resource] = old_resource
+ @scope = @scope.parent
end
def nested_options #:nodoc:
@@ -1701,21 +1704,22 @@ module ActionDispatch
@scope[:constraints][parent_resource.param]
end
- def canonical_action?(action, flag) #:nodoc:
- flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
+ def canonical_action?(action) #:nodoc:
+ resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
def shallow_scope(path, options = {}) #:nodoc:
- old_name_prefix, old_path = @scope[:as], @scope[:path]
- @scope[:as], @scope[:path] = @scope[:shallow_prefix], @scope[:shallow_path]
+ scope = { :as => @scope[:shallow_prefix],
+ :path => @scope[:shallow_path] }
+ @scope = @scope.new scope
scope(path, options) { yield }
ensure
- @scope[:as], @scope[:path] = old_name_prefix, old_path
+ @scope = @scope.parent
end
def path_for_action(action, path) #:nodoc:
- if canonical_action?(action, path.blank?)
+ if path.blank? && canonical_action?(action)
@scope[:path].to_s
else
"#{@scope[:path]}/#{action_path(action, path)}"
@@ -1730,15 +1734,17 @@ module ActionDispatch
def prefix_name_for_action(as, action) #:nodoc:
if as
prefix = as
- elsif !canonical_action?(action, @scope[:scope_level])
+ elsif !canonical_action?(action)
prefix = action
end
- prefix.to_s.tr('-', '_') if prefix
+
+ if prefix && prefix != '/' && !prefix.empty?
+ Mapper.normalize_name prefix.to_s.tr('-', '_')
+ end
end
def name_for_action(as, action) #:nodoc:
prefix = prefix_name_for_action(as, action)
- prefix = Mapper.normalize_name(prefix) if prefix
name_prefix = @scope[:as]
if parent_resource
@@ -1748,27 +1754,14 @@ module ActionDispatch
member_name = parent_resource.member_name
end
- name = case @scope[:scope_level]
- when :nested
- [name_prefix, prefix]
- when :collection
- [prefix, name_prefix, collection_name]
- when :new
- [prefix, :new, name_prefix, member_name]
- when :member
- [prefix, name_prefix, member_name]
- when :root
- [name_prefix, collection_name, prefix]
- else
- [name_prefix, member_name, prefix]
- end
+ name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
- if candidate = name.select(&:present?).join("_").presence
+ if candidate = name.compact.join("_").presence
# If a name was not explicitly given, we check if it is valid
# and return nil in case it isn't. Otherwise, we pass the invalid name
# forward so the underlying router engine treats it and raises an exception.
if as.nil?
- candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i
+ candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
else
candidate
end
@@ -1893,9 +1886,83 @@ module ActionDispatch
end
end
+ class Scope # :nodoc:
+ OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
+ :controller, :action, :path_names, :constraints,
+ :shallow, :blocks, :defaults, :options]
+
+ RESOURCE_SCOPES = [:resource, :resources]
+ RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
+
+ attr_reader :parent, :scope_level
+
+ def initialize(hash, parent = {}, scope_level = nil)
+ @hash = hash
+ @parent = parent
+ @scope_level = scope_level
+ end
+
+ def nested?
+ scope_level == :nested
+ end
+
+ def resources?
+ scope_level == :resources
+ end
+
+ def resource_method_scope?
+ RESOURCE_METHOD_SCOPES.include? scope_level
+ end
+
+ def action_name(name_prefix, prefix, collection_name, member_name)
+ case scope_level
+ when :nested
+ [name_prefix, prefix]
+ when :collection
+ [prefix, name_prefix, collection_name]
+ when :new
+ [prefix, :new, name_prefix, member_name]
+ when :member
+ [prefix, name_prefix, member_name]
+ when :root
+ [name_prefix, collection_name, prefix]
+ else
+ [name_prefix, member_name, prefix]
+ end
+ end
+
+ def resource_scope?
+ RESOURCE_SCOPES.include? scope_level
+ end
+
+ def options
+ OPTIONS
+ end
+
+ def new(hash)
+ self.class.new hash, self, scope_level
+ end
+
+ def new_level(level)
+ self.class.new(self, self, level)
+ end
+
+ def fetch(key, &block)
+ @hash.fetch(key, &block)
+ end
+
+ def [](key)
+ @hash.fetch(key) { @parent[key] }
+ end
+
+ def []=(k,v)
+ @hash[k] = v
+ end
+ end
+
def initialize(set) #:nodoc:
@set = set
- @scope = { :path_names => @set.resources_path_names }
+ @scope = Scope.new({ :path_names => @set.resources_path_names })
@concerns = {}
@nesting = []
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index bd3696cda1..0847842fa2 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -116,7 +116,6 @@ module ActionDispatch
action,
type,
opts
-
end
# Returns the path component of a URL for the given record. It uses
@@ -142,22 +141,26 @@ module ActionDispatch
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__ + 1
- def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
- end # end
- #
- def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
- end # end
+ def #{action}_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url_for_action("#{action}", record_or_hash, options)
+ end
+
+ def #{action}_polymorphic_path(record_or_hash, options = {})
+ polymorphic_path_for_action("#{action}", record_or_hash, options)
+ end
EOT
end
private
+ def polymorphic_url_for_action(action, record_or_hash, options)
+ polymorphic_url(record_or_hash, options.merge(:action => action))
+ end
+
+ def polymorphic_path_for_action(action, record_or_hash, options)
+ polymorphic_path(record_or_hash, options.merge(:action => action))
+ end
+
class HelperMethodBuilder # :nodoc:
CACHE = { 'path' => {}, 'url' => {} }
@@ -192,7 +195,8 @@ module ActionDispatch
case record_or_hash_or_array
when Array
- if record_or_hash_or_array.empty? || record_or_hash_or_array.include?(nil)
+ record_or_hash_or_array = record_or_hash_or_array.compact
+ if record_or_hash_or_array.empty?
raise ArgumentError, "Nil location provided. Can't build URI."
end
if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
@@ -249,9 +253,9 @@ module ActionDispatch
model = record.to_model
name = if record.persisted?
args << model
- model.class.model_name.singular_route_key
+ model.model_name.singular_route_key
else
- @key_strategy.call model.class.model_name
+ @key_strategy.call model.model_name
end
named_route = prefix + "#{name}_#{suffix}"
@@ -279,7 +283,7 @@ module ActionDispatch
parent.model_name.singular_route_key
else
args << parent.to_model
- parent.to_model.class.model_name.singular_route_key
+ parent.to_model.model_name.singular_route_key
end
}
@@ -292,9 +296,9 @@ module ActionDispatch
else
if record.persisted?
args << record.to_model
- record.to_model.class.model_name.singular_route_key
+ record.to_model.model_name.singular_route_key
else
- @key_strategy.call record.to_model.class.model_name
+ @key_strategy.call record.to_model.model_name
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 69535faabd..a641ea3ea9 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -6,6 +6,7 @@ require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/string/filters'
require 'action_controller/metal/exceptions'
require 'action_dispatch/http/request'
require 'action_dispatch/routing/endpoint'
@@ -86,36 +87,72 @@ module ActionDispatch
# named routes.
class NamedRouteCollection #:nodoc:
include Enumerable
- attr_reader :routes, :helpers, :module
+ attr_reader :routes, :url_helpers_module
def initialize
@routes = {}
- @helpers = []
- @module = Module.new
+ @path_helpers = Set.new
+ @url_helpers = Set.new
+ @url_helpers_module = Module.new
+ @path_helpers_module = Module.new
+ end
+
+ def route_defined?(name)
+ key = name.to_sym
+ @path_helpers.include?(key) || @url_helpers.include?(key)
+ end
+
+ def helpers
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `named_routes.helpers` is deprecated, please use `route_defined?(route_name)`
+ to see if a named route was defined.
+ MSG
+ @path_helpers + @url_helpers
end
def helper_names
- @helpers.map(&:to_s)
+ @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s)
end
def clear!
- @helpers.each do |helper|
- @module.remove_possible_method helper
+ @path_helpers.each do |helper|
+ @path_helpers_module.send :undef_method, helper
+ end
+
+ @url_helpers.each do |helper|
+ @url_helpers_module.send :undef_method, helper
end
@routes.clear
- @helpers.clear
+ @path_helpers.clear
+ @url_helpers.clear
end
def add(name, route)
- routes[name.to_sym] = route
- define_named_route_methods(name, route)
+ key = name.to_sym
+ path_name = :"#{name}_path"
+ url_name = :"#{name}_url"
+
+ if routes.key? key
+ @path_helpers_module.send :undef_method, path_name
+ @url_helpers_module.send :undef_method, url_name
+ end
+ routes[key] = route
+ define_url_helper @path_helpers_module, route, path_name, route.defaults, name, LEGACY
+ define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN
+
+ @path_helpers << path_name
+ @url_helpers << url_name
end
def get(name)
routes[name.to_sym]
end
+ def key?(name)
+ routes.key? name.to_sym
+ end
+
alias []= add
alias [] get
alias clear clear!
@@ -133,12 +170,31 @@ module ActionDispatch
routes.length
end
+ def path_helpers_module(warn = false)
+ if warn
+ mod = @path_helpers_module
+ helpers = @path_helpers
+ Module.new do
+ include mod
+
+ helpers.each do |meth|
+ define_method(meth) do |*args, &block|
+ ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead")
+ super(*args, &block)
+ end
+ end
+ end
+ else
+ @path_helpers_module
+ end
+ end
+
class UrlHelper # :nodoc:
- def self.create(route, options)
+ def self.create(route, options, route_name, url_strategy)
if optimize_helper?(route)
- OptimizedUrlHelper.new(route, options)
+ OptimizedUrlHelper.new(route, options, route_name, url_strategy)
else
- new route, options
+ new route, options, route_name, url_strategy
end
end
@@ -146,20 +202,22 @@ module ActionDispatch
!route.glob? && route.path.requirements.empty?
end
+ attr_reader :url_strategy, :route_name
+
class OptimizedUrlHelper < UrlHelper # :nodoc:
attr_reader :arg_size
- def initialize(route, options)
+ def initialize(route, options, route_name, url_strategy)
super
@required_parts = @route.required_parts
@arg_size = @required_parts.size
end
- def call(t, args)
- if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
+ def call(t, args, inner_options)
+ if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
options = t.url_options.merge @options
options[:path] = optimized_helper(args)
- ActionDispatch::Http::URL.url_for(options)
+ url_strategy.call options
else
super
end
@@ -201,21 +259,27 @@ module ActionDispatch
end
end
- def initialize(route, options)
+ def initialize(route, options, route_name, url_strategy)
@options = options
@segment_keys = route.segment_keys.uniq
@route = route
+ @url_strategy = url_strategy
+ @route_name = route_name
end
- def call(t, args)
+ def call(t, args, inner_options)
controller_options = t.url_options
options = controller_options.merge @options
- hash = handle_positional_args(controller_options, args, options, @segment_keys)
- t._routes.url_for(hash)
+ hash = handle_positional_args(controller_options,
+ inner_options || {},
+ args,
+ options,
+ @segment_keys)
+
+ t._routes.url_for(hash, route_name, url_strategy)
end
- def handle_positional_args(controller_options, args, result, path_params)
- inner_options = args.extract_options!
+ def handle_positional_args(controller_options, inner_options, args, result, path_params)
if args.size > 0
if args.size < path_params.size - 1 # take format into account
@@ -245,26 +309,50 @@ module ActionDispatch
#
# foo_url(bar, baz, bang, sort_by: 'baz')
#
- def define_url_helper(route, name, options)
- helper = UrlHelper.create(route, options.dup)
-
- @module.remove_possible_method name
- @module.module_eval do
+ def define_url_helper(mod, route, name, opts, route_key, url_strategy)
+ helper = UrlHelper.create(route, opts, route_key, url_strategy)
+ mod.module_eval do
define_method(name) do |*args|
- helper.call self, args
+ options = nil
+ options = args.pop if args.last.is_a? Hash
+ helper.call self, args, options
end
end
-
- helpers << name
end
+ end
- def define_named_route_methods(name, route)
- define_url_helper route, :"#{name}_path",
- route.defaults.merge(:use_route => name, :only_path => true)
- define_url_helper route, :"#{name}_url",
- route.defaults.merge(:use_route => name, :only_path => false)
+ # :stopdoc:
+ # strategy for building urls to send to the client
+ PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
+ FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) }
+ UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
+ LEGACY = ->(options) {
+ if options.key?(:only_path)
+ if options[:only_path]
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ You are calling a `*_path` helper with the `only_path` option
+ explicitly set to `true`. This option will stop working on
+ path helpers in Rails 5. Simply remove the `only_path: true`
+ argument from your call as it is redundant when applied to a
+ path helper.
+ MSG
+
+ PATH.call(options)
+ else
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ You are calling a `*_path` helper with the `only_path` option
+ explicitly set to `false`. This option will stop working on
+ path helpers in Rails 5. Use the corresponding `*_url` helper
+ instead.
+ MSG
+
+ FULL.call(options)
+ end
+ else
+ PATH.call(options)
end
- end
+ }
+ # :startdoc:
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
@@ -278,7 +366,7 @@ module ActionDispatch
def initialize(request_class = ActionDispatch::Request)
self.named_routes = NamedRouteCollection.new
- self.resources_path_names = self.class.default_resources_path_names.dup
+ self.resources_path_names = self.class.default_resources_path_names
self.default_url_options = {}
self.request_class = request_class
@@ -319,6 +407,7 @@ module ActionDispatch
mapper.instance_exec(&block)
end
end
+ private :eval_block
def finalize!
return if @finalized
@@ -334,6 +423,10 @@ module ActionDispatch
@prepend.each { |blk| eval_block(blk) }
end
+ def dispatcher(defaults)
+ Routing::RouteSet::Dispatcher.new(defaults)
+ end
+
module MountedHelpers #:nodoc:
extend ActiveSupport::Concern
include UrlFor
@@ -364,42 +457,51 @@ module ActionDispatch
RUBY
end
- def url_helpers
- @url_helpers ||= begin
- routes = self
-
- Module.new do
- extend ActiveSupport::Concern
- include UrlFor
-
- # Define url_for in the singleton level so one can do:
- # Rails.application.routes.url_helpers.url_for(args)
- @_routes = routes
- class << self
- delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
- attr_reader :_routes
- def url_options; {}; end
- end
+ def url_helpers(include_path_helpers = true)
+ routes = self
+
+ Module.new do
+ extend ActiveSupport::Concern
+ include UrlFor
+
+ # Define url_for in the singleton level so one can do:
+ # Rails.application.routes.url_helpers.url_for(args)
+ @_routes = routes
+ class << self
+ delegate :url_for, :optimize_routes_generation?, to: '@_routes'
+ attr_reader :_routes
+ def url_options; {}; end
+ end
- # Make named_routes available in the module singleton
- # as well, so one can do:
- # Rails.application.routes.url_helpers.posts_path
- extend routes.named_routes.module
+ url_helpers = routes.named_routes.url_helpers_module
- # Any class that includes this module will get all
- # named routes...
- include routes.named_routes.module
+ # Make named_routes available in the module singleton
+ # as well, so one can do:
+ # Rails.application.routes.url_helpers.posts_path
+ extend url_helpers
- # plus a singleton class method called _routes ...
- included do
- singleton_class.send(:redefine_method, :_routes) { routes }
- end
+ # Any class that includes this module will get all
+ # named routes...
+ include url_helpers
+
+ if include_path_helpers
+ path_helpers = routes.named_routes.path_helpers_module
+ else
+ path_helpers = routes.named_routes.path_helpers_module(true)
+ end
+
+ include path_helpers
+ extend path_helpers
- # And an instance method _routes. Note that
- # UrlFor (included in this module) add extra
- # conveniences for working with @_routes.
- define_method(:_routes) { @_routes || routes }
+ # plus a singleton class method called _routes ...
+ included do
+ singleton_class.send(:redefine_method, :_routes) { routes }
end
+
+ # And an instance method _routes. Note that
+ # UrlFor (included in this module) add extra
+ # conveniences for working with @_routes.
+ define_method(:_routes) { @_routes || routes }
end
end
@@ -491,8 +593,8 @@ module ActionDispatch
attr_reader :options, :recall, :set, :named_route
- def initialize(options, recall, set)
- @named_route = options.delete(:use_route)
+ def initialize(named_route, options, recall, set)
+ @named_route = named_route
@options = options.dup
@recall = recall.dup
@set = set
@@ -608,32 +710,34 @@ module ActionDispatch
end
def generate_extras(options, recall={})
- path, params = generate(options, recall)
+ route_key = options.delete :use_route
+ path, params = generate(route_key, options, recall)
return path, params.keys
end
- def generate(options, recall = {})
- Generator.new(options, recall, self).generate
+ def generate(route_key, options, recall = {})
+ Generator.new(route_key, options, recall, self).generate
end
+ private :generate
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
:trailing_slash, :anchor, :params, :only_path, :script_name,
:original_script_name]
- def mounted?
- false
- end
-
def optimize_routes_generation?
- !mounted? && default_url_options.empty?
+ default_url_options.empty?
end
def find_script_name(options)
- options.delete :script_name
+ options.delete(:script_name) { '' }
+ end
+
+ def path_for(options, route_name = nil) # :nodoc:
+ url_for(options, route_name, PATH)
end
# The +options+ argument must be a hash whose keys are *symbols*.
- def url_for(options)
+ def url_for(options, route_name = nil, url_strategy = UNKNOWN)
options = default_url_options.merge options
user = password = nil
@@ -648,14 +752,14 @@ module ActionDispatch
original_script_name = options.delete(:original_script_name)
script_name = find_script_name options
- if script_name && original_script_name
+ if original_script_name
script_name = original_script_name + script_name
end
path_options = options.dup
RESERVED_OPTIONS.each { |ro| path_options.delete ro }
- path, params = generate(path_options, recall)
+ path, params = generate(route_name, path_options, recall)
if options.key? :params
params.merge! options[:params]
@@ -667,7 +771,7 @@ module ActionDispatch
options[:user] = user
options[:password] = password
- ActionDispatch::Http::URL.url_for(options)
+ url_strategy.call options
end
def call(env)
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index e624fe3c4a..eb554ec383 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -152,7 +152,9 @@ module ActionDispatch
when nil
_routes.url_for(url_options.symbolize_keys)
when Hash
- _routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
+ route_name = options.delete :use_route
+ _routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
+ route_name)
when String
options
when Symbol
@@ -169,8 +171,7 @@ module ActionDispatch
protected
def optimize_routes_generation?
- return @_optimized_routes if defined?(@_optimized_routes)
- @_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty?
+ _routes.optimize_routes_generation? && default_url_options.empty?
end
def _with_routes(routes)
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index 226baf9ad0..41d00b5e2b 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -1,18 +1,22 @@
+require 'rails-dom-testing'
+
module ActionDispatch
module Assertions
- autoload :DomAssertions, 'action_dispatch/testing/assertions/dom'
autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response'
autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing'
- autoload :SelectorAssertions, 'action_dispatch/testing/assertions/selector'
- autoload :TagAssertions, 'action_dispatch/testing/assertions/tag'
extend ActiveSupport::Concern
- include DomAssertions
include ResponseAssertions
include RoutingAssertions
- include SelectorAssertions
- include TagAssertions
+ include Rails::Dom::Testing::Assertions
+
+ def html_document
+ @html_document ||= if @response.content_type =~ /xml$/
+ Nokogiri::XML::Document.parse(@response.body)
+ else
+ Nokogiri::HTML::DocumentFragment.parse(@response.body)
+ end
+ end
end
end
-
diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
index 241a39393a..fb579b52fe 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
@@ -1,27 +1,3 @@
-require 'action_view/vendor/html-scanner'
+require 'active_support/deprecation'
-module ActionDispatch
- module Assertions
- module DomAssertions
- # \Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
- #
- # # assert that the referenced method generates the appropriate HTML string
- # assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
- def assert_dom_equal(expected, actual, message = nil)
- expected_dom = HTML::Document.new(expected).root
- actual_dom = HTML::Document.new(actual).root
- assert_equal expected_dom, actual_dom, message
- end
-
- # The negated form of +assert_dom_equivalent+.
- #
- # # assert that the referenced method does not generate the specified HTML string
- # assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
- def assert_dom_not_equal(expected, actual, message = nil)
- expected_dom = HTML::Document.new(expected).root
- actual_dom = HTML::Document.new(actual).root
- assert_not_equal expected_dom, actual_dom, message
- end
- end
- end
-end
+ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::DomAssertions has been extracted to the rails-dom-testing gem.") \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 0adc6c84ff..13a72220b3 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -73,13 +73,8 @@ module ActionDispatch
if Regexp === fragment
fragment
else
- handle = @controller || Class.new(ActionController::Metal) do
- include ActionController::Redirecting
- def initialize(request)
- @_request = request
- end
- end.new(@request)
- handle._compute_redirect_to_location(fragment)
+ handle = @controller || ActionController::Redirecting
+ handle._compute_redirect_to_location(@request, fragment)
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index f1f998d932..e06f7037c6 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -38,7 +38,7 @@ module ActionDispatch
# # Test a custom route
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
def assert_recognizes(expected_options, path, extras={}, msg=nil)
- request = recognized_request_for(path, extras)
+ request = recognized_request_for(path, extras, msg)
expected_options = expected_options.clone
@@ -69,9 +69,9 @@ module ActionDispatch
#
# # Asserts that the generated route gives us our custom route
# assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
- def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
+ def assert_generates(expected_path, options, defaults={}, extras={}, message=nil)
if expected_path =~ %r{://}
- fail_on(URI::InvalidURIError) do
+ fail_on(URI::InvalidURIError, message) do
uri = URI.parse(expected_path)
expected_path = uri.path.to_s.empty? ? "/" : uri.path
end
@@ -165,7 +165,7 @@ module ActionDispatch
# ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, *args, &block)
- if defined?(@controller) && @controller && @routes && @routes.named_routes.helpers.include?(selector)
+ if defined?(@controller) && @controller && @routes && @routes.named_routes.route_defined?(selector)
@controller.send(selector, *args, &block)
else
super
@@ -174,7 +174,7 @@ module ActionDispatch
private
# Recognizes the route for a given path.
- def recognized_request_for(path, extras = {})
+ def recognized_request_for(path, extras = {}, msg)
if path.is_a?(Hash)
method = path[:method]
path = path[:path]
@@ -186,7 +186,7 @@ module ActionDispatch
request = ActionController::TestRequest.new
if path =~ %r{://}
- fail_on(URI::InvalidURIError) do
+ fail_on(URI::InvalidURIError, msg) do
uri = URI.parse(path)
request.env["rack.url_scheme"] = uri.scheme || "http"
request.host = uri.host if uri.host
@@ -200,7 +200,7 @@ module ActionDispatch
request.request_method = method if method
- params = fail_on(ActionController::RoutingError) do
+ params = fail_on(ActionController::RoutingError, msg) do
@routes.recognize_path(path, { :method => method, :extras => extras })
end
request.path_parameters = params.with_indifferent_access
@@ -208,10 +208,10 @@ module ActionDispatch
request
end
- def fail_on(exception_class)
+ def fail_on(exception_class, message)
yield
rescue exception_class => e
- raise Minitest::Assertion, e.message
+ raise Minitest::Assertion, message || e.message
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 12023e6f77..7361e6c44b 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -1,430 +1,3 @@
-require 'action_view/vendor/html-scanner'
-require 'active_support/core_ext/object/inclusion'
+require 'active_support/deprecation'
-#--
-# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
-# Under MIT and/or CC By license.
-#++
-
-module ActionDispatch
- module Assertions
- NO_STRIP = %w{pre script style textarea}
-
- # Adds the +assert_select+ method for use in Rails functional
- # test cases, which can be used to make assertions on the response HTML of a controller
- # action. You can also call +assert_select+ within another +assert_select+ to
- # make assertions on elements selected by the enclosing assertion.
- #
- # Use +css_select+ to select elements without making an assertions, either
- # from the response HTML or elements selected by the enclosing assertion.
- #
- # In addition to HTML responses, you can make the following assertions:
- #
- # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
- # * +assert_select_email+ - Assertions on the HTML body of an e-mail.
- #
- # Also see HTML::Selector to learn how to use selectors.
- module SelectorAssertions
- # Select and return all matching elements.
- #
- # If called with a single argument, uses that argument as a selector
- # to match all elements of the current page. Returns an empty array
- # if no match is found.
- #
- # If called with two arguments, uses the first argument as the base
- # element and the second argument as the selector. Attempts to match the
- # base element and any of its children. Returns an empty array if no
- # match is found.
- #
- # The selector may be a CSS selector expression (String), an expression
- # with substitution values (Array) or an HTML::Selector object.
- #
- # # Selects all div tags
- # divs = css_select("div")
- #
- # # Selects all paragraph tags and does something interesting
- # pars = css_select("p")
- # pars.each do |par|
- # # Do something fun with paragraphs here...
- # end
- #
- # # Selects all list items in unordered lists
- # items = css_select("ul>li")
- #
- # # Selects all form tags and then all inputs inside the form
- # forms = css_select("form")
- # forms.each do |form|
- # inputs = css_select(form, "input")
- # ...
- # end
- def css_select(*args)
- # See assert_select to understand what's going on here.
- arg = args.shift
-
- if arg.is_a?(HTML::Node)
- root = arg
- arg = args.shift
- elsif arg == nil
- raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
- elsif defined?(@selected) && @selected
- matches = []
-
- @selected.each do |selected|
- subset = css_select(selected, HTML::Selector.new(arg.dup, args.dup))
- subset.each do |match|
- matches << match unless matches.any? { |m| m.equal?(match) }
- end
- end
-
- return matches
- else
- root = response_from_page
- end
-
- case arg
- when String
- selector = HTML::Selector.new(arg, args)
- when Array
- selector = HTML::Selector.new(*arg)
- when HTML::Selector
- selector = arg
- else raise ArgumentError, "Expecting a selector as the first argument"
- end
-
- selector.select(root)
- end
-
- # An assertion that selects elements and makes one or more equality tests.
- #
- # If the first argument is an element, selects all matching elements
- # starting from (and including) that element and all its children in
- # depth-first order.
- #
- # If no element if specified, calling +assert_select+ selects from the
- # response HTML unless +assert_select+ is called from within an +assert_select+ block.
- #
- # When called with a block +assert_select+ passes an array of selected elements
- # to the block. Calling +assert_select+ from the block, with no element specified,
- # runs the assertion on the complete set of elements selected by the enclosing assertion.
- # Alternatively the array may be iterated through so that +assert_select+ can be called
- # separately for each element.
- #
- #
- # ==== Example
- # If the response contains two ordered lists, each with four list elements then:
- # assert_select "ol" do |elements|
- # elements.each do |element|
- # assert_select element, "li", 4
- # end
- # end
- #
- # will pass, as will:
- # assert_select "ol" do
- # assert_select "li", 8
- # end
- #
- # The selector may be a CSS selector expression (String), an expression
- # with substitution values, or an HTML::Selector object.
- #
- # === Equality Tests
- #
- # The equality test may be one of the following:
- # * <tt>true</tt> - Assertion is true if at least one element selected.
- # * <tt>false</tt> - Assertion is true if no element selected.
- # * <tt>String/Regexp</tt> - Assertion is true if the text value of at least
- # one element matches the string or regular expression.
- # * <tt>Integer</tt> - Assertion is true if exactly that number of
- # elements are selected.
- # * <tt>Range</tt> - Assertion is true if the number of selected
- # elements fit the range.
- # If no equality test specified, the assertion is true if at least one
- # element selected.
- #
- # To perform more than one equality tests, use a hash with the following keys:
- # * <tt>:text</tt> - Narrow the selection to elements that have this text
- # value (string or regexp).
- # * <tt>:html</tt> - Narrow the selection to elements that have this HTML
- # content (string or regexp).
- # * <tt>:count</tt> - Assertion is true if the number of selected elements
- # is equal to this value.
- # * <tt>:minimum</tt> - Assertion is true if the number of selected
- # elements is at least this value.
- # * <tt>:maximum</tt> - Assertion is true if the number of selected
- # elements is at most this value.
- #
- # If the method is called with a block, once all equality tests are
- # evaluated the block is called with an array of all matched elements.
- #
- # # At least one form element
- # assert_select "form"
- #
- # # Form element includes four input fields
- # assert_select "form input", 4
- #
- # # Page title is "Welcome"
- # assert_select "title", "Welcome"
- #
- # # Page title is "Welcome" and there is only one title element
- # assert_select "title", {count: 1, text: "Welcome"},
- # "Wrong title or more than one title element"
- #
- # # Page contains no forms
- # assert_select "form", false, "This page must contain no forms"
- #
- # # Test the content and style
- # assert_select "body div.header ul.menu"
- #
- # # Use substitution values
- # assert_select "ol>li#?", /item-\d+/
- #
- # # All input fields in the form have a name
- # assert_select "form input" do
- # assert_select "[name=?]", /.+/ # Not empty
- # end
- def assert_select(*args, &block)
- # Start with optional element followed by mandatory selector.
- arg = args.shift
- @selected ||= nil
-
- if arg.is_a?(HTML::Node)
- # First argument is a node (tag or text, but also HTML root),
- # so we know what we're selecting from.
- root = arg
- arg = args.shift
- elsif arg == nil
- # This usually happens when passing a node/element that
- # happens to be nil.
- raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
- elsif @selected
- root = HTML::Node.new(nil)
- root.children.concat @selected
- else
- # Otherwise just operate on the response document.
- root = response_from_page
- end
-
- # First or second argument is the selector: string and we pass
- # all remaining arguments. Array and we pass the argument. Also
- # accepts selector itself.
- case arg
- when String
- selector = HTML::Selector.new(arg, args)
- when Array
- selector = HTML::Selector.new(*arg)
- when HTML::Selector
- selector = arg
- else raise ArgumentError, "Expecting a selector as the first argument"
- end
-
- # Next argument is used for equality tests.
- equals = {}
- case arg = args.shift
- when Hash
- equals = arg
- when String, Regexp
- equals[:text] = arg
- when Integer
- equals[:count] = arg
- when Range
- equals[:minimum] = arg.begin
- equals[:maximum] = arg.end
- when FalseClass
- equals[:count] = 0
- when NilClass, TrueClass
- equals[:minimum] = 1
- else raise ArgumentError, "I don't understand what you're trying to match"
- end
-
- # By default we're looking for at least one match.
- if equals[:count]
- equals[:minimum] = equals[:maximum] = equals[:count]
- else
- equals[:minimum] = 1 unless equals[:minimum]
- end
-
- # Last argument is the message we use if the assertion fails.
- message = args.shift
- #- message = "No match made with selector #{selector.inspect}" unless message
- if args.shift
- raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
- end
-
- matches = selector.select(root)
- # If text/html, narrow down to those elements that match it.
- content_mismatch = nil
- if match_with = equals[:text]
- matches.delete_if do |match|
- text = ""
- stack = match.children.reverse
- while node = stack.pop
- if node.tag?
- stack.concat node.children.reverse
- else
- content = node.content
- text << content
- end
- end
- text.strip! unless NO_STRIP.include?(match.name)
- text.sub!(/\A\n/, '') if match.name == "textarea"
- unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
- content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, text)
- true
- end
- end
- elsif match_with = equals[:html]
- matches.delete_if do |match|
- html = match.children.map(&:to_s).join
- html.strip! unless NO_STRIP.include?(match.name)
- unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
- content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, html)
- true
- end
- end
- end
- # Expecting foo found bar element only if found zero, not if
- # found one but expecting two.
- message ||= content_mismatch if matches.empty?
- # Test minimum/maximum occurrence.
- min, max, count = equals[:minimum], equals[:maximum], equals[:count]
-
- # FIXME: minitest provides messaging when we use assert_operator,
- # so is this custom message really needed?
- message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size})
- if count
- assert_equal count, matches.size, message
- else
- assert_operator matches.size, :>=, min, message if min
- assert_operator matches.size, :<=, max, message if max
- end
-
- # If a block is given call that block. Set @selected to allow
- # nested assert_select, which can be nested several levels deep.
- if block_given? && !matches.empty?
- begin
- in_scope, @selected = @selected, matches
- yield matches
- ensure
- @selected = in_scope
- end
- end
-
- # Returns all matches elements.
- matches
- end
-
- def count_description(min, max, count) #:nodoc:
- pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')}
-
- if min && max && (max != min)
- "between #{min} and #{max} elements"
- elsif min && max && max == min && count
- "exactly #{count} #{pluralize['element', min]}"
- elsif min && !(min == 1 && max == 1)
- "at least #{min} #{pluralize['element', min]}"
- elsif max
- "at most #{max} #{pluralize['element', max]}"
- end
- end
-
- # Extracts the content of an element, treats it as encoded HTML and runs
- # nested assertion on it.
- #
- # You typically call this method within another assertion to operate on
- # all currently selected elements. You can also pass an element or array
- # of elements.
- #
- # The content of each element is un-encoded, and wrapped in the root
- # element +encoded+. It then calls the block with all un-encoded elements.
- #
- # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
- # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
- # # Select each entry item and then the title item
- # assert_select "entry>title" do
- # # Run assertions on the encoded title elements
- # assert_select_encoded do
- # assert_select "b"
- # end
- # end
- # end
- #
- #
- # # Selects all paragraph tags from within the description of an RSS feed
- # assert_select "rss[version=2.0]" do
- # # Select description element of each feed item.
- # assert_select "channel>item>description" do
- # # Run assertions on the encoded elements.
- # assert_select_encoded do
- # assert_select "p"
- # end
- # end
- # end
- def assert_select_encoded(element = nil, &block)
- case element
- when Array
- elements = element
- when HTML::Node
- elements = [element]
- when nil
- unless elements = @selected
- raise ArgumentError, "First argument is optional, but must be called from a nested assert_select"
- end
- else
- raise ArgumentError, "Argument is optional, and may be node or array of nodes"
- end
-
- fix_content = lambda do |node|
- # Gets around a bug in the Rails 1.1 HTML parser.
- node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) }
- end
-
- selected = elements.map do |elem|
- text = elem.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
- root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root
- css_select(root, "encoded:root", &block)[0]
- end
-
- begin
- old_selected, @selected = @selected, selected
- assert_select ":root", &block
- ensure
- @selected = old_selected
- end
- end
-
- # Extracts the body of an email and runs nested assertions on it.
- #
- # You must enable deliveries for this assertion to work, use:
- # ActionMailer::Base.perform_deliveries = true
- #
- # assert_select_email do
- # assert_select "h1", "Email alert"
- # end
- #
- # assert_select_email do
- # items = assert_select "ol>li"
- # items.each do
- # # Work with items here...
- # end
- # end
- def assert_select_email(&block)
- deliveries = ActionMailer::Base.deliveries
- assert !deliveries.empty?, "No e-mail in delivery list"
-
- deliveries.each do |delivery|
- (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
- if part["Content-Type"].to_s =~ /^text\/html\W/
- root = HTML::Document.new(part.body.to_s).root
- assert_select root, ":root", &block
- end
- end
- end
- end
-
- protected
- # +assert_select+ and +css_select+ call this to obtain the content in the HTML page.
- def response_from_page
- html_document.root
- end
- end
- end
-end
+ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::SelectorAssertions has been extracted to the rails-dom-testing gem.")
diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
index e5fe30ba82..da98b1d6ce 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
@@ -1,135 +1,3 @@
-require 'action_view/vendor/html-scanner'
+require 'active_support/deprecation'
-module ActionDispatch
- module Assertions
- # Pair of assertions to testing elements in the HTML output of the response.
- module TagAssertions
- # Asserts that there is a tag/node/element in the body of the response
- # that meets all of the given conditions. The +conditions+ parameter must
- # be a hash of any of the following keys (all are optional):
- #
- # * <tt>:tag</tt>: the node type must match the corresponding value
- # * <tt>:attributes</tt>: a hash. The node's attributes must match the
- # corresponding values in the hash.
- # * <tt>:parent</tt>: a hash. The node's parent must match the
- # corresponding hash.
- # * <tt>:child</tt>: a hash. At least one of the node's immediate children
- # must meet the criteria described by the hash.
- # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
- # meet the criteria described by the hash.
- # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
- # must meet the criteria described by the hash.
- # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
- # meet the criteria described by the hash.
- # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
- # the criteria described by the hash, and at least one sibling must match.
- # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
- # the criteria described by the hash, and at least one sibling must match.
- # * <tt>:children</tt>: a hash, for counting children of a node. Accepts
- # the keys:
- # * <tt>:count</tt>: either a number or a range which must equal (or
- # include) the number of children that match.
- # * <tt>:less_than</tt>: the number of matching children must be less
- # than this number.
- # * <tt>:greater_than</tt>: the number of matching children must be
- # greater than this number.
- # * <tt>:only</tt>: another hash consisting of the keys to use
- # to match on the children, and only matching children will be
- # counted.
- # * <tt>:content</tt>: the textual content of the node must match the
- # given value. This will not match HTML tags in the body of a
- # tag--only text.
- #
- # Conditions are matched using the following algorithm:
- #
- # * if the condition is a string, it must be a substring of the value.
- # * if the condition is a regexp, it must match the value.
- # * if the condition is a number, the value must match number.to_s.
- # * if the condition is +true+, the value must not be +nil+.
- # * if the condition is +false+ or +nil+, the value must be +nil+.
- #
- # # Assert that there is a "span" tag
- # assert_tag tag: "span"
- #
- # # Assert that there is a "span" tag with id="x"
- # assert_tag tag: "span", attributes: { id: "x" }
- #
- # # Assert that there is a "span" tag using the short-hand
- # assert_tag :span
- #
- # # Assert that there is a "span" tag with id="x" using the short-hand
- # assert_tag :span, attributes: { id: "x" }
- #
- # # Assert that there is a "span" inside of a "div"
- # assert_tag tag: "span", parent: { tag: "div" }
- #
- # # Assert that there is a "span" somewhere inside a table
- # assert_tag tag: "span", ancestor: { tag: "table" }
- #
- # # Assert that there is a "span" with at least one "em" child
- # assert_tag tag: "span", child: { tag: "em" }
- #
- # # Assert that there is a "span" containing a (possibly nested)
- # # "strong" tag.
- # assert_tag tag: "span", descendant: { tag: "strong" }
- #
- # # Assert that there is a "span" containing between 2 and 4 "em" tags
- # # as immediate children
- # assert_tag tag: "span",
- # children: { count: 2..4, only: { tag: "em" } }
- #
- # # Get funky: assert that there is a "div", with an "ul" ancestor
- # # and an "li" parent (with "class" = "enum"), and containing a
- # # "span" descendant that contains text matching /hello world/
- # assert_tag tag: "div",
- # ancestor: { tag: "ul" },
- # parent: { tag: "li",
- # attributes: { class: "enum" } },
- # descendant: { tag: "span",
- # child: /hello world/ }
- #
- # <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
- # with well-formed XHTML. They recognize a few tags as implicitly self-closing
- # (like br and hr and such) but will not work correctly with tags
- # that allow optional closing tags (p, li, td). <em>You must explicitly
- # close all of your tags to use these assertions.</em>
- def assert_tag(*opts)
- opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
- tag = find_tag(opts)
- assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
- end
-
- # Identical to +assert_tag+, but asserts that a matching tag does _not_
- # exist. (See +assert_tag+ for a full discussion of the syntax.)
- #
- # # Assert that there is not a "div" containing a "p"
- # assert_no_tag tag: "div", descendant: { tag: "p" }
- #
- # # Assert that an unordered list is empty
- # assert_no_tag tag: "ul", descendant: { tag: "li" }
- #
- # # Assert that there is not a "p" tag with between 1 to 3 "img" tags
- # # as immediate children
- # assert_no_tag tag: "p",
- # children: { count: 1..3, only: { tag: "img" } }
- def assert_no_tag(*opts)
- opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
- tag = find_tag(opts)
- assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
- end
-
- def find_tag(conditions)
- html_document.find(conditions)
- end
-
- def find_all_tag(conditions)
- html_document.find_all(conditions)
- end
-
- def html_document
- xml = @response.content_type =~ /xml$/
- @html_document ||= HTML::Document.new(@response.body, false, xml)
- end
- end
- end
-end
+ActiveSupport::Deprecation.warn('`ActionDispatch::Assertions::TagAssertions` has been extracted to the rails-dom-testing gem.')
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 17765d851b..c300a4ea0d 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -3,6 +3,7 @@ require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/try'
require 'rack/test'
+require 'minitest'
module ActionDispatch
module Integration #:nodoc:
@@ -188,8 +189,8 @@ module ActionDispatch
# This makes app.url_for and app.foo_path available in the console
if app.respond_to?(:routes)
singleton_class.class_eval do
- include app.routes.url_helpers if app.routes.respond_to?(:url_helpers)
- include app.routes.mounted_helpers if app.routes.respond_to?(:mounted_helpers)
+ include app.routes.url_helpers
+ include app.routes.mounted_helpers
end
end
@@ -200,7 +201,7 @@ module ActionDispatch
@url_options ||= default_url_options.dup.tap do |url_options|
url_options.reverse_merge!(controller.url_options) if controller
- if @app.respond_to?(:routes) && @app.routes.respond_to?(:default_url_options)
+ if @app.respond_to?(:routes)
url_options.reverse_merge!(@app.routes.default_url_options)
end
@@ -329,6 +330,7 @@ module ActionDispatch
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
+ reset_template_assertion
# reset the html_document variable, but only for new get/post calls
@html_document = nil unless method == 'cookies' || method == 'assigns'
integration_session.__send__(method, *args).tap do
@@ -493,5 +495,9 @@ module ActionDispatch
reset! unless integration_session
integration_session.url_options
end
+
+ def document_root_element
+ html_document
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 57c678843b..de3dc5f924 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -39,7 +39,7 @@ module ActionDispatch
end
def action=(action_name)
- path_parameters["action"] = action_name.to_s
+ path_parameters[:action] = action_name.to_s
end
def if_modified_since=(last_modified)