diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r-- | actionpack/lib/action_dispatch/http/headers.rb | 23 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/http/request.rb | 4 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/http/response.rb | 3 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/http/url.rb | 26 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing.rb | 1 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/mapper.rb | 13 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/polymorphic_routes.rb | 245 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/route_set.rb | 59 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/url_for.rb | 6 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/testing/assertions/response.rb | 8 |
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 |