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.rb201
1 files changed, 89 insertions, 112 deletions
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