# * George Moschovitis # (c) 2004-2005 Navel, all rights reserved. # $Id$ module N require 'nitro/controller' require 'nitro/routing' require 'nitro/simple' # The Dispatcher manages a set of controllers. class Dispatcher include Router # 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 # APIs map. attr_accessor :apis # Create a new Dispatcher. # # Input: # # [+controllers+] # Either a hash of controller mappings or a single # controller that gets mapped to :root. # # [+apis+] # A hash of apis supported by the Dispatcher. def initialize(controllers = nil, apis = nil, routes = nil) @public_root = 'public' @template_root = @public_root if controllers and controllers.is_a?(Class) and controllers.ancestors.include?(Controller) controllers = { :root => controllers } else controllers ||= { :root => SimpleController } end mount(controllers) @apis = apis || {} end # Process the given hash and mount the # defined controllers. # # Input: # # [+controllers+] # A hash representing the mapping of # mount points to controllers. # # === Examples # # dispatcher.mount { # :root => MainController # mounts / # 'users' => UsersController # mounts /users # } def add_controller(controllers) (@controllers ||= {}).update(controllers) update_routes end alias_method :mount, :add_controller # Add a new api to the dispatcher # # [+api+] # API symbol # [+data+] # Data for this API [content_type, ..] # # === Example # # dispatcher.add_api(:xml, 'text/xml') def add_api(api, data) (@apis ||= {})[api] = data end # Update the routes. Typically called after a new # Controller is mounted. def update_routes @routes = [] @controllers.each do |base, c| base = (base == :root ? '' : "/#{base}") c.action_metadata.each do |action, meta| if route = meta[:route] @routes << [route, "#{base}/#{action}", *meta.params.keys] end end end end # Processes the path and dispatches to the corresponding # controller/action pair. # The base returned contains a trailing '/'. # # [+path+] # The path to dispatch. # # [:context] # The dispatching context. def dispatch(path, context = nil) api = :xhtml path = route(path, context) if @apis # OPTIMIZE: check only if lookup fails. @apis.each { |k, v| api = k if path.slice!(/#{k}\//) } end parts = path.split('/') case parts.size when 0 # / -> root.index # base = @template_root klass = controller_class_for(:root, context) action = 'index' when 2 if klass = controller_class_for(parts[1], context) # controller/ -> controller.index # base = "#{@template_root}/#{parts[1]}" action = 'index' else # action/ -> root.action # base = @template_root klass = controller_class_for(:root, context) action = parts[1] end when 3 # controller/action/ -> controller.action # base = "#{@template_root}/#{parts[1]}" klass = controller_class_for(parts[1], context) action = parts[2] end content_type = @apis[:api] || 'text/html' return klass, "__#{api}__#{action}", content_type end alias_method :split_path, :dispatch # Get the controller for the given key. # Also handles reloading of controllers. def controller_class_for(key, context) klass = @controllers[key] if (:full == Rendering.reload) and context and context[:__RELOADED__].nil? and klass def_file = klass::DEF_FILE Controller.remove_subclasses load(def_file) klass = @controllers[key] = Object.const_get(klass.name.intern) context[:__RELOADED__] = true end return klass end end end