diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/routing/route_set.rb')
-rw-r--r-- | actionpack/lib/action_dispatch/routing/route_set.rb | 367 |
1 files changed, 156 insertions, 211 deletions
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 454593b59f..5853adb110 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,14 +1,11 @@ -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' -require 'action_dispatch/routing/endpoint' +require "action_dispatch/journey" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/array/extract_options" +require "action_controller/metal/exceptions" +require "action_dispatch/http/request" +require "action_dispatch/routing/endpoint" module ActionDispatch module Routing @@ -21,65 +18,47 @@ module ActionDispatch alias inspect to_s class Dispatcher < Routing::Endpoint - def initialize(defaults) - @defaults = defaults - @controller_class_names = ThreadSafe::Cache.new + def initialize(raise_on_name_error) + @raise_on_name_error = raise_on_name_error end def dispatcher?; true; end def serve(req) - req.check_path_parameters! - params = req.path_parameters - - prepare_params!(params) - - # Just raise undefined constant errors if a controller was specified as default. - unless controller = controller(params, @defaults.key?(:controller)) - return [404, {'X-Cascade' => 'pass'}, []] + params = req.path_parameters + controller = controller req + res = controller.make_response! req + dispatch(controller, params[:action], req, res) + rescue ActionController::RoutingError + if @raise_on_name_error + raise + else + return [404, { "X-Cascade" => "pass" }, []] end - - dispatch(controller, params[:action], req.env) end - def prepare_params!(params) - normalize_controller!(params) - merge_default_action!(params) - end + private - # If this is a default_controller (i.e. a controller specified by the user) - # we should raise an error in case it's not found, because it usually means - # a user error. However, if the controller was retrieved through a dynamic - # segment, as in :controller(/:action), we should simply return nil and - # delegate the control back to Rack cascade. Besides, if this is not a default - # controller, it means we should respect the @scope[:module] parameter. - def controller(params, default_controller=true) - if params && params.key?(:controller) - controller_param = params[:controller] - controller_reference(controller_param) - end + def controller(req) + req.controller_class rescue NameError => e - raise ActionController::RoutingError, e.message, e.backtrace if default_controller + raise ActionController::RoutingError, e.message, e.backtrace end - private - - def controller_reference(controller_param) - const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller" - ActiveSupport::Dependencies.constantize(const_name) + def dispatch(controller, action, req, res) + controller.dispatch(action, req, res) end + end - def dispatch(controller, action, env) - controller.action(action).call(env) + class StaticDispatcher < Dispatcher + def initialize(controller_class) + super(false) + @controller_class = controller_class end - def normalize_controller!(params) - params[:controller] = params[:controller].underscore if params.key?(:controller) - end + private - def merge_default_action!(params) - params[:action] ||= 'index' - end + def controller(_); @controller_class; end end # A NamedRouteCollection instance is a collection of named routes, and also @@ -88,9 +67,10 @@ module ActionDispatch class NamedRouteCollection include Enumerable attr_reader :routes, :url_helpers_module, :path_helpers_module + private :routes def initialize - @routes = {} + @routes = {} @path_helpers = Set.new @url_helpers = Set.new @url_helpers_module = Module.new @@ -142,6 +122,7 @@ module ActionDispatch end def key?(name) + return unless name routes.key? name.to_sym end @@ -198,40 +179,40 @@ module ActionDispatch private - def optimized_helper(args) - params = parameterize_args(args) { |k| - raise_generation_error(args) - } + def optimized_helper(args) + params = parameterize_args(args) do + raise_generation_error(args) + end - @route.format params - end + @route.format params + end - def optimize_routes_generation?(t) - t.send(:optimize_routes_generation?) - end + def optimize_routes_generation?(t) + t.send(:optimize_routes_generation?) + end - def parameterize_args(args) - params = {} - @arg_size.times { |i| - key = @required_parts[i] - value = args[i].to_param - yield key if value.nil? || value.empty? - params[key] = value - } - params - end + def parameterize_args(args) + params = {} + @arg_size.times { |i| + key = @required_parts[i] + value = args[i].to_param + yield key if value.nil? || value.empty? + params[key] = value + } + params + end - def raise_generation_error(args) - missing_keys = [] - params = parameterize_args(args) { |missing_key| - missing_keys << missing_key - } - constraints = Hash[@route.requirements.merge(params).sort_by{|k,v| k.to_s}] - message = "No route matches #{constraints.inspect}" - message << " missing required keys: #{missing_keys.sort.inspect}" + def raise_generation_error(args) + missing_keys = [] + params = parameterize_args(args) { |missing_key| + missing_keys << missing_key + } + constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }] + message = "No route matches #{constraints.inspect}" + message << ", missing required keys: #{missing_keys.sort.inspect}" - raise ActionController::UrlGenerationError, message - end + raise ActionController::UrlGenerationError, message + end end def initialize(route, options, route_name, url_strategy) @@ -267,9 +248,13 @@ module ActionDispatch path_params -= controller_options.keys path_params -= result.keys end - path_params -= inner_options.keys - path_params.take(args.size).each do |param| - result[param] = args.shift + inner_options.each_key do |key| + path_params.delete(key) + end + + args.each_with_index do |arg, index| + param = path_params[index] + result[param] = arg if param end end @@ -278,29 +263,39 @@ module ActionDispatch end private - # Create a url helper allowing ordered parameters to be associated - # with corresponding dynamic segments, so you can do: - # - # foo_url(bar, baz, bang) - # - # Instead of: - # - # foo_url(bar: bar, baz: baz, bang: bang) - # - # Also allow options hash, so you can do: - # - # foo_url(bar, baz, bang, sort_by: 'baz') - # - def define_url_helper(mod, route, name, opts, route_key, url_strategy) - helper = UrlHelper.create(route, opts, route_key, url_strategy) - mod.module_eval do - define_method(name) do |*args| - options = nil - options = args.pop if args.last.is_a? Hash - helper.call self, args, options + # Create a url helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(bar: bar, baz: baz, bang: bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, sort_by: 'baz') + # + def define_url_helper(mod, route, name, opts, route_key, url_strategy) + helper = UrlHelper.create(route, opts, route_key, url_strategy) + mod.module_eval do + define_method(name) do |*args| + last = args.last + options = \ + case last + when Hash + args.pop + when ActionController::Parameters + if last.permitted? + args.pop.to_h + else + raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE + end + end + helper.call self, args, options + end end end - end end # strategy for building urls to send to the client @@ -315,7 +310,7 @@ module ActionDispatch alias :routes :set def self.default_resources_path_names - { :new => 'new', :edit => 'edit' } + { new: "new", edit: "edit" } end def self.new_with_config(config) @@ -351,7 +346,7 @@ module ActionDispatch @set = Journey::Routes.new @router = Journey::Router.new @set - @formatter = Journey::Formatter.new @set + @formatter = Journey::Formatter.new self end def relative_url_root @@ -366,6 +361,11 @@ module ActionDispatch ActionDispatch::Request end + def make_request(env) + request_class.new env + end + private :make_request + def draw(&block) clear! unless @disable_clear_and_finalize eval_block(block) @@ -382,10 +382,6 @@ module ActionDispatch end def eval_block(block) - if block.arity == 1 - raise "You are using the old router DSL which has been removed in Rails 3.1. " << - "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" - end mapper = Mapper.new(self) if default_scope mapper.with_default_scope(default_scope, &block) @@ -409,10 +405,6 @@ module ActionDispatch @prepend.each { |blk| eval_block(blk) } end - def dispatcher(defaults) - Routing::RouteSet::Dispatcher.new(defaults) - end - module MountedHelpers extend ActiveSupport::Concern include UrlFor @@ -508,7 +500,7 @@ module ActionDispatch routes.empty? end - def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) + def add_route(mapping, path_ast, name, anchor) raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) if name && named_routes[name] @@ -519,74 +511,32 @@ module ActionDispatch "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created" end - path = conditions.delete :path_info - ast = conditions.delete :parsed_path_info - required_defaults = conditions.delete :required_defaults - path = build_path(path, ast, requirements, anchor) - conditions = build_conditions(conditions) - - route = @set.add_route(app, path, conditions, required_defaults, defaults, name) + route = @set.add_route(name, mapping) named_routes[name] = route if name - route - end - - def build_path(path, ast, requirements, anchor) - strexp = Journey::Router::Strexp.new( - ast, - path, - requirements, - SEPARATORS, - anchor) - - pattern = Journey::Path::Pattern.new(strexp) - - builder = Journey::GTG::Builder.new pattern.spec - - # Get all the symbol nodes followed by literals that are not the - # dummy node. - symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n| - builder.followpos(n).first.literal? - } - - # Get all the symbol nodes preceded by literals. - symbols.concat pattern.spec.find_all(&:literal?).map { |n| - builder.followpos(n).first - }.find_all(&:symbol?) - - symbols.each { |x| - x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/ - } - - pattern - end - private :build_path - - def build_conditions(current_conditions) - conditions = current_conditions.dup - # Rack-Mount requires that :request_method be a regular expression. - # :request_method represents the HTTP verb that matches this route. - # - # Here we munge values before they get sent on to rack-mount. - verbs = conditions[:request_method] || [] - unless verbs.empty? - conditions[:request_method] = %r[^#{verbs.join('|')}$] + if route.segment_keys.include?(:controller) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using a dynamic :controller segment in a route is deprecated and + will be removed in Rails 5.1. + MSG end - conditions.keep_if do |k, _| - request_class.public_method_defined?(k) + if route.segment_keys.include?(:action) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using a dynamic :action segment in a route is deprecated and + will be removed in Rails 5.1. + MSG end + + route end - private :build_conditions class Generator PARAMETERIZE = lambda do |name, value| if name == :controller value - elsif value.is_a?(Array) - value.map(&:to_param).join('/') - elsif param = value.to_param - param + else + value.to_param end end @@ -594,16 +544,14 @@ module ActionDispatch def initialize(named_route, options, recall, set) @named_route = named_route - @options = options.dup - @recall = recall.dup + @options = options + @recall = recall @set = set - normalize_recall! normalize_options! normalize_controller_action_id! use_relative_controller! normalize_controller! - normalize_action! end def controller @@ -617,16 +565,11 @@ module ActionDispatch def use_recall_for(key) if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key]) if !named_route_exists? || segment_keys.include?(key) - @options[key] = @recall.delete(key) + @options[key] = @recall[key] end end end - # Set 'index' as default action for recall - def normalize_recall! - @recall[:action] ||= 'index' - end - def normalize_options! # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like @@ -638,12 +581,12 @@ module ActionDispatch # be "index", not the recalled action of "show". if options[:controller] - options[:action] ||= 'index' + options[:action] ||= "index" options[:controller] = options[:controller].to_s end if options.key?(:action) - options[:action] = (options[:action] || 'index').to_s + options[:action] = (options[:action] || "index").to_s end end @@ -653,8 +596,8 @@ module ActionDispatch # :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! - use_recall_for(:controller) or return - use_recall_for(:action) or return + use_recall_for(:controller) || return + use_recall_for(:action) || return use_recall_for(:id) end @@ -662,7 +605,7 @@ module ActionDispatch # is specified, the controller becomes "foo/baz/bat" def use_relative_controller! if !named_route && different_controller? && !controller.start_with?("/") - old_parts = current_controller.split('/') + old_parts = current_controller.split("/") size = controller.count("/") + 1 parts = old_parts[0...-size] << controller @options[:controller] = parts.join("/") @@ -671,13 +614,12 @@ module ActionDispatch # Remove leading slashes from controllers def normalize_controller! - @options[:controller] = controller.sub(%r{^/}, '') if controller - end - - # Move 'index' action from options to recall - def normalize_action! - if @options[:action] == 'index' - @recall[:action] = @options.delete(:action) + if controller + if controller.start_with?("/".freeze) + @options[:controller] = controller[1..-1] + else + @options[:controller] = controller + end end end @@ -704,11 +646,11 @@ module ActionDispatch # Generate the path indicated by the arguments, and return an array of # the keys that were not used to generate it. - def extra_keys(options, recall={}) + def extra_keys(options, recall = {}) generate_extras(options, recall).last end - def generate_extras(options, recall={}) + def generate_extras(options, recall = {}) route_key = options.delete :use_route path, params = generate(route_key, options, recall) return path, params.keys @@ -721,14 +663,18 @@ module ActionDispatch RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length, :trailing_slash, :anchor, :params, :only_path, :script_name, - :original_script_name] + :original_script_name, :relative_url_root] def optimize_routes_generation? default_url_options.empty? end def find_script_name(options) - options.delete(:script_name) || relative_url_root || '' + options.delete(:script_name) || find_relative_url_root(options) || "" + end + + def find_relative_url_root(options) + options.delete(:relative_url_root) || relative_url_root end def path_for(options, route_name = nil) @@ -746,7 +692,7 @@ module ActionDispatch password = options.delete :password end - recall = options.delete(:_recall) { {} } + recall = options.delete(:_recall) { {} } original_script_name = options.delete(:original_script_name) script_name = find_script_name options @@ -774,7 +720,7 @@ module ActionDispatch end def call(env) - req = request_class.new(env) + req = make_request(env) req.path_info = Journey::Router::Utils.normalize_path(req.path_info) @router.serve(req) end @@ -785,12 +731,12 @@ module ActionDispatch extras = environment[:extras] || {} begin - env = Rack::MockRequest.env_for(path, {:method => method}) + env = Rack::MockRequest.env_for(path, method: method) rescue URI::InvalidURIError => e raise ActionController::RoutingError, e.message end - req = request_class.new(env) + req = make_request(env) @router.recognize(req) do |route, params| params.merge!(extras) params.each do |key, value| @@ -803,14 +749,13 @@ module ActionDispatch req.path_parameters = old_params.merge params app = route.app if app.matches?(req) && app.dispatcher? - dispatcher = app.app - - if dispatcher.controller(params, false) - dispatcher.prepare_params!(params) - return params - else + begin + req.controller_class + rescue NameError raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller" end + + return req.path_parameters end end |