require 'nano/kernel/singleton' require 'nitro/controller' require 'nitro/routing' require 'nitro/mixin/helper' module Nitro # The Dispatcher manages a set of controllers. class Dispatcher include Router # The dispatcher specialization used. setting :mode, :default => :nice, :doc => 'The dispatcher specialization used' unless const_defined? :ROOT ROOT = '/' end # 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. # # Input: # # [+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 mount(controllers) end # A published object is exposed through a REST interface. # Only the public non standard methods of the object are # accessible. Published objects implement the Controller # part of MVC. # # Process the given hash and mount the # defined classes (controllers). # # Input: # # [+controllers+] # A hash representing the mapping of # mount points to controllers. # # === Examples # # disp.mount( # '/' => MainController, # mounts / # '/users' => UsersController # mounts /users # ) # disp.publish '/' => MainController def add_controller(controllers) for path, c in controllers unless (c.ancestors.include?(Controller) or c.ancestors.include?(Publishable)) 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 end (@controllers ||= {}).update(controllers) update_routes() end alias_method :mount, :add_controller alias_method :publish, :add_controller alias_method :map=, :add_controller # Call this method to automatically include helpers in the # Controllers. For each Controller 'XxxController' the # default helper 'Helper' and the auto mixin # 'XxxControllerMixin' (if it exists) are included. def auto_mixin(c) c.helper(Helper) begin if helper = Module.by_name("#{c}Mixin") c.helper(helper) end rescue NameError # The auto helper is not defined. end end # Update the routes. Typically called after a new # Controller is mounted. def update_routes @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 else keys = [] end @routes << [route, "#{base}/#{action}", *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. #-- # FIXME: this is a critical method that should be optimized # watch out for excessive String creation. #++ def dispatch(path, context = nil) # Specialize in your application or use the default # specializations in lib/nitro/dispatcher/* end alias_method :split_path, :dispatch # 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 end return klass end end end # * George Moschovitis