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/cache.rb42
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb31
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb35
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/closed_error.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb82
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb78
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb30
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb52
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/rescue.rb26
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb177
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb21
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb18
-rw-r--r--actionpack/lib/action_dispatch/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb130
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb10
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb121
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb9
31 files changed, 519 insertions, 433 deletions
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index aaed0d750f..bea62b94d2 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -4,14 +4,18 @@ module ActionDispatch
module Http
module Cache
module Request
+
+ HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
+ HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
+
def if_modified_since
- if since = env['HTTP_IF_MODIFIED_SINCE']
+ if since = env[HTTP_IF_MODIFIED_SINCE]
Time.rfc2822(since) rescue nil
end
end
def if_none_match
- env['HTTP_IF_NONE_MATCH']
+ env[HTTP_IF_NONE_MATCH]
end
def not_modified?(modified_at)
@@ -43,31 +47,35 @@ module ActionDispatch
alias :etag? :etag
def last_modified
- if last = headers['Last-Modified']
+ if last = headers[LAST_MODIFIED]
Time.httpdate(last)
end
end
def last_modified?
- headers.include?('Last-Modified')
+ headers.include?(LAST_MODIFIED)
end
def last_modified=(utc_time)
- headers['Last-Modified'] = utc_time.httpdate
+ headers[LAST_MODIFIED] = utc_time.httpdate
end
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
- @etag = self["ETag"] = %("#{Digest::MD5.hexdigest(key)}")
+ @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
end
private
+ LAST_MODIFIED = "Last-Modified".freeze
+ ETAG = "ETag".freeze
+ CACHE_CONTROL = "Cache-Control".freeze
+
def prepare_cache_control!
@cache_control = {}
- @etag = self["ETag"]
+ @etag = self[ETAG]
- if cache_control = self["Cache-Control"]
+ if cache_control = self[CACHE_CONTROL]
cache_control.split(/,\s*/).each do |segment|
first, last = segment.split("=")
@cache_control[first.to_sym] = last || true
@@ -81,28 +89,32 @@ module ActionDispatch
end
end
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
+ NO_CACHE = "no-cache".freeze
+ PUBLIC = "public".freeze
+ PRIVATE = "private".freeze
+ MUST_REVALIDATE = "must-revalidate".freeze
def set_conditional_cache_control!
- return if self["Cache-Control"].present?
+ return if self[CACHE_CONTROL].present?
control = @cache_control
if control.empty?
- headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
+ headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
- headers["Cache-Control"] = "no-cache"
+ headers[CACHE_CONTROL] = NO_CACHE
else
extras = control[:extras]
max_age = control[:max_age]
options = []
options << "max-age=#{max_age.to_i}" if max_age
- options << (control[:public] ? "public" : "private")
- options << "must-revalidate" if control[:must_revalidate]
+ options << (control[:public] ? PUBLIC : PRIVATE)
+ options << MUST_REVALIDATE if control[:must_revalidate]
options.concat(extras) if extras
- headers["Cache-Control"] = options.join(", ")
+ headers[CACHE_CONTROL] = options.join(", ")
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 8a9f9c4315..25affb9f50 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -103,7 +103,7 @@ module Mime
SET << Mime.const_get(symbol.to_s.upcase)
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
- ([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = SET.last }
+ ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
end
def parse(accept_header)
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 1480e8f77c..490b46c990 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -20,6 +20,8 @@ module ActionDispatch
@filters.present?
end
+ FILTERED = '[FILTERED]'.freeze
+
def compiled_filter
@compiled_filter ||= begin
regexps, blocks = compile_filter
@@ -29,7 +31,7 @@ module ActionDispatch
original_params.each do |key, value|
if regexps.find { |r| key =~ r }
- value = '[FILTERED]'
+ value = FILTERED
elsif value.is_a?(Hash)
value = filter(value)
elsif value.is_a?(Array)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index ef5d207b26..d9b63faf5e 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -41,8 +41,6 @@ module ActionDispatch
# you'll get a weird error down the road, but our form handling
# should really prevent that from happening
def encode_params(params)
- return params unless "ruby".encoding_aware?
-
if params.is_a?(String)
return params.force_encoding("UTF-8").encode!
elsif !params.is_a?(Hash)
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index b10f6b48c7..0a0ebe7fad 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -35,14 +35,6 @@ module ActionDispatch
METHOD
end
- def self.new(env)
- if request = env["action_dispatch.request"] && request.instance_of?(self)
- return request
- end
-
- super
- end
-
def key?(key)
@env.key?(key)
end
@@ -94,31 +86,31 @@ module ActionDispatch
end
# Is this a GET (or HEAD) request?
- # Equivalent to <tt>request.request_method == :get</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :get</tt>.
def get?
HTTP_METHOD_LOOKUP[request_method] == :get
end
# Is this a POST request?
- # Equivalent to <tt>request.request_method == :post</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :post</tt>.
def post?
HTTP_METHOD_LOOKUP[request_method] == :post
end
# Is this a PUT request?
- # Equivalent to <tt>request.request_method == :put</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?
HTTP_METHOD_LOOKUP[request_method] == :put
end
# Is this a DELETE request?
- # Equivalent to <tt>request.request_method == :delete</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :delete</tt>.
def delete?
HTTP_METHOD_LOOKUP[request_method] == :delete
end
# Is this a HEAD request?
- # Equivalent to <tt>request.method == :head</tt>.
+ # Equivalent to <tt>request.method_symbol == :head</tt>.
def head?
HTTP_METHOD_LOOKUP[method] == :head
end
@@ -130,10 +122,18 @@ module ActionDispatch
Http::Headers.new(@env)
end
+ def original_fullpath
+ @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
+ end
+
def fullpath
@fullpath ||= super
end
+ def original_url
+ base_url + original_fullpath
+ end
+
def media_type
content_mime_type.to_s
end
@@ -157,8 +157,7 @@ module ActionDispatch
# Originating IP address, usually set by the RemoteIp middleware.
def remote_ip
- # Coerce the remote_ip object into a string, because to_s could return nil
- @remote_ip ||= @env["action_dispatch.remote_ip"].to_s || ip
+ @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
@@ -190,7 +189,7 @@ module ActionDispatch
# variable is already set, wrap it in a StringIO.
def body
if raw_post = @env['RAW_POST_DATA']
- raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
+ raw_post.force_encoding(Encoding::BINARY)
StringIO.new(raw_post)
else
@env['rack.input']
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index f1e85559a3..84732085f0 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -53,8 +53,10 @@ module ActionDispatch # :nodoc:
# information.
attr_accessor :charset, :content_type
- CONTENT_TYPE = "Content-Type"
-
+ CONTENT_TYPE = "Content-Type".freeze
+ SET_COOKIE = "Set-Cookie".freeze
+ LOCATION = "Location".freeze
+
cattr_accessor(:default_charset) { "utf-8" }
include Rack::Response::Helpers
@@ -66,10 +68,10 @@ module ActionDispatch # :nodoc:
@sending_file = false
@blank = false
- if content_type = self["Content-Type"]
+ if content_type = self[CONTENT_TYPE]
type, charset = content_type.split(/;\s*charset=/)
@content_type = Mime::Type.lookup(type)
- @charset = charset || "UTF-8"
+ @charset = charset || self.class.default_charset
end
prepare_cache_control!
@@ -109,9 +111,9 @@ module ActionDispatch # :nodoc:
end
def body
- str = ''
- each { |part| str << part.to_s }
- str
+ strings = []
+ each { |part| strings << part.to_s }
+ strings.join
end
EMPTY = " "
@@ -119,14 +121,7 @@ module ActionDispatch # :nodoc:
def body=(body)
@blank = true if body == EMPTY
- # Explicitly check for strings. This is *wrong* theoretically
- # but if we don't check this, the performance on string bodies
- # is bad on Ruby 1.8 (because strings responds to each then).
- @body = if body.respond_to?(:to_str) || !body.respond_to?(:each)
- [body]
- else
- body
- end
+ @body = body.respond_to?(:each) ? body : [body]
end
def body_parts
@@ -142,12 +137,12 @@ module ActionDispatch # :nodoc:
end
def location
- headers['Location']
+ headers[LOCATION]
end
alias_method :redirect_url, :location
def location=(url)
- headers['Location'] = url
+ headers[LOCATION] = url
end
def close
@@ -158,10 +153,10 @@ module ActionDispatch # :nodoc:
assign_default_content_type_and_charset!
handle_conditional_get!
- @header["Set-Cookie"] = @header["Set-Cookie"].join("\n") if @header["Set-Cookie"].respond_to?(:join)
+ @header[SET_COOKIE] = @header[SET_COOKIE].join("\n") if @header[SET_COOKIE].respond_to?(:join)
if [204, 304].include?(@status)
- @header.delete "Content-Type"
+ @header.delete CONTENT_TYPE
[@status, @header, []]
else
[@status, @header, self]
@@ -175,7 +170,7 @@ module ActionDispatch # :nodoc:
# assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies
cookies = {}
- if header = self["Set-Cookie"]
+ if header = self[SET_COOKIE]
header = header.split("\n") if header.respond_to?(:to_str)
header.each do |cookie|
if pair = cookie.split(';').first
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 94fa747a79..5ab99d1061 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -22,8 +22,8 @@ module ActionDispatch
private
def encode_filename(filename)
- # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8
- if "ruby".encoding_aware? && filename
+ # Encode the filename in the utf8 encoding, unless it is nil
+ if filename
filename.force_encoding("UTF-8").encode!
else
filename
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 129a8b1031..64459836b5 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -70,7 +70,7 @@ module ActionDispatch
host = ""
unless options[:subdomain] == false
- host << (options[:subdomain] || extract_subdomain(options[:host], tld_length))
+ host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param
host << "."
end
host << (options[:domain] || extract_domain(options[:host], tld_length))
diff --git a/actionpack/lib/action_dispatch/middleware/closed_error.rb b/actionpack/lib/action_dispatch/middleware/closed_error.rb
deleted file mode 100644
index 0a4db47f4b..0000000000
--- a/actionpack/lib/action_dispatch/middleware/closed_error.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module ActionDispatch
- class ClosedError < StandardError #:nodoc:
- def initialize(kind)
- super "Cannot modify #{kind} because it was closed. This means it was already streamed back to the client or converted to HTTP headers."
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index a4ffd40a66..39ff58a447 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -121,10 +121,6 @@ module ActionDispatch
@cookies = {}
end
- attr_reader :closed
- alias :closed? :closed
- def close!; @closed = true end
-
def each(&block)
@cookies.each(&block)
end
@@ -165,7 +161,6 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
def []=(key, options)
- raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -243,10 +238,13 @@ module ActionDispatch
@delete_cookies.clear
end
+ mattr_accessor :always_write_cookie
+ self.always_write_cookie = false
+
private
def write_cookie?(cookie)
- @secure || !cookie[:secure] || defined?(Rails.env) && Rails.env.development?
+ @secure || !cookie[:secure] || always_write_cookie
end
end
@@ -256,7 +254,6 @@ module ActionDispatch
end
def []=(key, options)
- raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -295,7 +292,6 @@ module ActionDispatch
end
def []=(key, options)
- raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -349,9 +345,6 @@ module ActionDispatch
end
[status, headers, body]
- ensure
- cookie_jar = ActionDispatch::Request.new(env).cookie_jar unless cookie_jar
- cookie_jar.close!
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
new file mode 100644
index 0000000000..b903f98761
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -0,0 +1,82 @@
+require 'action_dispatch/http/request'
+require 'action_dispatch/middleware/exception_wrapper'
+
+module ActionDispatch
+ # This middleware is responsible for logging exceptions and
+ # showing a debugging page in case the request is local.
+ class DebugExceptions
+ RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ begin
+ response = @app.call(env)
+
+ if response[1]['X-Cascade'] == 'pass'
+ body = response[2]
+ body.close if body.respond_to?(:close)
+ raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
+ end
+ rescue Exception => exception
+ raise exception if env['action_dispatch.show_exceptions'] == false
+ end
+
+ exception ? render_exception(env, exception) : response
+ end
+
+ private
+
+ def render_exception(env, exception)
+ wrapper = ExceptionWrapper.new(env, exception)
+ log_error(env, wrapper)
+
+ if env['action_dispatch.show_detailed_exceptions']
+ template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
+ :request => Request.new(env),
+ :exception => wrapper.exception,
+ :application_trace => wrapper.application_trace,
+ :framework_trace => wrapper.framework_trace,
+ :full_trace => wrapper.full_trace
+ )
+
+ file = "rescues/#{wrapper.rescue_template}"
+ body = template.render(:template => file, :layout => 'rescues/layout')
+ render(wrapper.status_code, body)
+ else
+ raise exception
+ end
+ end
+
+ def render(status, body)
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+
+ def log_error(env, wrapper)
+ logger = logger(env)
+ return unless logger
+
+ exception = wrapper.exception
+
+ trace = wrapper.application_trace
+ trace = wrapper.framework_trace if trace.empty?
+
+ ActiveSupport::Deprecation.silence do
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << trace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
+ end
+
+ def logger(env)
+ env['action_dispatch.logger'] || stderr_logger
+ end
+
+ def stderr_logger
+ @stderr_logger ||= ActiveSupport::Logger.new($stderr)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
new file mode 100644
index 0000000000..c0532c80c4
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -0,0 +1,78 @@
+require 'action_controller/metal/exceptions'
+require 'active_support/core_ext/exception'
+
+module ActionDispatch
+ class ExceptionWrapper
+ 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::NotImplemented' => :not_implemented,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
+ )
+
+ cattr_accessor :rescue_templates
+ @@rescue_templates = Hash.new('diagnostics')
+ @@rescue_templates.merge!(
+ 'ActionView::MissingTemplate' => 'missing_template',
+ 'ActionController::RoutingError' => 'routing_error',
+ 'AbstractController::ActionNotFound' => 'unknown_action',
+ 'ActionView::Template::Error' => 'template_error'
+ )
+
+ attr_reader :env, :exception
+
+ def initialize(env, exception)
+ @env = env
+ @exception = original_exception(exception)
+ end
+
+ def rescue_template
+ @@rescue_templates[@exception.class.name]
+ end
+
+ def status_code
+ Rack::Utils.status_code(@@rescue_responses[@exception.class.name])
+ end
+
+ def application_trace
+ clean_backtrace(:silent)
+ end
+
+ def framework_trace
+ clean_backtrace(:noise)
+ end
+
+ def full_trace
+ clean_backtrace(:all)
+ end
+
+ private
+
+ def original_exception(exception)
+ if registered_original_exception?(exception)
+ exception.original_exception
+ else
+ exception
+ end
+ end
+
+ def registered_original_exception?(exception)
+ exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
+ end
+
+ def clean_backtrace(*args)
+ if backtrace_cleaner
+ backtrace_cleaner.clean(@exception.backtrace, *args)
+ else
+ @exception.backtrace
+ end
+ end
+
+ def backtrace_cleaner
+ @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index e59404ef68..bc5b163931 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -93,7 +93,6 @@ module ActionDispatch
end
def []=(k, v) #:nodoc:
- raise ClosedError, :flash if closed?
keep(k)
@flashes[k] = v
end
@@ -159,10 +158,6 @@ module ActionDispatch
@now ||= FlashNow.new(self)
end
- attr_reader :closed
- alias :closed? :closed
- def close!; @closed = true; end
-
# Keeps either the entire current flash or a specific flash entry available for the next action:
#
# flash.keep # keeps the entire flash
@@ -258,7 +253,6 @@ module ActionDispatch
end
env[KEY] = new_hash
- new_hash.close!
end
if session.key?('flash') && session['flash'].empty?
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index d4208ca96e..1cb803ffb9 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -52,14 +52,9 @@ module ActionDispatch
false
end
rescue Exception => e # YAML, XML or Ruby code block errors
- logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
+ logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
- raise
- { "body" => request.raw_post,
- "content_type" => request.content_mime_type,
- "content_length" => request.content_length,
- "exception" => "#{e.message} (#{e.class})",
- "backtrace" => e.backtrace }
+ raise e
end
def content_type_from_legacy_post_data_format_header(env)
@@ -73,8 +68,8 @@ module ActionDispatch
nil
end
- def logger
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
+ def logger(env)
+ env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
new file mode 100644
index 0000000000..85b8d178bf
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -0,0 +1,30 @@
+module ActionDispatch
+ # A simple Rack application that renders exceptions in the given public path.
+ class PublicExceptions
+ attr_accessor :public_path
+
+ def initialize(public_path)
+ @public_path = public_path
+ end
+
+ def call(env)
+ status = env["PATH_INFO"][1..-1]
+ locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
+ path = "#{public_path}/#{status}.html"
+
+ if locale_path && File.exist?(locale_path)
+ render(status, File.read(locale_path))
+ elsif File.exist?(path)
+ render(status, File.read(path))
+ else
+ [404, { "X-Cascade" => "pass" }, []]
+ end
+ end
+
+ private
+
+ def render(status, body)
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 29289a76b4..4f48f1c974 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -43,34 +43,58 @@ module ActionDispatch
# Execute all prepare callbacks.
def self.prepare!
- new(nil).run_callbacks :prepare
+ new(nil).prepare!
end
# Execute all cleanup callbacks.
def self.cleanup!
- new(nil).run_callbacks :cleanup
+ new(nil).cleanup!
end
- def initialize(app)
+ def initialize(app, condition=nil)
@app = app
- end
-
- module CleanupOnClose
- def close
- super if defined?(super)
- ensure
- ActionDispatch::Reloader.cleanup!
- end
+ @condition = condition || lambda { true }
+ @validated = true
end
def call(env)
- run_callbacks :prepare
+ @validated = @condition.call
+ prepare!
response = @app.call(env)
- response[2].extend(CleanupOnClose)
+ response[2].extend(module_hook)
response
rescue Exception
- run_callbacks :cleanup
+ cleanup!
raise
end
+
+ def prepare! #:nodoc:
+ run_callbacks :prepare if validated?
+ end
+
+ def cleanup! #:nodoc:
+ run_callbacks :cleanup if validated?
+ ensure
+ @validated = true
+ end
+
+ private
+
+ def validated? #:nodoc:
+ @validated
+ end
+
+ def module_hook #:nodoc:
+ middleware = self
+ Module.new do
+ define_method :close do
+ begin
+ super() if defined?(super)
+ ensure
+ middleware.cleanup!
+ end
+ end
+ end
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index ee0d19a50d..66ece60860 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -33,7 +33,9 @@ module ActionDispatch
class GetIp
def initialize(env, middleware)
- @env, @middleware = env, middleware
+ @env = env
+ @middleware = middleware
+ @calculated_ip = false
end
# Determines originating IP address. REMOTE_ADDR is the standard
@@ -55,7 +57,10 @@ module ActionDispatch
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
- client_ip || forwarded_ips.last || remote_addrs.first
+ not_proxy = client_ip || forwarded_ips.last || remote_addrs.first
+
+ # Return first REMOTE_ADDR if there are no other options
+ not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
end
def to_s
@@ -66,9 +71,9 @@ module ActionDispatch
protected
- def ips_from(header)
+ def ips_from(header, allow_proxies = false)
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
- ips.reject{|ip| ip =~ @middleware.proxies }
+ allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index bee446c8a5..d5a0b80fd5 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -33,7 +33,7 @@ module ActionDispatch
end
def internal_request_id
- SecureRandom.hex(16)
+ SecureRandom.uuid
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/rescue.rb b/actionpack/lib/action_dispatch/middleware/rescue.rb
deleted file mode 100644
index aee672112c..0000000000
--- a/actionpack/lib/action_dispatch/middleware/rescue.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module ActionDispatch
- class Rescue
- def initialize(app, rescuers = {}, &block)
- @app, @rescuers = app, {}
- rescuers.each { |exception, rescuer| rescue_from(exception, rescuer) }
- instance_eval(&block) if block_given?
- end
-
- def call(env)
- @app.call(env)
- rescue Exception => exception
- if rescuer = @rescuers[exception.class.name]
- env['action_dispatch.rescue.exception'] = exception
- rescuer.call(env)
- else
- raise exception
- end
- end
-
- protected
- def rescue_from(exception, rescuer)
- exception = exception.class.name if exception.is_a?(Exception)
- @rescuers[exception.to_s] = rescuer
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 6bcf099d2c..6a8e690d18 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -30,7 +30,7 @@ module ActionDispatch
def generate_sid
sid = SecureRandom.hex(16)
- sid.encode!('UTF-8') if sid.respond_to?(:encode!)
+ sid.encode!('UTF-8')
sid
end
@@ -74,10 +74,6 @@ module ActionDispatch
class AbstractStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
-
- def destroy_session(env, sid, options)
- raise '#destroy_session needs to be implemented.'
- end
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 2fa68c64c5..3d06214bf1 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,172 +1,63 @@
-require 'active_support/core_ext/exception'
-require 'action_controller/metal/exceptions'
-require 'active_support/notifications'
require 'action_dispatch/http/request'
+require 'action_dispatch/middleware/exception_wrapper'
+require 'active_support/deprecation'
module ActionDispatch
- # This middleware rescues any exception returned by the application and renders
- # nice exception pages if it's being rescued locally.
+ # This middleware rescues any exception returned by the application
+ # and calls an exceptions app that will wrap it in a format for the end user.
+ #
+ # The exceptions app should be passed as parameter on initialization
+ # of ShowExceptions. Everytime there is an exception, ShowExceptions will
+ # store the exception in env["action_dispatch.exception"], rewrite the
+ # PATH_INFO to the exception status code and call the rack app.
+ #
+ # If the application returns a "X-Cascade" pass response, this middleware
+ # will send an empty response as result with the correct status code.
+ # If any exception happens inside the exceptions app, this middleware
+ # catches the exceptions and returns a FAILSAFE_RESPONSE.
class ShowExceptions
- RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
-
- cattr_accessor :rescue_responses
- @@rescue_responses = Hash.new(:internal_server_error)
- @@rescue_responses.update({
- 'ActionController::RoutingError' => :not_found,
- 'AbstractController::ActionNotFound' => :not_found,
- 'ActiveRecord::RecordNotFound' => :not_found,
- 'ActiveRecord::StaleObjectError' => :conflict,
- 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
- 'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
- })
-
- cattr_accessor :rescue_templates
- @@rescue_templates = Hash.new('diagnostics')
- @@rescue_templates.update({
- 'ActionView::MissingTemplate' => 'missing_template',
- 'ActionController::RoutingError' => 'routing_error',
- 'AbstractController::ActionNotFound' => 'unknown_action',
- 'ActionView::Template::Error' => 'template_error'
- })
-
FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'},
["<html><body><h1>500 Internal Server Error</h1>" <<
"If you are the administrator of this website, then please read this web " <<
"application's log file and/or the web server's log file to find out what " <<
"went wrong.</body></html>"]]
- def initialize(app, consider_all_requests_local = false)
+ def initialize(app, exceptions_app)
@app = app
- @consider_all_requests_local = consider_all_requests_local
+ @exceptions_app = exceptions_app
end
def call(env)
begin
- status, headers, body = @app.call(env)
- exception = nil
-
- # Only this middleware cares about RoutingError. So, let's just raise
- # it here.
- if headers['X-Cascade'] == 'pass'
- raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
- end
+ response = @app.call(env)
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
- exception ? render_exception(env, exception) : [status, headers, body]
+ response || render_exception(env, exception)
end
private
- def render_exception(env, exception)
- log_error(exception)
- exception = original_exception(exception)
-
- request = Request.new(env)
- if @consider_all_requests_local || request.local?
- rescue_action_locally(request, exception)
- else
- rescue_action_in_public(exception)
- end
- rescue Exception => failsafe_error
- $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
- FAILSAFE_RESPONSE
- end
-
- # Render detailed diagnostics for unhandled exceptions rescued from
- # a controller action.
- def rescue_action_locally(request, exception)
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
- :request => request,
- :exception => exception,
- :application_trace => application_trace(exception),
- :framework_trace => framework_trace(exception),
- :full_trace => full_trace(exception)
- )
- file = "rescues/#{@@rescue_templates[exception.class.name]}"
- body = template.render(:template => file, :layout => 'rescues/layout')
- render(status_code(exception), body)
- end
- # Attempts to render a static error page based on the
- # <tt>status_code</tt> thrown, or just return headers if no such file
- # exists. At first, it will try to render a localized static page.
- # For example, if a 500 error is being handled Rails and locale is :da,
- # it will first attempt to render the file at <tt>public/500.da.html</tt>
- # then attempt to render <tt>public/500.html</tt>. If none of them exist,
- # the body of the response will be left empty.
- def rescue_action_in_public(exception)
- status = status_code(exception)
- locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
- path = "#{public_path}/#{status}.html"
-
- if locale_path && File.exist?(locale_path)
- render(status, File.read(locale_path))
- elsif File.exist?(path)
- render(status, File.read(path))
- else
- render(status, '')
- end
- end
-
- def status_code(exception)
- Rack::Utils.status_code(@@rescue_responses[exception.class.name])
- end
-
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
- end
-
- def public_path
- defined?(Rails.public_path) ? Rails.public_path : 'public_path'
- end
-
- def log_error(exception)
- return unless logger
-
- ActiveSupport::Deprecation.silence do
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << application_trace(exception).join("\n ")
- logger.fatal("#{message}\n\n")
- end
- end
-
- def application_trace(exception)
- clean_backtrace(exception, :silent)
- end
-
- def framework_trace(exception)
- clean_backtrace(exception, :noise)
- end
-
- def full_trace(exception)
- clean_backtrace(exception, :all)
- end
-
- def clean_backtrace(exception, *args)
- defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
- Rails.backtrace_cleaner.clean(exception.backtrace, *args) :
- exception.backtrace
- end
-
- def logger
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
- end
+ # Define this method because some plugins were monkey patching it.
+ # Remove this after 3.2 is out with the other deprecations in this class.
+ def status_code(*)
+ end
- def original_exception(exception)
- if registered_original_exception?(exception)
- exception.original_exception
- else
- exception
- end
+ def render_exception(env, exception)
+ wrapper = ExceptionWrapper.new(env, exception)
+ status = wrapper.status_code
+ env["action_dispatch.exception"] = wrapper.exception
+ env["PATH_INFO"] = "/#{status}"
+ response = @exceptions_app.call(env)
+ response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
+ rescue Exception => failsafe_error
+ $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
+ FAILSAFE_RESPONSE
end
- def registered_original_exception?(exception)
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
+ def pass_response(status)
+ [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
end
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 6e71fd7ddc..1a308707d1 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -16,6 +16,7 @@
background-color: #eee;
padding: 10px;
font-size: 11px;
+ white-space: pre-wrap;
}
a { color: #000; }
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index ccfa858cce..f06c07daa5 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -1,10 +1,15 @@
<h1>Routing Error</h1>
<p><pre><%=h @exception.message %></pre></p>
-<% unless @exception.failures.empty? %><p>
- <h2>Failure reasons:</h2>
- <ol>
- <% @exception.failures.each do |route, reason| %>
- <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
- <% end %>
- </ol>
-</p><% end %>
+<% unless @exception.failures.empty? %>
+ <p>
+ <h2>Failure reasons:</h2>
+ <ol>
+ <% @exception.failures.each do |route, reason| %>
+ <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
+ <% end %>
+ </ol>
+ </p>
+<% end %>
+<p>
+ Try running <code>rake routes</code> for more information on available routes.
+</p> \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 1af89858d1..46c06386d8 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -9,11 +9,27 @@ module ActionDispatch
config.action_dispatch.best_standards_support = true
config.action_dispatch.tld_length = 1
config.action_dispatch.ignore_accept_header = false
- config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true}
+ config.action_dispatch.rescue_templates = { }
+ config.action_dispatch.rescue_responses = { }
+
+ config.action_dispatch.rack_cache = {
+ :metastore => "rails:/",
+ :entitystore => "rails:/",
+ :verbose => true
+ }
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
+ ActionDispatch::Response.default_charset = app.config.encoding
+
+ ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
+ ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
+
+ config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
+ ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
+
+ ActionDispatch.test_app = app
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 1dcd83ceb5..fa300b4a16 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -277,7 +277,6 @@ module ActionDispatch
#
module Routing
autoload :Mapper, 'action_dispatch/routing/mapper'
- autoload :Route, 'action_dispatch/routing/route'
autoload :RouteSet, 'action_dispatch/routing/route_set'
autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
autoload :UrlFor, 'action_dispatch/routing/url_for'
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 970236a05a..ce4d407217 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -16,7 +16,7 @@ module ActionDispatch
end
end
- attr_reader :app
+ attr_reader :app, :constraints
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
@@ -374,10 +374,6 @@ module ActionDispatch
# # Matches any request starting with 'path'
# match 'path' => 'c#a', :anchor => false
def match(path, options=nil)
- mapping = Mapping.new(@set, @scope, path, options || {})
- app, conditions, requirements, defaults, as, anchor = mapping.to_route
- @set.add_route(app, conditions, requirements, defaults, as, anchor)
- self
end
# Mount a Rack-based application to be used within the application.
@@ -867,8 +863,6 @@ module ActionDispatch
CANONICAL_ACTIONS = %w(index create new show update destroy)
class Resource #:nodoc:
- DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
-
attr_reader :controller, :path, :options
def initialize(entities, options = {})
@@ -880,7 +874,7 @@ module ActionDispatch
end
def default_actions
- self.class::DEFAULT_ACTIONS
+ [:index, :create, :new, :show, :update, :destroy, :edit]
end
def actions
@@ -934,16 +928,17 @@ module ActionDispatch
end
class SingletonResource < Resource #:nodoc:
- DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
-
def initialize(entities, options)
super
-
@as = nil
@controller = (options[:controller] || plural).to_s
@as = options[:as]
end
+ def default_actions
+ [:show, :create, :update, :destroy, :new, :edit]
+ end
+
def plural
@plural ||= name.to_s.pluralize
end
@@ -991,7 +986,7 @@ module ActionDispatch
return self
end
- resource_scope(SingletonResource.new(resources.pop, options)) do
+ resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
yield if block_given?
collection do
@@ -1051,8 +1046,8 @@ module ActionDispatch
# Takes same options as <tt>Base#match</tt> as well as:
#
# [:path_names]
- # Allows you to change the paths of the seven default actions.
- # Paths not specified are not changed.
+ # Allows you to change the segment component of the +edit+ and +new+ actions.
+ # Actions not specified are not changed.
#
# resources :posts, :path_names => { :new => "brand_new" }
#
@@ -1122,7 +1117,7 @@ module ActionDispatch
return self
end
- resource_scope(Resource.new(resources.pop, options)) do
+ resource_scope(:resources, Resource.new(resources.pop, options)) do
yield if block_given?
collection do
@@ -1245,32 +1240,44 @@ module ActionDispatch
parent_resource.instance_of?(Resource) && @scope[:shallow]
end
- def match(*args)
- options = args.extract_options!.dup
- options[:anchor] = true unless options.key?(:anchor)
-
- if args.length > 1
- args.each { |path| match(path, options.dup) }
- return self
+ def match(path, *rest)
+ if rest.empty? && Hash === path
+ options = path
+ path, to = options.find { |name, value| name.is_a?(String) }
+ options[:to] = to
+ options.delete(path)
+ paths = [path]
+ else
+ options = rest.pop || {}
+ paths = [path] + rest
end
- on = options.delete(:on)
- if VALID_ON_OPTIONS.include?(on)
- args.push(options)
- return send(on){ match(*args) }
- elsif on
+ options[:anchor] = true unless options.key?(:anchor)
+
+ if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
- if @scope[:scope_level] == :resources
- args.push(options)
- return nested { match(*args) }
- elsif @scope[:scope_level] == :resource
- args.push(options)
- return member { match(*args) }
+ paths.each { |_path| decomposed_match(_path, options.dup) }
+ self
+ end
+
+ def decomposed_match(path, options) # :nodoc:
+ if on = options.delete(:on)
+ send(on) { decomposed_match(path, options) }
+ else
+ case @scope[:scope_level]
+ when :resources
+ nested { decomposed_match(path, options) }
+ when :resource
+ member { decomposed_match(path, options) }
+ else
+ add_route(path, options)
+ end
end
+ end
- action = args.first
+ def add_route(action, options) # :nodoc:
path = path_for_action(action, options.delete(:path))
if action.to_s =~ /^[\w\/]+$/
@@ -1279,13 +1286,15 @@ module ActionDispatch
action = nil
end
- if options.key?(:as) && !options[:as]
+ if !options.fetch(:as, true)
options.delete(:as)
else
options[:as] = name_for_action(options[:as], action)
end
- super(path, options)
+ mapping = Mapping.new(@set, @scope, path, options)
+ app, conditions, requirements, defaults, as, anchor = mapping.to_route
+ @set.add_route(app, conditions, requirements, defaults, as, anchor)
end
def root(options={})
@@ -1341,7 +1350,7 @@ module ActionDispatch
end
def scope_action_options? #:nodoc:
- @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
+ @scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
end
def scope_action_options #:nodoc:
@@ -1349,11 +1358,11 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- @scope[:scope_level].in?([:resource, :resources])
+ [:resource, :resources].include? @scope[:scope_level]
end
def resource_method_scope? #:nodoc:
- @scope[:scope_level].in?([:collection, :member, :new])
+ [:collection, :member, :new].include? @scope[:scope_level]
end
def with_exclusive_scope
@@ -1378,8 +1387,8 @@ module ActionDispatch
@scope[:scope_level_resource] = old_resource
end
- def resource_scope(resource) #:nodoc:
- with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
+ def resource_scope(kind, resource) #:nodoc:
+ with_scope_level(kind, resource) do
scope(parent_resource.resource_scope) do
yield
end
@@ -1387,10 +1396,12 @@ module ActionDispatch
end
def nested_options #:nodoc:
- {}.tap do |options|
- options[:as] = parent_resource.member_name
- options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
- end
+ options = { :as => parent_resource.member_name }
+ options[:constraints] = {
+ :"#{parent_resource.singular}_id" => id_constraint
+ } if id_constraint?
+
+ options
end
def id_constraint? #:nodoc:
@@ -1421,8 +1432,7 @@ module ActionDispatch
end
def action_path(name, path = nil) #:nodoc:
- # Ruby 1.8 can't transform empty strings to symbols
- name = name.to_sym if name.is_a?(String) && !name.empty?
+ name = name.to_sym if name.is_a?(String)
path || @scope[:path_names][name] || name.to_s
end
@@ -1461,22 +1471,17 @@ module ActionDispatch
[name_prefix, member_name, prefix]
end
- candidate = name.select(&:present?).join("_").presence
- candidate unless as.nil? && @set.routes.find { |r| r.name == candidate }
- end
- end
-
- module Shorthand #:nodoc:
- def match(path, *rest)
- if rest.empty? && Hash === path
- options = path
- path, to = options.find { |name, value| name.is_a?(String) }
- options.merge!(:to => to).delete(path)
- super(path, options)
- else
- super
+ if candidate = name.select(&:present?).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
+ else
+ candidate
+ end
+ end
end
- end
end
def initialize(set) #:nodoc:
@@ -1489,7 +1494,6 @@ module ActionDispatch
include Redirection
include Scoping
include Resources
- include Shorthand
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index e989a38d8b..013cf93dbc 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -165,7 +165,7 @@ module ActionDispatch
if parent.is_a?(Symbol) || parent.is_a?(String)
parent
else
- ActiveModel::Naming.route_key(parent).singularize
+ ActiveModel::Naming.singular_route_key(parent)
end
end
else
@@ -176,9 +176,11 @@ module ActionDispatch
if record.is_a?(Symbol) || record.is_a?(String)
route << record
elsif record
- route << ActiveModel::Naming.route_key(record)
- route = [route.join("_").singularize] if inflection == :singular
- route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural
+ if inflection == :singular
+ route << ActiveModel::Naming.singular_route_key(record)
+ else
+ route << ActiveModel::Naming.route_key(record)
+ end
else
raise ArgumentError, "Nil location provided. Can't build URI."
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 804991ad5f..617b24b46a 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -2,6 +2,54 @@ require 'action_dispatch/http/request'
module ActionDispatch
module Routing
+ class Redirect # :nodoc:
+ attr_reader :status, :block
+
+ def initialize(status, block)
+ @status = status
+ @block = block
+ end
+
+ def call(env)
+ req = Request.new(env)
+
+ uri = URI.parse(path(req.symbolized_path_parameters, req))
+ uri.scheme ||= req.scheme
+ uri.host ||= req.host
+ uri.port ||= req.port unless req.standard_port?
+
+ body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
+
+ headers = {
+ 'Location' => uri.to_s,
+ 'Content-Type' => 'text/html',
+ 'Content-Length' => body.length.to_s
+ }
+
+ [ status, headers, [body] ]
+ end
+
+ def path(params, request)
+ block.call params, request
+ end
+ end
+
+ class OptionRedirect < Redirect # :nodoc:
+ alias :options :block
+
+ def path(params, request)
+ url_options = {
+ :protocol => request.protocol,
+ :host => request.host,
+ :port => request.optional_port,
+ :path => request.path,
+ :params => request.query_parameters
+ }.merge options
+
+ ActionDispatch::Http::URL.url_for url_options
+ end
+ end
+
module Redirection
# Redirect any path to another path:
@@ -40,71 +88,18 @@ module ActionDispatch
options = args.last.is_a?(Hash) ? args.pop : {}
status = options.delete(:status) || 301
- path = args.shift
+ return OptionRedirect.new(status, options) if options.any?
- path_proc = if path.is_a?(String)
- proc { |params| (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) }
- elsif options.any?
- options_proc(options)
- elsif path.respond_to?(:call)
- proc { |params, request| path.call(params, request) }
- elsif block
- block
- else
- raise ArgumentError, "redirection argument not supported"
- end
-
- redirection_proc(status, path_proc)
- end
+ path = args.shift
- private
-
- def options_proc(options)
- proc do |params, request|
- path = if options[:path].nil?
- request.path
- elsif params.empty? || !options[:path].match(/%\{\w*\}/)
- options.delete(:path)
- else
- (options.delete(:path) % params)
- end
-
- default_options = {
- :protocol => request.protocol,
- :host => request.host,
- :port => request.optional_port,
- :path => path,
- :params => request.query_parameters
- }
-
- ActionDispatch::Http::URL.url_for(options.reverse_merge(default_options))
- end
- end
-
- def redirection_proc(status, path_proc)
- lambda do |env|
- req = Request.new(env)
-
- params = [req.symbolized_path_parameters]
- params << req if path_proc.arity > 1
-
- uri = URI.parse(path_proc.call(*params))
- uri.scheme ||= req.scheme
- uri.host ||= req.host
- uri.port ||= req.port unless req.standard_port?
-
- body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
-
- headers = {
- 'Location' => uri.to_s,
- 'Content-Type' => 'text/html',
- 'Content-Length' => body.length.to_s
- }
-
- [ status, headers, [body] ]
- end
- end
+ block = lambda { |params, request|
+ (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params)
+ } if String === path
+ block = path if path.respond_to? :call
+ raise ArgumentError, "redirection argument not supported" unless block
+ Redirect.new status, block
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2bcde16110..d065d9f9d8 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -356,7 +356,7 @@ module ActionDispatch
conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
- named_routes[name] = route if name
+ named_routes[name] = route if name && !named_routes[name]
route
end
@@ -557,7 +557,7 @@ module ActionDispatch
path_addition, params = generate(path_options, path_segments || {})
path << path_addition
- ActionDispatch::Http::URL.url_for(options.merge({
+ ActionDispatch::Http::URL.url_for(options.merge!({
:path => path,
:params => params,
:user => user,
@@ -584,7 +584,7 @@ module ActionDispatch
@router.recognize(req) do |route, matches, params|
params.each do |key, value|
if value.is_a?(String)
- value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
+ value = value.dup.force_encoding(Encoding::BINARY)
params[key] = URI.parser.unescape(value)
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 8fc8dc191b..22e41c9c16 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -42,7 +42,7 @@ module ActionDispatch
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
- # :host => 'www.example.com') # Changed this.
+ # :host => 'www.example.com')
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
@@ -52,7 +52,7 @@ module ActionDispatch
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
- # in full. However, mailers don't have hostname information, and what's why you'll still
+ # in full. However, mailers don't have hostname information, and that's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
@@ -145,7 +145,7 @@ module ActionDispatch
when String
options
when nil, Hash
- _routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys)
+ _routes.url_for((options || {}).symbolize_keys.reverse_merge!(url_options))
else
polymorphic_url(options)
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 0f1bb9f260..26db8662a8 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -463,9 +463,12 @@ module ActionDispatch
@@app = nil
def self.app
- # DEPRECATE Rails application fallback
- # This should be set by the initializer
- @@app || (defined?(Rails.application) && Rails.application) || nil
+ if !@@app && !ActionDispatch.test_app
+ ActiveSupport::Deprecation.warn "Rails application fallback is deprecated " \
+ "and no longer works, please set ActionDispatch.test_app", caller
+ end
+
+ @@app || ActionDispatch.test_app
end
def self.app=(app)