aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/routing
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/routing')
-rw-r--r--actionpack/lib/action_dispatch/routing/deprecated_mapper.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb565
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb201
3 files changed, 437 insertions, 333 deletions
diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
index dd76391870..8ce6b2f6d5 100644
--- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
@@ -113,7 +113,7 @@ module ActionDispatch
end
end
- requirements[:controller] ||= Routing.controller_constraints
+ requirements[:controller] ||= @set.controller_constraints
if defaults[:controller]
defaults[:action] ||= 'index'
@@ -175,7 +175,7 @@ module ActionDispatch
optional = false
elsif segment =~ /^:(\w+)$/
if defaults.has_key?($1.to_sym)
- defaults.delete($1.to_sym)
+ defaults.delete($1.to_sym) if defaults[$1.to_sym].nil?
else
optional = false
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index cfe7425a61..d480af876d 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,153 +1,192 @@
module ActionDispatch
module Routing
class Mapper
- module Resources
- def resource(*resources, &block)
- options = resources.last.is_a?(Hash) ? resources.pop : {}
-
- if resources.length > 1
- raise ArgumentError if block_given?
- resources.each { |r| resource(r, options) }
- return self
+ class Constraints
+ def self.new(app, constraints = [])
+ if constraints.any?
+ super(app, constraints)
+ else
+ app
end
+ end
- resource = resources.pop
+ def initialize(app, constraints = [])
+ @app, @constraints = app, constraints
+ end
- if @scope[:scope_level] == :resources
- member do
- resource(resource, options, &block)
- end
- return self
- end
+ def call(env)
+ req = Rack::Request.new(env)
- singular = resource.to_s
- plural = singular.pluralize
+ @constraints.each { |constraint|
+ if constraint.respond_to?(:matches?) && !constraint.matches?(req)
+ return [417, {}, []]
+ elsif constraint.respond_to?(:call) && !constraint.call(req)
+ return [417, {}, []]
+ end
+ }
- controller(plural) do
- namespace(resource) do
- with_scope_level(:resource) do
- yield if block_given?
+ @app.call(env)
+ end
+ end
- get "", :to => :show, :as => "#{singular}"
- post "", :to => :create
- put "", :to => :update
- delete "", :to => :destroy
- get "new", :to => :new, :as => "new_#{singular}"
- get "edit", :to => :edit, :as => "edit_#{singular}"
- end
- end
- end
+ module Base
+ def initialize(set)
+ @set = set
+ end
- self
+ def root(options = {})
+ match '/', options.merge(:as => :root)
end
- def resources(*resources, &block)
- options = resources.last.is_a?(Hash) ? resources.pop : {}
+ def match(*args)
+ options = args.extract_options!
- if resources.length > 1
- raise ArgumentError if block_given?
- resources.each { |r| resources(r, options) }
- return self
- end
+ path = args.first
- resource = resources.pop
+ conditions, defaults = {}, {}
- plural = resource.to_s
- singular = plural.singularize
+ path = nil if path == ""
+ path = "#{@scope[:path]}#{path}" if @scope[:path]
+ path = Rack::Mount::Utils.normalize_path(path) if path
- if @scope[:scope_level] == :resources
- parent_resource = @scope[:scope_level_options][:name]
- with_scope_level(:member) do
- scope(":#{parent_resource}_id", :name_prefix => parent_resource) do
- resources(resource, options, &block)
- end
- end
- return self
- end
+ raise ArgumentError, "path is required" unless path
- if @scope[:options] && (prefix = @scope[:options][:name_prefix])
- plural = "#{prefix}_#{plural}"
- singular = "#{prefix}_#{singular}"
+ constraints = options[:constraints] || {}
+ unless constraints.is_a?(Hash)
+ block, constraints = constraints, {}
end
+ blocks = ((@scope[:blocks] || []) + [block]).compact
+ constraints = (@scope[:constraints] || {}).merge(constraints)
+ options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) }
- controller(resource) do
- namespace(resource) do
- with_scope_level(:resources, :name => singular) do
- yield if block_given?
+ conditions[:path_info] = path
+ requirements = constraints.dup
- member do
- get "", :to => :show, :as => singular
- put "", :to => :update
- delete "", :to => :destroy
- get "edit", :to => :edit, :as => "edit_#{singular}"
- end
+ path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS)
+ segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names
+ constraints.reject! { |k, v| segment_keys.include?(k.to_s) }
+ conditions.merge!(constraints)
- collection do
- get "", :to => :index, :as => plural
- post "", :to => :create
- get "new", :to => :new, :as => "new_#{singular}"
- end
- end
- end
+ requirements[:controller] ||= @set.controller_constraints
+
+ if via = options[:via]
+ via = Array(via).map { |m| m.to_s.upcase }
+ conditions[:request_method] = Regexp.union(*via)
end
+ defaults[:controller] ||= @scope[:controller].to_s if @scope[:controller]
+
+ app = initialize_app_endpoint(options, defaults)
+ validate_defaults!(app, defaults, segment_keys)
+ app = Constraints.new(app, blocks)
+
+ @set.add_route(app, conditions, requirements, defaults, options[:as])
+
self
end
- def collection
- unless @scope[:scope_level] == :resources
- raise ArgumentError, "can't use collection outside resources scope"
- end
+ private
+ def initialize_app_endpoint(options, defaults)
+ app = nil
+
+ if options[:to].respond_to?(:call)
+ app = options[:to]
+ defaults.delete(:controller)
+ defaults.delete(:action)
+ elsif options[:to].is_a?(String)
+ defaults[:controller], defaults[:action] = options[:to].split('#')
+ elsif options[:to].is_a?(Symbol)
+ defaults[:action] = options[:to].to_s
+ end
- with_scope_level(:collection) do
- yield
+ app || Routing::RouteSet::Dispatcher.new(:defaults => defaults)
end
- end
- def member
- unless @scope[:scope_level] == :resources
- raise ArgumentError, "can't use member outside resources scope"
- end
+ def validate_defaults!(app, defaults, segment_keys)
+ return unless app.is_a?(Routing::RouteSet::Dispatcher)
- with_scope_level(:member) do
- scope(":id") do
- yield
+ unless defaults.include?(:controller) || segment_keys.include?("controller")
+ raise ArgumentError, "missing :controller"
+ end
+
+ unless defaults.include?(:action) || segment_keys.include?("action")
+ raise ArgumentError, "missing :action"
end
end
+ end
+
+ module HttpHelpers
+ def get(*args, &block)
+ map_method(:get, *args, &block)
end
- def match(*args)
- options = args.last.is_a?(Hash) ? args.pop : {}
- args.push(options)
+ def post(*args, &block)
+ map_method(:post, *args, &block)
+ end
- case options.delete(:on)
- when :collection
- return collection { match(*args) }
- when :member
- return member { match(*args) }
- end
+ def put(*args, &block)
+ map_method(:put, *args, &block)
+ end
- if @scope[:scope_level] == :resources
- raise ArgumentError, "can't define route directly in resources scope"
- end
+ def delete(*args, &block)
+ map_method(:delete, *args, &block)
+ end
- super
+ def redirect(path, options = {})
+ status = options[:status] || 301
+ lambda { |env|
+ req = Rack::Request.new(env)
+ url = req.scheme + '://' + req.host + path
+ [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']]
+ }
end
private
- def with_scope_level(kind, options = {})
- old, @scope[:scope_level] = @scope[:scope_level], kind
- old_options, @scope[:scope_level_options] = @scope[:scope_level_options], options
- yield
- ensure
- @scope[:scope_level] = old
- @scope[:scope_level_options] = old_options
+ def map_method(method, *args, &block)
+ options = args.extract_options!
+ options[:via] = method
+ args.push(options)
+ match(*args, &block)
+ self
end
end
module Scoping
+ def initialize(*args)
+ @scope = {}
+ super
+ end
+
def scope(*args)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ options = args.extract_options!
+
+ case args.first
+ when String
+ options[:path] = args.first
+ when Symbol
+ options[:controller] = args.first
+ end
+
+ if path = options.delete(:path)
+ path_set = true
+ path, @scope[:path] = @scope[:path], Rack::Mount::Utils.normalize_path(@scope[:path].to_s + path.to_s)
+ else
+ path_set = false
+ end
+
+ if name_prefix = options.delete(:name_prefix)
+ name_prefix_set = true
+ name_prefix, @scope[:name_prefix] = @scope[:name_prefix], (@scope[:name_prefix] ? "#{@scope[:name_prefix]}_#{name_prefix}" : name_prefix)
+ else
+ name_prefix_set = false
+ end
+
+ if controller = options.delete(:controller)
+ controller_set = true
+ controller, @scope[:controller] = @scope[:controller], controller
+ else
+ controller_set = false
+ end
constraints = options.delete(:constraints) || {}
unless constraints.is_a?(Hash)
@@ -158,24 +197,12 @@ module ActionDispatch
options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options)
- path_set = controller_set = false
-
- case args.first
- when String
- path_set = true
- path = args.first
- path, @scope[:path] = @scope[:path], "#{@scope[:path]}#{Rack::Mount::Utils.normalize_path(path)}"
- when Symbol
- controller_set = true
- controller = args.first
- controller, @scope[:controller] = @scope[:controller], controller
- end
-
yield
self
ensure
@scope[:path] = path if path_set
+ @scope[:name_prefix] = name_prefix if name_prefix_set
@scope[:controller] = controller if controller_set
@scope[:options] = options
@scope[:blocks] = blocks
@@ -187,153 +214,253 @@ module ActionDispatch
end
def namespace(path)
- scope(path.to_s) { yield }
+ scope("/#{path}") { yield }
end
def constraints(constraints = {})
scope(:constraints => constraints) { yield }
end
- end
- class Constraints
- def initialize(app, constraints = [])
- @app, @constraints = app, constraints
- end
+ def match(*args)
+ options = args.extract_options!
- def call(env)
- req = Rack::Request.new(env)
+ options = (@scope[:options] || {}).merge(options)
- @constraints.each { |constraint|
- if constraint.respond_to?(:matches?) && !constraint.matches?(req)
- return [417, {}, []]
- elsif constraint.respond_to?(:call) && !constraint.call(req)
- return [417, {}, []]
- end
- }
+ if @scope[:name_prefix] && !options[:as].blank?
+ options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}"
+ elsif @scope[:name_prefix] && options[:as] == ""
+ options[:as] = @scope[:name_prefix].to_s
+ end
- @app.call(env)
+ args.push(options)
+ super(*args)
end
end
- def initialize(set)
- @set = set
- @scope = {}
-
- extend Scoping
- extend Resources
- end
+ module Resources
+ class Resource #:nodoc:
+ attr_reader :plural, :singular
- def get(*args, &block)
- map_method(:get, *args, &block)
- end
+ def initialize(entities, options = {})
+ entities = entities.to_s
- def post(*args, &block)
- map_method(:post, *args, &block)
- end
+ @plural = entities.pluralize
+ @singular = entities.singularize
+ end
- def put(*args, &block)
- map_method(:put, *args, &block)
- end
+ def name
+ plural
+ end
- def delete(*args, &block)
- map_method(:delete, *args, &block)
- end
+ def controller
+ plural
+ end
- def root(options = {})
- match '/', options.merge(:as => :root)
- end
+ def member_name
+ singular
+ end
- def match(*args)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ def collection_name
+ plural
+ end
- if args.length > 1
- args.each { |path| match(path, options) }
- return self
+ def id_segment
+ ":#{singular}_id"
+ end
end
- if args.first.is_a?(Symbol)
- return match(args.first.to_s, options.merge(:to => args.first.to_sym))
+ class SingletonResource < Resource #:nodoc:
+ def initialize(entity, options = {})
+ super
+ end
+
+ def name
+ singular
+ end
end
- path = args.first
+ def resource(*resources, &block)
+ options = resources.extract_options!
- options = (@scope[:options] || {}).merge(options)
- conditions, defaults = {}, {}
+ if resources.length > 1
+ raise ArgumentError if block_given?
+ resources.each { |r| resource(r, options) }
+ return self
+ end
+
+ resource = SingletonResource.new(resources.pop)
+
+ if @scope[:scope_level] == :resources
+ nested do
+ resource(resource.name, options, &block)
+ end
+ return self
+ end
- path = nil if path == ""
- path = Rack::Mount::Utils.normalize_path(path) if path
- path = "#{@scope[:path]}#{path}" if @scope[:path]
+ scope(:path => "/#{resource.name}", :controller => resource.controller) do
+ with_scope_level(:resource, resource) do
+ yield if block_given?
- raise ArgumentError, "path is required" unless path
+ get "(.:format)", :to => :show, :as => resource.member_name
+ post "(.:format)", :to => :create
+ put "(.:format)", :to => :update
+ delete "(.:format)", :to => :destroy
+ get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}"
+ get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}"
+ end
+ end
- constraints = options[:constraints] || {}
- unless constraints.is_a?(Hash)
- block, constraints = constraints, {}
+ self
end
- blocks = ((@scope[:blocks] || []) + [block]).compact
- constraints = (@scope[:constraints] || {}).merge(constraints)
- options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) }
- conditions[:path_info] = path
- requirements = constraints.dup
+ def resources(*resources, &block)
+ options = resources.extract_options!
+
+ if resources.length > 1
+ raise ArgumentError if block_given?
+ resources.each { |r| resources(r, options) }
+ return self
+ end
+
+ resource = Resource.new(resources.pop)
+
+ if @scope[:scope_level] == :resources
+ nested do
+ resources(resource.name, options, &block)
+ end
+ return self
+ end
+
+ scope(:path => "/#{resource.name}", :controller => resource.controller) do
+ with_scope_level(:resources, resource) do
+ yield if block_given?
+
+ with_scope_level(:collection) do
+ get "(.:format)", :to => :index, :as => resource.collection_name
+ post "(.:format)", :to => :create
+ with_exclusive_name_prefix :new do
+ get "/new(.:format)", :to => :new, :as => resource.singular
+ end
+ end
+
+ with_scope_level(:member) do
+ scope("/:id") do
+ get "(.:format)", :to => :show, :as => resource.member_name
+ put "(.:format)", :to => :update
+ delete "(.:format)", :to => :destroy
+ with_exclusive_name_prefix :edit do
+ get "/edit(.:format)", :to => :edit, :as => resource.singular
+ end
+ end
+ end
+ end
+ end
- path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS)
- segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names
- constraints.reject! { |k, v| segment_keys.include?(k.to_s) }
- conditions.merge!(constraints)
+ self
+ end
- requirements[:controller] ||= Routing.controller_constraints
+ def collection
+ unless @scope[:scope_level] == :resources
+ raise ArgumentError, "can't use collection outside resources scope"
+ end
- if via = options[:via]
- via = Array(via).map { |m| m.to_s.upcase }
- conditions[:request_method] = Regexp.union(*via)
+ with_scope_level(:collection) do
+ scope(:name_prefix => parent_resource.collection_name, :as => "") do
+ yield
+ end
+ end
end
- defaults[:controller] = @scope[:controller].to_s if @scope[:controller]
+ def member
+ unless @scope[:scope_level] == :resources
+ raise ArgumentError, "can't use member outside resources scope"
+ end
- if options[:to].respond_to?(:call)
- app = options[:to]
- defaults.delete(:controller)
- defaults.delete(:action)
- elsif options[:to].is_a?(String)
- defaults[:controller], defaults[:action] = options[:to].split('#')
- elsif options[:to].is_a?(Symbol)
- defaults[:action] = options[:to].to_s
+ with_scope_level(:member) do
+ scope("/:id", :name_prefix => parent_resource.member_name, :as => "") do
+ yield
+ end
+ end
end
- app ||= Routing::RouteSet::Dispatcher.new(:defaults => defaults)
- if app.is_a?(Routing::RouteSet::Dispatcher)
- unless defaults.include?(:controller) || segment_keys.include?("controller")
- raise ArgumentError, "missing :controller"
+ def nested
+ unless @scope[:scope_level] == :resources
+ raise ArgumentError, "can't use nested outside resources scope"
end
- unless defaults.include?(:action) || segment_keys.include?("action")
- raise ArgumentError, "missing :action"
+
+ with_scope_level(:nested) do
+ scope("/#{parent_resource.id_segment}", :name_prefix => parent_resource.member_name) do
+ yield
+ end
end
end
- app = Constraints.new(app, blocks) if blocks.any?
- @set.add_route(app, conditions, requirements, defaults, options[:as])
+ def match(*args)
+ options = args.extract_options!
- self
- end
+ if args.length > 1
+ args.each { |path| match(path, options) }
+ return self
+ end
- def redirect(path, options = {})
- status = options[:status] || 301
- lambda { |env|
- req = Rack::Request.new(env)
- url = req.scheme + '://' + req.host + path
- [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']]
- }
- end
+ if args.first.is_a?(Symbol)
+ with_exclusive_name_prefix(args.first) do
+ return match("/#{args.first}(.:format)", options.merge(:to => args.first.to_sym))
+ end
+ end
- private
- def map_method(method, *args, &block)
- options = args.last.is_a?(Hash) ? args.pop : {}
- options[:via] = method
args.push(options)
- match(*args, &block)
- self
+
+ case options.delete(:on)
+ when :collection
+ return collection { match(*args) }
+ when :member
+ return member { match(*args) }
+ end
+
+ if @scope[:scope_level] == :resources
+ raise ArgumentError, "can't define route directly in resources scope"
+ end
+
+ super
end
+
+ protected
+ def parent_resource
+ @scope[:scope_level_resource]
+ end
+
+ private
+ def with_exclusive_name_prefix(prefix)
+ begin
+ old_name_prefix = @scope[:name_prefix]
+
+ if !old_name_prefix.blank?
+ @scope[:name_prefix] = "#{prefix}_#{@scope[:name_prefix]}"
+ else
+ @scope[:name_prefix] = prefix.to_s
+ end
+
+ yield
+ ensure
+ @scope[:name_prefix] = old_name_prefix
+ end
+ end
+
+ def with_scope_level(kind, resource = parent_resource)
+ old, @scope[:scope_level] = @scope[:scope_level], kind
+ old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
+ yield
+ ensure
+ @scope[:scope_level] = old
+ @scope[:scope_level_resource] = old_resource
+ end
+ end
+
+ include Base
+ include HttpHelpers
+ include Scoping
+ include Resources
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 79e15edeaa..bf2443c1be 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -18,31 +18,37 @@ module ActionDispatch
def call(env)
params = env[PARAMETERS_KEY]
+ prepare_params!(params)
+
+ unless controller = controller(params)
+ return [417, {}, []]
+ end
+
+ controller.action(params[:action]).call(env)
+ end
+
+ def prepare_params!(params)
merge_default_action!(params)
split_glob_param!(params) if @glob_param
+
params.each do |key, value|
if value.is_a?(String)
value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
params[key] = URI.unescape(value)
end
end
+ end
- if env['action_controller.recognize']
- [200, {}, params]
- else
- controller = controller(params)
- controller.action(params[:action]).call(env)
+ def controller(params)
+ if params && params.has_key?(:controller)
+ controller = "#{params[:controller].camelize}Controller"
+ ActiveSupport::Inflector.constantize(controller)
end
+ rescue NameError
+ nil
end
private
- def controller(params)
- if params && params.has_key?(:controller)
- controller = "#{params[:controller].camelize}Controller"
- ActiveSupport::Inflector.constantize(controller)
- end
- end
-
def merge_default_action!(params)
params[:action] ||= 'index'
end
@@ -197,26 +203,40 @@ module ActionDispatch
end
end
- attr_accessor :routes, :named_routes, :configuration_files
+ attr_accessor :routes, :named_routes
+ attr_accessor :disable_clear_and_finalize
def initialize
- self.configuration_files = []
-
self.routes = []
self.named_routes = NamedRouteCollection.new
- clear!
+ @disable_clear_and_finalize = false
end
def draw(&block)
- clear!
- Mapper.new(self).instance_exec(DeprecatedMapper.new(self), &block)
+ clear! unless @disable_clear_and_finalize
+
+ mapper = Mapper.new(self)
+ if block.arity == 1
+ mapper.instance_exec(DeprecatedMapper.new(self), &block)
+ else
+ mapper.instance_exec(&block)
+ end
+
+ finalize! unless @disable_clear_and_finalize
+
+ nil
+ end
+
+ def finalize!
@set.add_route(NotFound)
install_helpers
@set.freeze
end
def clear!
+ # Clear the controller cache so we may discover new ones
+ @controller_constraints = nil
routes.clear
named_routes.clear
@set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY)
@@ -231,65 +251,38 @@ module ActionDispatch
routes.empty?
end
- def add_configuration_file(path)
- self.configuration_files << path
- end
-
- # Deprecated accessor
- def configuration_file=(path)
- add_configuration_file(path)
- end
-
- # Deprecated accessor
- def configuration_file
- configuration_files
- end
-
- def load!
- # Clear the controller cache so we may discover new ones
- Routing.clear_controller_cache!
-
- load_routes!
- end
-
- # reload! will always force a reload whereas load checks the timestamp first
- alias reload! load!
+ CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/
- def reload
- if configuration_files.any? && @routes_last_modified
- if routes_changed_at == @routes_last_modified
- return # routes didn't change, don't reload
- else
- @routes_last_modified = routes_changed_at
- end
- end
-
- load!
- end
-
- def load_routes!
- if configuration_files.any?
- configuration_files.each { |config| load(config) }
- @routes_last_modified = routes_changed_at
- else
- draw do |map|
- map.connect ":controller/:action/:id"
- end
+ def controller_constraints
+ @controller_constraints ||= begin
+ source = controller_namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" }
+ source << CONTROLLER_REGEXP.source
+ Regexp.compile(source.sort.reverse.join('|'))
end
end
- def routes_changed_at
- routes_changed_at = nil
+ def controller_namespaces
+ namespaces = Set.new
- configuration_files.each do |config|
- config_changed_at = File.stat(config).mtime
+ # Find any nested controllers already in memory
+ ActionController::Base.subclasses.each do |klass|
+ controller_name = klass.underscore
+ namespaces << controller_name.split('/')[0...-1].join('/')
+ end
- if routes_changed_at.nil? || config_changed_at > routes_changed_at
- routes_changed_at = config_changed_at
+ # TODO: Move this into Railties
+ if defined?(Rails.application)
+ # Find namespaces in controllers/ directory
+ Rails.application.configuration.controller_paths.each do |load_path|
+ load_path = File.expand_path(load_path)
+ Dir["#{load_path}/**/*_controller.rb"].collect do |path|
+ namespaces << File.dirname(path).sub(/#{load_path}\/?/, '')
+ end
end
end
- routes_changed_at
+ namespaces.delete('')
+ namespaces
end
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
@@ -374,7 +367,8 @@ module ActionDispatch
end
recall[:action] = options.delete(:action) if options[:action] == 'index'
- parameterize = lambda { |name, value|
+ opts = {}
+ opts[:parameterize] = lambda { |name, value|
if name == :controller
value
elsif value.is_a?(Array)
@@ -384,14 +378,23 @@ module ActionDispatch
end
}
- path = @set.url(named_route, options, recall, :parameterize => parameterize)
+ unless result = @set.generate(:path_info, named_route, options, recall, opts)
+ raise ActionController::RoutingError, "No route matches #{options.inspect}"
+ end
+
+ path, params = result
+ params.each do |k, v|
+ if v
+ params[k] = v
+ else
+ params.delete(k)
+ end
+ end
+
if path && method == :generate_extras
- uri = URI(path)
- extras = uri.query ?
- Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } :
- []
- [uri.path, extras]
+ [path, params.keys]
elsif path
+ path << "?#{params.to_query}" if params.any?
path
else
raise ActionController::RoutingError, "No route matches #{options.inspect}"
@@ -402,37 +405,11 @@ module ActionDispatch
def call(env)
@set.call(env)
- rescue ActionController::RoutingError => e
- raise e if env['action_controller.rescue_error'] == false
-
- method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO']
-
- # Route was not recognized. Try to find out why (maybe wrong verb).
- allows = HTTP_METHODS.select { |verb|
- begin
- recognize_path(path, {:method => verb}, false)
- rescue ActionController::RoutingError
- nil
- end
- }
-
- if !HTTP_METHODS.include?(method)
- raise ActionController::NotImplemented.new(*allows)
- elsif !allows.empty?
- raise ActionController::MethodNotAllowed.new(*allows)
- else
- raise e
- end
- end
-
- def recognize(request)
- params = recognize_path(request.path, extract_request_environment(request))
- request.path_parameters = params.with_indifferent_access
- "#{params[:controller].to_s.camelize}Controller".constantize
end
- def recognize_path(path, environment = {}, rescue_error = true)
+ def recognize_path(path, environment = {})
method = (environment[:method] || "GET").to_s.upcase
+ path = Rack::Mount::Utils.normalize_path(path)
begin
env = Rack::MockRequest.env_for(path, {:method => method})
@@ -440,16 +417,16 @@ module ActionDispatch
raise ActionController::RoutingError, e.message
end
- env['action_controller.recognize'] = true
- env['action_controller.rescue_error'] = rescue_error
- status, headers, body = call(env)
- body
- end
+ req = Rack::Request.new(env)
+ @set.recognize(req) do |route, params|
+ dispatcher = route.app
+ if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params)
+ dispatcher.prepare_params!(params)
+ return params
+ end
+ end
- # Subclasses and plugins may override this method to extract further attributes
- # from the request, for use by route conditions and such.
- def extract_request_environment(request)
- { :method => request.method }
+ raise ActionController::RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
end
end
end