1
0
Fork 0
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:
Joshua Peek 2009-10-20 10:46:27 -05:00
parent 35576a237e
commit df68cae0c0
6 changed files with 204 additions and 216 deletions

View file

@ -26,7 +26,6 @@ module ActionController
autoload :PerformanceTest, 'action_controller/deprecated/performance_test' autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier' autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Resources, 'action_controller/deprecated'
autoload :Routing, 'action_controller/deprecated' autoload :Routing, 'action_controller/deprecated'
autoload :SessionManagement, 'action_controller/metal/session_management' autoload :SessionManagement, 'action_controller/metal/session_management'
autoload :TestCase, 'action_controller/testing/test_case' autoload :TestCase, 'action_controller/testing/test_case'

View file

@ -1,5 +1,4 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
ActionController::Routing = ActionDispatch::Routing ActionController::Routing = ActionDispatch::Routing
ActionDispatch::Resources = ActionDispatch::Routing::Resources
ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new

View file

@ -260,7 +260,7 @@ module ActionDispatch
# Run <tt>rake routes</tt>. # Run <tt>rake routes</tt>.
# #
module Routing module Routing
autoload :Resources, 'action_dispatch/routing/resources' autoload :Mapper, 'action_dispatch/routing/mapper'
autoload :RouteSet, 'action_dispatch/routing/route_set' autoload :RouteSet, 'action_dispatch/routing/route_set'
SEPARATORS = %w( / . ? ) SEPARATORS = %w( / . ? )

View file

@ -1,8 +1,11 @@
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/try'
module ActionDispatch module ActionDispatch
module Routing 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 # == Overview
# #
# ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms, # 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. # 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 # 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 INHERITABLE_OPTIONS = :namespace, :shallow
class Resource #:nodoc: class Resource #:nodoc:

View file

@ -53,63 +53,6 @@ module ActionDispatch
end end
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 # A NamedRouteCollection instance is a collection of named routes, and also
# maintains an anonymous module that can be used to install helpers for the # maintains an anonymous module that can be used to install helpers for the
# named routes. # named routes.
@ -347,109 +290,14 @@ module ActionDispatch
routes_changed_at routes_changed_at
end end
def add_route(path, options = {}) def add_route(app, conditions = {}, defaults = {}, name = nil)
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
route = @set.add_route(app, conditions, defaults, name) route = @set.add_route(app, conditions, defaults, name)
route.extend(RouteExtensions) route.extend(RouteExtensions)
named_routes[name] = route if name
routes << route routes << route
route route
end 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) def options_as_params(options)
# If an explicit :controller was given, always make :action explicit # If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like # 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 _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
end end
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 end
end end

View file

@ -41,7 +41,7 @@ class ResourcesTest < ActionController::TestCase
end end
def test_should_arrange_actions 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 }, :collection => { :rss => :get, :reorder => :post, :csv => :post },
:member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post }, :member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post },
:new => { :preview => :get, :draft => :get }) :new => { :preview => :get, :draft => :get })
@ -54,18 +54,18 @@ class ResourcesTest < ActionController::TestCase
end end
def test_should_resource_controller_name_equal_resource_name_by_default 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 assert_equal 'messages', resource.controller
end end
def test_should_resource_controller_name_equal_controller_option 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 assert_equal 'posts', resource.controller
end end
def test_should_all_singleton_paths_be_the_same def test_should_all_singleton_paths_be_the_same
[ :path, :nesting_path_prefix, :member_path ].each do |method| [ :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) assert_equal 'admin/messages', resource.send(method)
end end
end end
@ -121,7 +121,7 @@ class ResourcesTest < ActionController::TestCase
end end
def test_override_paths_for_default_restful_actions 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'}) :path_names => {:new => 'nuevo', :edit => 'editar'})
assert_equal resource.new_path, "#{resource.path}/nuevo" assert_equal resource.new_path, "#{resource.path}/nuevo"
end end