lib/nitro/dispatcher.rb in nitro-0.25.0 vs lib/nitro/dispatcher.rb in nitro-0.26.0

- old
+ new

@@ -4,36 +4,23 @@ require 'nitro/routing' require 'nitro/helper/default' module Nitro -# The Dispatcher manages a set of controllers. +# Raised when an action can not be found for a path +# check for this in your error action to catch as if 404 -class Dispatcher +class NoActionError < NoMethodError; end +# The Dispatcher manages a set of controllers. It maps +# a request uri to a [controller, action] pair. + +class Dispatcher include Router - # The dispatcher specialization used. - - setting :mode, :default => :nice, :doc => 'The dispatcher specialization used' - - unless const_defined? :ROOT - ROOT = '/' - end + ROOT = '/' - # The public root directory. The files in this - # directory are published by the web server. - - attr_accessor :public_root - - # The template root directory. By default this points to the - # public root directory to allow for PHP/JSP/ASP style - # programming. But you should probably change this to - # another directory for extra security. - - attr_accessor :template_root - # The controllers map. attr_accessor :controllers # Create a new Dispatcher. @@ -43,13 +30,10 @@ # [+controllers+] # Either a hash of controller mappings or a single # controller that gets mapped to :root. def initialize(controllers = nil) - @public_root = 'public' - @template_root = @public_root - if controllers and controllers.is_a?(Class) and controllers.ancestors.include?(Controller) controllers = { '/' => controllers } else controllers ||= { '/' => Controller } end @@ -85,25 +69,25 @@ c.send :include, Publishable end auto_mixin(c) - # Perform mount-time initialization of the controller. - - if c.respond_to? :mounted - c.mounted(path) - end - # Try to setup a template_root if none is defined: unless c.template_root c.module_eval %{ def self.template_root "#{Template.root}#{path}".gsub(/\\/$/, '') end } end + + # Keep the mount point as an annotation. + + c.ann.self.mount_point = path.gsub(/^\//, '') + + c.mounted(path) if c.respond_to?(:mounted) end (@controllers ||= {}).update(controllers) update_routes() @@ -136,17 +120,18 @@ @routes = [] @controllers.each do |base, c| base = '' if base == '/' for m in c.action_methods - if route = c.ann(:m).route and (!route.nil?) - unless c.ann(:m).params.nil? - keys = c.ann(:m).params.keys + m = m.to_sym + if route = c.ann(m).route and (!route.nil?) + unless c.ann(m).params.nil? + keys = c.ann(m).params.keys else keys = [] end - @routes << [route, "#{base}/#{action}", *keys] + @routes << [route, "#{base}/#{m}", *keys] end end end end @@ -157,36 +142,100 @@ # [+path+] # The path to dispatch. # # [:context] # The dispatching context. + # + # The dispatching algorithm handles implicit nice urls. + # Subdirectories are also supported. + # Action containing '/' separators look for templates + # in subdirectories. The '/' char is converted to '__' + # to find the actual action. + # + # Returns the dispatcher class, the action name and the + # base url. For the root path, the base url is nil. #-- # FIXME: this is a critical method that should be optimized # watch out for excessive String creation. + # TODO: add caching. #++ - + def dispatch(path, context = nil) - # Specialize in your application or use the default - # specializations in lib/nitro/dispatcher/* + path = route(path, context) + + parts = path.split('/') + parts.shift # get rid of the leading '/'. + + if klass = controller_class_for("/#{parts.first}") + base = "/#{parts.shift}" + else + base = nil + klass = controller_class_for(ROOT) + end + + idx = 0 + found = false + + # default to index + + parts << 'index' if parts.empty? + + # Try to find the first valid action substring + + action = '' + + for part in parts + action << part + if klass.respond_to_action_or_template?(action) + found = true + break + end + action << '__' + idx += 1 + end + + if found + parts.slice!(0, idx + 1) + else + #-- + # FIXME: no raise to make testable. + #++ + raise NoActionError, "No action to dispatch to on #{klass}" + end + + # push any remaining parts of the url onto the query + # string for use with request + + unless parts.empty? + context.headers['QUERY_STRING'] = "#{parts.join(';')};#{context.headers['QUERY_STRING']}" + end + + base = nil if base == ROOT + + return klass, "#{action}_action", base end alias_method :split_path, :dispatch +private + # Get the controller for the given key. # Also handles reloading of controllers. def controller_class_for(key) klass = @controllers[key] if klass and Compiler.reload klass.instance_methods.grep(/(_action$)|(_template$)/).each do |m| klass.send(:remove_method, m) rescue nil end + klass.compile_scaffolding_code if klass.respond_to?(:compile_scaffolding_code) end return klass end end end # * George Moschovitis <gm@navel.gr> +# * Chris Farmiloe <chris.farmiloe@farmiloe.com>