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/headers.rb23
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb26
-rw-r--r--actionpack/lib/action_dispatch/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb13
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb245
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb59
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb8
10 files changed, 266 insertions, 122 deletions
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 788eed8b89..3e607bbde1 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,5 +1,10 @@
module ActionDispatch
module Http
+ # Provides access to the request's HTTP headers from the environment.
+ #
+ # env = { "CONTENT_TYPE" => "text/plain" }
+ # headers = ActionDispatch::Http::Headers.new(env)
+ # headers["Content-Type"] # => "text/plain"
class Headers
CGI_VARIABLES = %w(
CONTENT_TYPE CONTENT_LENGTH
@@ -14,14 +19,16 @@ module ActionDispatch
include Enumerable
attr_reader :env
- def initialize(env = {})
+ def initialize(env = {}) # :nodoc:
@env = env
end
+ # Returns the value for the given key mapped to @env.
def [](key)
@env[env_name(key)]
end
+ # Sets the given value for the key mapped to @env.
def []=(key, value)
@env[env_name(key)] = value
end
@@ -31,6 +38,13 @@ module ActionDispatch
end
alias :include? :key?
+ # Returns the value for the given key mapped to @env.
+ #
+ # If the key is not found and an optional code block is not provided,
+ # raises a <tt>KeyError</tt> exception.
+ #
+ # If the code block is provided, then it will be run and
+ # its result returned.
def fetch(key, *args, &block)
@env.fetch env_name(key), *args, &block
end
@@ -39,12 +53,17 @@ module ActionDispatch
@env.each(&block)
end
+ # Returns a new Http::Headers instance containing the contents of
+ # <tt>headers_or_env</tt> and the original instance.
def merge(headers_or_env)
headers = Http::Headers.new(env.dup)
headers.merge!(headers_or_env)
headers
end
+ # Adds the contents of <tt>headers_or_env</tt> to original instance
+ # entries; duplicate keys are overwritten with the values from
+ # <tt>headers_or_env</tt>.
def merge!(headers_or_env)
headers_or_env.each do |key, value|
self[env_name(key)] = value
@@ -52,6 +71,8 @@ module ActionDispatch
end
private
+ # Converts a HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
def env_name(key)
key = key.to_s
if key =~ HTTP_HEADER
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index daa06e96e6..cdb3e44b3a 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -64,6 +64,7 @@ module ActionDispatch
# Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt)
# Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt)
# Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt)
+ # Calendar Extensions to WebDAV (http://www.ietf.org/rfc/rfc4791.txt)
# PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt)
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
@@ -71,9 +72,10 @@ module ActionDispatch
RFC3648 = %w(ORDERPATCH)
RFC3744 = %w(ACL)
RFC5323 = %w(SEARCH)
+ RFC4791 = %w(MKCALENDAR)
RFC5789 = %w(PATCH)
- HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789
+ HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789
HTTP_METHOD_LOOKUP = {}
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 3d27ff2b24..eaea93b730 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -296,6 +296,9 @@ module ActionDispatch # :nodoc:
cookies
end
+ def _status_code
+ @status
+ end
private
def before_committed
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 6f5a52c568..c9860af909 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -29,15 +29,12 @@ module ActionDispatch
extract_subdomains(host, tld_length).join('.')
end
- def url_for(options = {})
- options = options.dup
- path = options.delete(:script_name).to_s.chomp("/")
- path << options.delete(:path).to_s
-
- params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
- params.reject! { |_,v| v.to_param.nil? }
+ def url_for(options)
+ path = options[:script_name].to_s.chomp("/")
+ path << options[:path].to_s
result = build_host_url(options)
+
if options[:trailing_slash]
if path.include?('?')
result << path.sub(/\?/, '/\&')
@@ -47,7 +44,16 @@ module ActionDispatch
else
result << path
end
- result << "?#{params.to_query}" unless params.empty?
+
+ if options.key? :params
+ params = options[:params].is_a?(Hash) ?
+ options[:params] :
+ { params: options[:params] }
+
+ params.reject! { |_,v| v.to_param.nil? }
+ result << "?#{params.to_query}" unless params.empty?
+ end
+
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
result
end
@@ -55,7 +61,7 @@ module ActionDispatch
private
def build_host_url(options)
- if options[:host].blank? && options[:only_path].blank?
+ 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'
end
@@ -130,7 +136,7 @@ module ActionDispatch
case options[:protocol]
when "//"
- nil
+ options[:port]
when "https://"
options[:port].to_i == 443 ? nil : options[:port]
else
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 9cd884daa3..ce03164ca9 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,6 +1,7 @@
# encoding: UTF-8
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/regexp'
+require 'active_support/dependencies/autoload'
module ActionDispatch
# The routing module provides URL rewriting in native Ruby. It's a way to
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 4c20974ac7..58c7f5330e 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -578,18 +578,17 @@ module ActionDispatch
_route = @set.named_routes.routes[name.to_sym]
_routes = @set
app.routes.define_mounted_helper(name)
- app.routes.singleton_class.class_eval do
- redefine_method :mounted? do
- true
- end
-
- redefine_method :_generate_prefix do |options|
+ app.routes.extend Module.new {
+ def mounted?; true; 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)
+ end
end
- end
+ }
end
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index b800ee6448..bd3696cda1 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -101,58 +101,45 @@ module ActionDispatch
# polymorphic_url(Comment) # same as comments_url()
#
def polymorphic_url(record_or_hash_or_array, options = {})
- recipient = self
-
- if record_or_hash_or_array.kind_of?(Array)
- if record_or_hash_or_array.include? nil
- raise ArgumentError, "Nil location provided. Can't build URI."
- end
- record_or_hash_or_array = record_or_hash_or_array.dup
- if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
- recipient = record_or_hash_or_array.shift
- end
- record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
+ if Hash === record_or_hash_or_array
+ options = record_or_hash_or_array.merge(options)
+ record = options.delete :id
+ return polymorphic_url record, options
end
- record = extract_record(record_or_hash_or_array)
- record = convert_to_model(record)
-
- args = Array === record_or_hash_or_array ?
- record_or_hash_or_array.dup :
- [ record_or_hash_or_array ]
+ opts = options.dup
+ action = opts.delete :action
+ type = opts.delete(:routing_type) || :url
- inflection = if options[:action] && options[:action].to_s == "new"
- args.pop
- :singular
- elsif (record.respond_to?(:persisted?) && !record.persisted?)
- args.pop
- :plural
- elsif record.is_a?(Class)
- args.pop
- :plural
- else
- :singular
- end
-
- args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
- named_route = build_named_route_call(record_or_hash_or_array, record, inflection, options)
-
- url_options = options.except(:action, :routing_type)
- unless url_options.empty?
- args << url_options
- end
+ HelperMethodBuilder.polymorphic_method self,
+ record_or_hash_or_array,
+ action,
+ type,
+ opts
- args.collect! { |a| convert_to_model(a) }
-
- recipient.send(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
# <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
def polymorphic_path(record_or_hash_or_array, options = {})
- polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
+ if Hash === record_or_hash_or_array
+ options = record_or_hash_or_array.merge(options)
+ record = options.delete :id
+ return polymorphic_path record, options
+ end
+
+ opts = options.dup
+ action = opts.delete :action
+ type = :path
+
+ HelperMethodBuilder.polymorphic_method self,
+ record_or_hash_or_array,
+ action,
+ type,
+ opts
end
+
%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 = {})
@@ -170,53 +157,169 @@ module ActionDispatch
end
private
- def action_prefix(options)
- options[:action] ? "#{options[:action]}_" : ''
+
+ class HelperMethodBuilder # :nodoc:
+ CACHE = { 'path' => {}, 'url' => {} }
+
+ def self.get(action, type)
+ type = type.to_s
+ CACHE[type].fetch(action) { build action, type }
+ end
+
+ def self.url; CACHE['url'.freeze][nil]; end
+ def self.path; CACHE['path'.freeze][nil]; end
+
+ def self.build(action, type)
+ prefix = action ? "#{action}_" : ""
+ suffix = type
+ if action.to_s == 'new'
+ HelperMethodBuilder.singular prefix, suffix
+ else
+ HelperMethodBuilder.plural prefix, suffix
+ end
end
- def routing_type(options)
- options[:routing_type] || :url
+ def self.singular(prefix, suffix)
+ new(->(name) { name.singular_route_key }, prefix, suffix)
end
- def build_named_route_call(records, record, inflection, options = {})
- if records.is_a?(Array)
- record = records.pop
- route = records.map do |parent|
- if parent.is_a?(Symbol) || parent.is_a?(String)
- parent
- else
- model_name_from_record_or_class(parent).singular_route_key
- end
+ def self.plural(prefix, suffix)
+ new(->(name) { name.route_key }, prefix, suffix)
+ end
+
+ def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
+ builder = get action, type
+
+ case record_or_hash_or_array
+ when Array
+ if record_or_hash_or_array.empty? || record_or_hash_or_array.include?(nil)
+ raise ArgumentError, "Nil location provided. Can't build URI."
+ end
+ if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
+ recipient = record_or_hash_or_array.shift
end
+
+ method, args = builder.handle_list record_or_hash_or_array
+ when String, Symbol
+ method, args = builder.handle_string record_or_hash_or_array
+ when Class
+ method, args = builder.handle_class record_or_hash_or_array
+
+ when nil
+ raise ArgumentError, "Nil location provided. Can't build URI."
+ else
+ method, args = builder.handle_model record_or_hash_or_array
+ end
+
+
+ if options.empty?
+ recipient.send(method, *args)
else
- route = []
+ recipient.send(method, *args, options)
end
+ end
- if record.is_a?(Symbol) || record.is_a?(String)
- route << record
- elsif record
- if inflection == :singular
- route << model_name_from_record_or_class(record).singular_route_key
+ attr_reader :suffix, :prefix
+
+ def initialize(key_strategy, prefix, suffix)
+ @key_strategy = key_strategy
+ @prefix = prefix
+ @suffix = suffix
+ end
+
+ def handle_string(record)
+ [get_method_for_string(record), []]
+ end
+
+ def handle_string_call(target, str)
+ target.send get_method_for_string str
+ end
+
+ def handle_class(klass)
+ [get_method_for_class(klass), []]
+ end
+
+ def handle_class_call(target, klass)
+ target.send get_method_for_class klass
+ end
+
+ def handle_model(record)
+ args = []
+
+ model = record.to_model
+ name = if record.persisted?
+ args << model
+ model.class.model_name.singular_route_key
+ else
+ @key_strategy.call model.class.model_name
+ end
+
+ named_route = prefix + "#{name}_#{suffix}"
+
+ [named_route, args]
+ end
+
+ def handle_model_call(target, model)
+ method, args = handle_model model
+ target.send(method, *args)
+ end
+
+ def handle_list(list)
+ record_list = list.dup
+ record = record_list.pop
+
+ args = []
+
+ route = record_list.map { |parent|
+ case parent
+ when Symbol, String
+ parent.to_s
+ when Class
+ args << parent
+ parent.model_name.singular_route_key
else
- route << model_name_from_record_or_class(record).route_key
+ args << parent.to_model
+ parent.to_model.class.model_name.singular_route_key
end
+ }
+
+ route <<
+ case record
+ when Symbol, String
+ record.to_s
+ when Class
+ @key_strategy.call record.model_name
else
- raise ArgumentError, "Nil location provided. Can't build URI."
+ if record.persisted?
+ args << record.to_model
+ record.to_model.class.model_name.singular_route_key
+ else
+ @key_strategy.call record.to_model.class.model_name
+ end
end
- route << routing_type(options)
+ route << suffix
- action_prefix(options) + route.join("_")
+ named_route = prefix + route.join("_")
+ [named_route, args]
end
- def extract_record(record_or_hash_or_array)
- case record_or_hash_or_array
- when Array; record_or_hash_or_array.last
- when Hash; record_or_hash_or_array[:id]
- else record_or_hash_or_array
- end
+ private
+
+ def get_method_for_class(klass)
+ name = @key_strategy.call klass.model_name
+ prefix + "#{name}_#{suffix}"
+ end
+
+ def get_method_for_string(str)
+ prefix + "#{str}_#{suffix}"
end
+
+ [nil, 'new', 'edit'].each do |action|
+ CACHE['url'][action] = build action, 'url'
+ CACHE['path'][action] = build action, 'path'
+ end
+ end
end
end
end
-
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 1ec6fa674b..e699419f23 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,11 +1,13 @@
require 'action_dispatch/journey'
require 'forwardable'
require 'thread_safe'
+require 'active_support/concern'
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 'action_controller/metal/exceptions'
+require 'action_dispatch/http/request'
module ActionDispatch
module Routing
@@ -155,7 +157,7 @@ module ActionDispatch
end
def self.optimize_helper?(route)
- !route.glob? && route.requirements.except(:controller, :action).empty?
+ !route.glob? && route.path.requirements.empty?
end
class OptimizedUrlHelper < UrlHelper # :nodoc:
@@ -171,8 +173,7 @@ module ActionDispatch
def call(t, args)
if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
- options = @options.dup
- options.merge!(t.url_options) if t.respond_to?(:url_options)
+ options = t.url_options.merge @options
options[:path] = optimized_helper(args)
ActionDispatch::Http::URL.url_for(options)
else
@@ -225,17 +226,18 @@ module ActionDispatch
end
def call(t, args)
- t.url_for(handle_positional_args(t, args, @options, @segment_keys))
+ options = t.url_options.merge @options
+ hash = handle_positional_args(t, args, options, @segment_keys)
+ t._routes.url_for(hash)
end
- def handle_positional_args(t, args, options, keys)
+ def handle_positional_args(t, args, result, keys)
inner_options = args.extract_options!
- result = options.dup
if args.size > 0
if args.size < keys.size - 1 # take format into account
- keys -= t.url_options.keys if t.respond_to?(:url_options)
- keys -= options.keys
+ keys -= t.url_options.keys
+ keys -= result.keys
end
keys -= inner_options.keys
result.merge!(Hash[keys.zip(args)])
@@ -393,6 +395,8 @@ module ActionDispatch
@_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
@@ -639,28 +643,34 @@ module ActionDispatch
!mounted? && default_url_options.empty?
end
- def _generate_prefix(options = {})
- nil
+ def find_script_name(options)
+ options.delete :script_name
end
- # The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
+ # The +options+ argument must be a hash whose keys are *symbols*.
def url_for(options)
- options = default_url_options.merge(options || {})
+ options = default_url_options.merge options
+
+ user = password = nil
- user, password = extract_authentication(options)
- recall = options.delete(:_recall)
+ if options[:user] && options[:password]
+ user = options.delete :user
+ password = options.delete :password
+ end
- original_script_name = options.delete(:original_script_name).presence
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
+ recall = options.delete(:_recall) { {} }
+
+ original_script_name = options.delete(:original_script_name)
+ script_name = find_script_name options
if script_name && original_script_name
script_name = original_script_name + script_name
end
- path_options = options.except(*RESERVED_OPTIONS)
- path_options = yield(path_options) if block_given?
+ path_options = options.dup
+ RESERVED_OPTIONS.each { |ro| path_options.delete ro }
- path, params = generate(path_options, recall || {})
+ path, params = generate(path_options, recall)
params.merge!(options[:params] || {})
ActionDispatch::Http::URL.url_for(options.merge!({
@@ -715,17 +725,6 @@ module ActionDispatch
raise ActionController::RoutingError, "No route matches #{path.inspect}"
end
-
- private
-
- def extract_authentication(options)
- if options[:user] && options[:password]
- [options.delete(:user), options.delete(:password)]
- else
- nil
- end
- end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 4a0ef40873..e624fe3c4a 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -155,10 +155,14 @@ module ActionDispatch
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
when String
options
+ when Symbol
+ HelperMethodBuilder.url.handle_string_call self, options
when Array
polymorphic_url(options, options.extract_options!)
+ when Class
+ HelperMethodBuilder.url.handle_class_call self, options
else
- polymorphic_url(options)
+ HelperMethodBuilder.url.handle_model_call self, options
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 68feb26936..0adc6c84ff 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -73,7 +73,13 @@ module ActionDispatch
if Regexp === fragment
fragment
else
- @controller._compute_redirect_to_location(fragment)
+ 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)
end
end
end