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>