mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Group together all the old routing dsl logic
This commit is contained in:
parent
35576a237e
commit
df68cae0c0
6 changed files with 204 additions and 216 deletions
|
@ -26,7 +26,6 @@ module ActionController
|
|||
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
|
||||
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
|
||||
autoload :RecordIdentifier, 'action_controller/record_identifier'
|
||||
autoload :Resources, 'action_controller/deprecated'
|
||||
autoload :Routing, 'action_controller/deprecated'
|
||||
autoload :SessionManagement, 'action_controller/metal/session_management'
|
||||
autoload :TestCase, 'action_controller/testing/test_case'
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
|
||||
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
|
||||
ActionController::Routing = ActionDispatch::Routing
|
||||
ActionDispatch::Resources = ActionDispatch::Routing::Resources
|
||||
ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new
|
||||
|
|
|
@ -260,7 +260,7 @@ module ActionDispatch
|
|||
# Run <tt>rake routes</tt>.
|
||||
#
|
||||
module Routing
|
||||
autoload :Resources, 'action_dispatch/routing/resources'
|
||||
autoload :Mapper, 'action_dispatch/routing/mapper'
|
||||
autoload :RouteSet, 'action_dispatch/routing/route_set'
|
||||
|
||||
SEPARATORS = %w( / . ? )
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
require 'active_support/core_ext/hash/slice'
|
||||
require 'active_support/core_ext/object/try'
|
||||
|
||||
module ActionDispatch
|
||||
module Routing
|
||||
# Mapper instances are used to build routes. The object passed to the draw
|
||||
# block in config/routes.rb is a Mapper instance.
|
||||
#
|
||||
# Mapper instances have relatively few instance methods, in order to avoid
|
||||
# clashes with named routes.
|
||||
#
|
||||
# == Overview
|
||||
#
|
||||
# ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
|
||||
|
@ -45,7 +48,196 @@ module ActionDispatch
|
|||
# supplying you with methods to create them in your routes.rb file.
|
||||
#
|
||||
# Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
module Resources
|
||||
class Mapper #:doc:
|
||||
def initialize(set) #:nodoc:
|
||||
@set = set
|
||||
end
|
||||
|
||||
# Create an unnamed route with the provided +path+ and +options+. See
|
||||
# ActionDispatch::Routing for an introduction to routes.
|
||||
def connect(path, options = {})
|
||||
options = options.dup
|
||||
|
||||
if conditions = options.delete(:conditions)
|
||||
conditions = conditions.dup
|
||||
method = [conditions.delete(:method)].flatten.compact
|
||||
method.map! { |m|
|
||||
m = m.to_s.upcase
|
||||
|
||||
if m == "HEAD"
|
||||
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
|
||||
end
|
||||
|
||||
unless HTTP_METHODS.include?(m.downcase.to_sym)
|
||||
raise ArgumentError, "Invalid HTTP method specified in route conditions"
|
||||
end
|
||||
|
||||
m
|
||||
}
|
||||
|
||||
if method.length > 1
|
||||
method = Regexp.union(*method)
|
||||
elsif method.length == 1
|
||||
method = method.first
|
||||
else
|
||||
method = nil
|
||||
end
|
||||
end
|
||||
|
||||
path_prefix = options.delete(:path_prefix)
|
||||
name_prefix = options.delete(:name_prefix)
|
||||
namespace = options.delete(:namespace)
|
||||
|
||||
name = options.delete(:_name)
|
||||
name = "#{name_prefix}#{name}" if name_prefix
|
||||
|
||||
requirements = options.delete(:requirements) || {}
|
||||
defaults = options.delete(:defaults) || {}
|
||||
options.each do |k, v|
|
||||
if v.is_a?(Regexp)
|
||||
if value = options.delete(k)
|
||||
requirements[k.to_sym] = value
|
||||
end
|
||||
else
|
||||
value = options.delete(k)
|
||||
defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
|
||||
end
|
||||
end
|
||||
|
||||
requirements.each do |_, requirement|
|
||||
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
||||
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
||||
end
|
||||
if requirement.multiline?
|
||||
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
|
||||
requirements[:controller] ||= Regexp.union(*possible_names)
|
||||
|
||||
if defaults[:controller]
|
||||
defaults[:action] ||= 'index'
|
||||
defaults[:controller] = defaults[:controller].to_s
|
||||
defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
|
||||
end
|
||||
|
||||
if defaults[:action]
|
||||
defaults[:action] = defaults[:action].to_s
|
||||
end
|
||||
|
||||
if path.is_a?(String)
|
||||
path = "#{path_prefix}/#{path}" if path_prefix
|
||||
path = path.gsub('.:format', '(.:format)')
|
||||
path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
|
||||
glob = $1.to_sym if path =~ /\/\*(\w+)$/
|
||||
path = ::Rack::Mount::Utils.normalize_path(path)
|
||||
path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
|
||||
|
||||
if glob && !defaults[glob].blank?
|
||||
raise ActionController::RoutingError, "paths cannot have non-empty default values"
|
||||
end
|
||||
end
|
||||
|
||||
app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob)
|
||||
|
||||
conditions = {}
|
||||
conditions[:request_method] = method if method
|
||||
conditions[:path_info] = path if path
|
||||
|
||||
@set.add_route(app, conditions, defaults, name)
|
||||
end
|
||||
|
||||
def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
|
||||
path = (path =~ /^\//) ? path.dup : "/#{path}"
|
||||
optional, segments = true, []
|
||||
|
||||
required_segments = requirements.keys
|
||||
required_segments -= defaults.keys.compact
|
||||
|
||||
old_segments = path.split('/')
|
||||
old_segments.shift
|
||||
length = old_segments.length
|
||||
|
||||
old_segments.reverse.each_with_index do |segment, index|
|
||||
required_segments.each do |required|
|
||||
if segment =~ /#{required}/
|
||||
optional = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if optional
|
||||
if segment == ":id" && segments.include?(":action")
|
||||
optional = false
|
||||
elsif segment == ":controller" || segment == ":action" || segment == ":id"
|
||||
# Ignore
|
||||
elsif !(segment =~ /^:\w+$/) &&
|
||||
!(segment =~ /^:\w+\(\.:format\)$/)
|
||||
optional = false
|
||||
elsif segment =~ /^:(\w+)$/
|
||||
if defaults.has_key?($1.to_sym)
|
||||
defaults.delete($1.to_sym)
|
||||
else
|
||||
optional = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if optional && index < length - 1
|
||||
segments.unshift('(/', segment)
|
||||
segments.push(')')
|
||||
elsif optional
|
||||
segments.unshift('/(', segment)
|
||||
segments.push(')')
|
||||
else
|
||||
segments.unshift('/', segment)
|
||||
end
|
||||
end
|
||||
|
||||
segments.join
|
||||
end
|
||||
private :optionalize_trailing_dynamic_segments
|
||||
|
||||
# Creates a named route called "root" for matching the root level request.
|
||||
def root(options = {})
|
||||
if options.is_a?(Symbol)
|
||||
if source_route = @set.named_routes.routes[options]
|
||||
options = source_route.defaults.merge({ :conditions => source_route.conditions })
|
||||
end
|
||||
end
|
||||
named_route("root", '', options)
|
||||
end
|
||||
|
||||
def named_route(name, path, options = {}) #:nodoc:
|
||||
options[:_name] = name
|
||||
connect(path, options)
|
||||
end
|
||||
|
||||
# Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
|
||||
# Example:
|
||||
#
|
||||
# map.namespace(:admin) do |admin|
|
||||
# admin.resources :products,
|
||||
# :has_many => [ :tags, :images, :variants ]
|
||||
# end
|
||||
#
|
||||
# This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
|
||||
# It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
|
||||
# Admin::TagsController.
|
||||
def namespace(name, options = {}, &block)
|
||||
if options[:namespace]
|
||||
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
|
||||
else
|
||||
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(route_name, *args, &proc) #:nodoc:
|
||||
super unless args.length >= 1 && proc.nil?
|
||||
named_route(route_name, *args)
|
||||
end
|
||||
|
||||
INHERITABLE_OPTIONS = :namespace, :shallow
|
||||
|
||||
class Resource #:nodoc:
|
|
@ -53,63 +53,6 @@ module ActionDispatch
|
|||
end
|
||||
end
|
||||
|
||||
# Mapper instances are used to build routes. The object passed to the draw
|
||||
# block in config/routes.rb is a Mapper instance.
|
||||
#
|
||||
# Mapper instances have relatively few instance methods, in order to avoid
|
||||
# clashes with named routes.
|
||||
class Mapper #:doc:
|
||||
include Routing::Resources
|
||||
|
||||
def initialize(set) #:nodoc:
|
||||
@set = set
|
||||
end
|
||||
|
||||
# Create an unnamed route with the provided +path+ and +options+. See
|
||||
# ActionDispatch::Routing for an introduction to routes.
|
||||
def connect(path, options = {})
|
||||
@set.add_route(path, options)
|
||||
end
|
||||
|
||||
# Creates a named route called "root" for matching the root level request.
|
||||
def root(options = {})
|
||||
if options.is_a?(Symbol)
|
||||
if source_route = @set.named_routes.routes[options]
|
||||
options = source_route.defaults.merge({ :conditions => source_route.conditions })
|
||||
end
|
||||
end
|
||||
named_route("root", '', options)
|
||||
end
|
||||
|
||||
def named_route(name, path, options = {}) #:nodoc:
|
||||
@set.add_named_route(name, path, options)
|
||||
end
|
||||
|
||||
# Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
|
||||
# Example:
|
||||
#
|
||||
# map.namespace(:admin) do |admin|
|
||||
# admin.resources :products,
|
||||
# :has_many => [ :tags, :images, :variants ]
|
||||
# end
|
||||
#
|
||||
# This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
|
||||
# It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
|
||||
# Admin::TagsController.
|
||||
def namespace(name, options = {}, &block)
|
||||
if options[:namespace]
|
||||
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
|
||||
else
|
||||
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(route_name, *args, &proc) #:nodoc:
|
||||
super unless args.length >= 1 && proc.nil?
|
||||
@set.add_named_route(route_name, *args)
|
||||
end
|
||||
end
|
||||
|
||||
# A NamedRouteCollection instance is a collection of named routes, and also
|
||||
# maintains an anonymous module that can be used to install helpers for the
|
||||
# named routes.
|
||||
|
@ -347,109 +290,14 @@ module ActionDispatch
|
|||
routes_changed_at
|
||||
end
|
||||
|
||||
def add_route(path, options = {})
|
||||
options = options.dup
|
||||
|
||||
if conditions = options.delete(:conditions)
|
||||
conditions = conditions.dup
|
||||
method = [conditions.delete(:method)].flatten.compact
|
||||
method.map! { |m|
|
||||
m = m.to_s.upcase
|
||||
|
||||
if m == "HEAD"
|
||||
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
|
||||
end
|
||||
|
||||
unless HTTP_METHODS.include?(m.downcase.to_sym)
|
||||
raise ArgumentError, "Invalid HTTP method specified in route conditions"
|
||||
end
|
||||
|
||||
m
|
||||
}
|
||||
|
||||
if method.length > 1
|
||||
method = Regexp.union(*method)
|
||||
elsif method.length == 1
|
||||
method = method.first
|
||||
else
|
||||
method = nil
|
||||
end
|
||||
end
|
||||
|
||||
path_prefix = options.delete(:path_prefix)
|
||||
name_prefix = options.delete(:name_prefix)
|
||||
namespace = options.delete(:namespace)
|
||||
|
||||
name = options.delete(:_name)
|
||||
name = "#{name_prefix}#{name}" if name_prefix
|
||||
|
||||
requirements = options.delete(:requirements) || {}
|
||||
defaults = options.delete(:defaults) || {}
|
||||
options.each do |k, v|
|
||||
if v.is_a?(Regexp)
|
||||
if value = options.delete(k)
|
||||
requirements[k.to_sym] = value
|
||||
end
|
||||
else
|
||||
value = options.delete(k)
|
||||
defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
|
||||
end
|
||||
end
|
||||
|
||||
requirements.each do |_, requirement|
|
||||
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
||||
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
||||
end
|
||||
if requirement.multiline?
|
||||
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
|
||||
requirements[:controller] ||= Regexp.union(*possible_names)
|
||||
|
||||
if defaults[:controller]
|
||||
defaults[:action] ||= 'index'
|
||||
defaults[:controller] = defaults[:controller].to_s
|
||||
defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
|
||||
end
|
||||
|
||||
if defaults[:action]
|
||||
defaults[:action] = defaults[:action].to_s
|
||||
end
|
||||
|
||||
if path.is_a?(String)
|
||||
path = "#{path_prefix}/#{path}" if path_prefix
|
||||
path = path.gsub('.:format', '(.:format)')
|
||||
path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
|
||||
glob = $1.to_sym if path =~ /\/\*(\w+)$/
|
||||
path = ::Rack::Mount::Utils.normalize_path(path)
|
||||
path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
|
||||
|
||||
if glob && !defaults[glob].blank?
|
||||
raise ActionController::RoutingError, "paths cannot have non-empty default values"
|
||||
end
|
||||
end
|
||||
|
||||
app = Dispatcher.new(:defaults => defaults, :glob => glob)
|
||||
|
||||
conditions = {}
|
||||
conditions[:request_method] = method if method
|
||||
conditions[:path_info] = path if path
|
||||
|
||||
def add_route(app, conditions = {}, defaults = {}, name = nil)
|
||||
route = @set.add_route(app, conditions, defaults, name)
|
||||
route.extend(RouteExtensions)
|
||||
named_routes[name] = route if name
|
||||
routes << route
|
||||
route
|
||||
end
|
||||
|
||||
def add_named_route(name, path, options = {})
|
||||
options[:_name] = name
|
||||
route = add_route(path, options)
|
||||
named_routes[route.name] = route
|
||||
route
|
||||
end
|
||||
|
||||
def options_as_params(options)
|
||||
# If an explicit :controller was given, always make :action explicit
|
||||
# too, so that action expiry works as expected for things like
|
||||
|
@ -644,56 +492,6 @@ module ActionDispatch
|
|||
_escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def optionalize_trailing_dynamic_segments(path, requirements, defaults)
|
||||
path = (path =~ /^\//) ? path.dup : "/#{path}"
|
||||
optional, segments = true, []
|
||||
|
||||
required_segments = requirements.keys
|
||||
required_segments -= defaults.keys.compact
|
||||
|
||||
old_segments = path.split('/')
|
||||
old_segments.shift
|
||||
length = old_segments.length
|
||||
|
||||
old_segments.reverse.each_with_index do |segment, index|
|
||||
required_segments.each do |required|
|
||||
if segment =~ /#{required}/
|
||||
optional = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if optional
|
||||
if segment == ":id" && segments.include?(":action")
|
||||
optional = false
|
||||
elsif segment == ":controller" || segment == ":action" || segment == ":id"
|
||||
# Ignore
|
||||
elsif !(segment =~ /^:\w+$/) &&
|
||||
!(segment =~ /^:\w+\(\.:format\)$/)
|
||||
optional = false
|
||||
elsif segment =~ /^:(\w+)$/
|
||||
if defaults.has_key?($1.to_sym)
|
||||
defaults.delete($1.to_sym)
|
||||
else
|
||||
optional = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if optional && index < length - 1
|
||||
segments.unshift('(/', segment)
|
||||
segments.push(')')
|
||||
elsif optional
|
||||
segments.unshift('/(', segment)
|
||||
segments.push(')')
|
||||
else
|
||||
segments.unshift('/', segment)
|
||||
end
|
||||
end
|
||||
|
||||
segments.join
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ class ResourcesTest < ActionController::TestCase
|
|||
end
|
||||
|
||||
def test_should_arrange_actions
|
||||
resource = ActionDispatch::Routing::Resources::Resource.new(:messages,
|
||||
resource = ActionDispatch::Routing::Mapper::Resource.new(:messages,
|
||||
:collection => { :rss => :get, :reorder => :post, :csv => :post },
|
||||
:member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post },
|
||||
:new => { :preview => :get, :draft => :get })
|
||||
|
@ -54,18 +54,18 @@ class ResourcesTest < ActionController::TestCase
|
|||
end
|
||||
|
||||
def test_should_resource_controller_name_equal_resource_name_by_default
|
||||
resource = ActionDispatch::Routing::Resources::Resource.new(:messages, {})
|
||||
resource = ActionDispatch::Routing::Mapper::Resource.new(:messages, {})
|
||||
assert_equal 'messages', resource.controller
|
||||
end
|
||||
|
||||
def test_should_resource_controller_name_equal_controller_option
|
||||
resource = ActionDispatch::Routing::Resources::Resource.new(:messages, :controller => 'posts')
|
||||
resource = ActionDispatch::Routing::Mapper::Resource.new(:messages, :controller => 'posts')
|
||||
assert_equal 'posts', resource.controller
|
||||
end
|
||||
|
||||
def test_should_all_singleton_paths_be_the_same
|
||||
[ :path, :nesting_path_prefix, :member_path ].each do |method|
|
||||
resource = ActionDispatch::Routing::Resources::SingletonResource.new(:messages, :path_prefix => 'admin')
|
||||
resource = ActionDispatch::Routing::Mapper::SingletonResource.new(:messages, :path_prefix => 'admin')
|
||||
assert_equal 'admin/messages', resource.send(method)
|
||||
end
|
||||
end
|
||||
|
@ -121,7 +121,7 @@ class ResourcesTest < ActionController::TestCase
|
|||
end
|
||||
|
||||
def test_override_paths_for_default_restful_actions
|
||||
resource = ActionDispatch::Routing::Resources::Resource.new(:messages,
|
||||
resource = ActionDispatch::Routing::Mapper::Resource.new(:messages,
|
||||
:path_names => {:new => 'nuevo', :edit => 'editar'})
|
||||
assert_equal resource.new_path, "#{resource.path}/nuevo"
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue