aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/routing/route_set.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/routing/route_set.rb')
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb315
1 files changed, 218 insertions, 97 deletions
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index ed7130b58e..9eff30fa53 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,12 +1,14 @@
-require 'action_dispatch/journey'
-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'
+# frozen_string_literal: true
+
+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/redefine_method"
+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
@@ -34,7 +36,7 @@ module ActionDispatch
if @raise_on_name_error
raise
else
- return [404, {'X-Cascade' => 'pass'}, []]
+ return [404, { "X-Cascade" => "pass" }, []]
end
end
@@ -59,7 +61,7 @@ module ActionDispatch
private
- def controller(_); @controller_class; end
+ def controller(_); @controller_class; end
end
# A NamedRouteCollection instance is a collection of named routes, and also
@@ -71,7 +73,7 @@ module ActionDispatch
private :routes
def initialize
- @routes = {}
+ @routes = {}
@path_helpers = Set.new
@url_helpers = Set.new
@url_helpers_module = Module.new
@@ -89,11 +91,11 @@ module ActionDispatch
def clear!
@path_helpers.each do |helper|
- @path_helpers_module.send :undef_method, helper
+ @path_helpers_module.send :remove_method, helper
end
@url_helpers.each do |helper|
- @url_helpers_module.send :undef_method, helper
+ @url_helpers_module.send :remove_method, helper
end
@routes.clear
@@ -144,6 +146,31 @@ module ActionDispatch
routes.length
end
+ # Given a +name+, defines name_path and name_url helpers.
+ # Used by 'direct', 'resolve', and 'polymorphic' route helpers.
+ def add_url_helper(name, defaults, &block)
+ helper = CustomUrlHelper.new(name, defaults, &block)
+ path_name = :"#{name}_path"
+ url_name = :"#{name}_url"
+
+ @path_helpers_module.module_eval do
+ define_method(path_name) do |*args|
+ helper.call(self, args, true)
+ end
+ end
+
+ @url_helpers_module.module_eval do
+ define_method(url_name) do |*args|
+ helper.call(self, args, false)
+ end
+ end
+
+ @path_helpers << path_name
+ @url_helpers << url_name
+
+ self
+ end
+
class UrlHelper
def self.create(route, options, route_name, url_strategy)
if optimize_helper?(route)
@@ -172,6 +199,16 @@ module ActionDispatch
if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
options = t.url_options.merge @options
options[:path] = optimized_helper(args)
+
+ original_script_name = options.delete(:original_script_name)
+ script_name = t._routes.find_script_name(options)
+
+ if original_script_name
+ script_name = original_script_name + script_name
+ end
+
+ options[:script_name] = script_name
+
url_strategy.call options
else
super
@@ -180,40 +217,40 @@ module ActionDispatch
private
- def optimized_helper(args)
- params = parameterize_args(args) do
- raise_generation_error(args)
- end
+ 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}".dup
+ 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)
@@ -248,6 +285,8 @@ module ActionDispatch
if args.size < path_params_size
path_params -= controller_options.keys
path_params -= result.keys
+ else
+ path_params = path_params.dup
end
inner_options.each_key do |key|
path_params.delete(key)
@@ -264,38 +303,35 @@ 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|
- 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
+ # 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
+ args.pop.to_h
+ end
+ helper.call self, args, options
+ end
end
end
- end
end
# strategy for building urls to send to the client
@@ -305,12 +341,12 @@ module ActionDispatch
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options
- attr_reader :env_key
+ attr_reader :env_key, :polymorphic_mappings
alias :routes :set
def self.default_resources_path_names
- { :new => 'new', :edit => 'edit' }
+ { new: "new", edit: "edit" }
end
def self.new_with_config(config)
@@ -347,6 +383,13 @@ module ActionDispatch
@set = Journey::Routes.new
@router = Journey::Router.new @set
@formatter = Journey::Formatter.new self
+ @polymorphic_mappings = {}
+ end
+
+ def eager_load!
+ router.eager_load!
+ routes.each(&:eager_load!)
+ nil
end
def relative_url_root
@@ -402,6 +445,7 @@ module ActionDispatch
named_routes.clear
set.clear
formatter.clear
+ @polymorphic_mappings.clear
@prepend.each { |blk| eval_block(blk) }
end
@@ -418,7 +462,7 @@ module ActionDispatch
MountedHelpers
end
- def define_mounted_helper(name)
+ def define_mounted_helper(name, script_namer = nil)
return if MountedHelpers.method_defined?(name)
routes = self
@@ -426,7 +470,7 @@ module ActionDispatch
MountedHelpers.class_eval do
define_method "_#{name}" do
- RoutesProxy.new(routes, _routes_context, helpers)
+ RoutesProxy.new(routes, _routes_context, helpers, script_namer)
end
end
@@ -446,17 +490,50 @@ module ActionDispatch
# Define url_for in the singleton level so one can do:
# Rails.application.routes.url_helpers.url_for(args)
- @_routes = routes
+ proxy_class = Class.new do
+ include UrlFor
+ include routes.named_routes.path_helpers_module
+ include routes.named_routes.url_helpers_module
+
+ attr_reader :_routes
+
+ def initialize(routes)
+ @_routes = routes
+ end
+
+ def optimize_routes_generation?
+ @_routes.optimize_routes_generation?
+ end
+ end
+
+ @_proxy = proxy_class.new(routes)
+
class << self
def url_for(options)
- @_routes.url_for(options)
+ @_proxy.url_for(options)
+ end
+
+ def full_url_for(options)
+ @_proxy.full_url_for(options)
+ end
+
+ def route_for(name, *args)
+ @_proxy.route_for(name, *args)
end
def optimize_routes_generation?
- @_routes.optimize_routes_generation?
+ @_proxy.optimize_routes_generation?
end
- attr_reader :_routes
+ def polymorphic_url(record_or_hash_or_array, options = {})
+ @_proxy.polymorphic_url(record_or_hash_or_array, options)
+ end
+
+ def polymorphic_path(record_or_hash_or_array, options = {})
+ @_proxy.polymorphic_path(record_or_hash_or_array, options)
+ end
+
+ def _routes; @_proxy._routes; end
def url_options; {}; end
end
@@ -480,7 +557,7 @@ module ActionDispatch
# plus a singleton class method called _routes ...
included do
- singleton_class.send(:redefine_method, :_routes) { routes }
+ redefine_singleton_method(:_routes) { routes }
end
# And an instance method _routes. Note that
@@ -500,7 +577,7 @@ module ActionDispatch
routes.empty?
end
- def add_route(mapping, path_ast, name, anchor)
+ def add_route(mapping, name)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
if name && named_routes[name]
@@ -517,20 +594,58 @@ module ActionDispatch
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.
+ will be removed in Rails 6.0.
MSG
end
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.
+ will be removed in Rails 6.0.
MSG
end
route
end
+ def add_polymorphic_mapping(klass, options, &block)
+ @polymorphic_mappings[klass] = CustomUrlHelper.new(klass, options, &block)
+ end
+
+ def add_url_helper(name, options, &block)
+ named_routes.add_url_helper(name, options, &block)
+ end
+
+ class CustomUrlHelper
+ attr_reader :name, :defaults, :block
+
+ def initialize(name, defaults, &block)
+ @name = name
+ @defaults = defaults
+ @block = block
+ end
+
+ def call(t, args, only_path = false)
+ options = args.extract_options!
+ url = t.full_url_for(eval_block(t, args, options))
+
+ if only_path
+ "/" + url.partition(%r{(?<!/)/(?!/)}).last
+ else
+ url
+ end
+ end
+
+ private
+ def eval_block(t, args, options)
+ t.instance_exec(*args, merge_defaults(options), &block)
+ end
+
+ def merge_defaults(options)
+ defaults ? defaults.merge(options) : options
+ end
+ end
+
class Generator
PARAMETERIZE = lambda do |name, value|
if name == :controller
@@ -581,12 +696,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
@@ -596,8 +711,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
@@ -605,7 +720,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("/")
@@ -646,11 +761,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
@@ -670,7 +785,7 @@ module ActionDispatch
end
def find_script_name(options)
- options.delete(:script_name) || find_relative_url_root(options) || ''
+ options.delete(:script_name) || find_relative_url_root(options) || ""
end
def find_relative_url_root(options)
@@ -692,7 +807,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
@@ -731,12 +846,16 @@ 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 = make_request(env)
+ recognize_path_with_request(req, path, extras)
+ end
+
+ def recognize_path_with_request(req, path, extras)
@router.recognize(req) do |route, params|
params.merge!(extras)
params.each do |key, value|
@@ -745,8 +864,7 @@ module ActionDispatch
params[key] = URI.parser.unescape(value)
end
end
- old_params = req.path_parameters
- req.path_parameters = old_params.merge params
+ req.path_parameters = params
app = route.app
if app.matches?(req) && app.dispatcher?
begin
@@ -756,6 +874,9 @@ module ActionDispatch
end
return req.path_parameters
+ elsif app.matches?(req) && app.engine?
+ path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras)
+ return path_parameters
end
end