Class: Nitro::Dispatcher

Inherits:
Object
  • Object
show all
Includes:
Router
Defined in:
lib/nitro/dispatcher.rb

Overview

The Dispatcher manages a set of controllers. It maps a request uri to a [controller, action] pair.

Constant Summary collapse

ROOT =
'/'

Instance Attribute Summary collapse

Attributes included from Router

#rules

Instance Method Summary collapse

Methods included from Router

#<<, add_rule, #add_rule, #add_rules, #decode_route, #encode_route, #init_routes

Constructor Details

#initialize(controllers = nil) ⇒ Dispatcher

Create a new Dispatcher.

Input:

controllers

Either a hash of controller mappings or a single controller that gets mapped to :root.



37
38
39
40
41
42
43
44
45
# File 'lib/nitro/dispatcher.rb', line 37

def initialize(controllers = nil)
  if controllers and controllers.is_a?(Class) and controllers.ancestors.include?(Controller)
    controllers = { '/' => controllers }
  else
    controllers ||= { '/' => Controller }
  end

  mount(controllers)
end

Instance Attribute Details

#controllersObject

The controllers map.



27
28
29
# File 'lib/nitro/dispatcher.rb', line 27

def controllers
  @controllers
end

#serverObject

The server.



23
24
25
# File 'lib/nitro/dispatcher.rb', line 23

def server
  @server
end

Instance Method Details

#add_controller(controllers) ⇒ Object Also known as: mount, publish, map=

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



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/nitro/dispatcher.rb', line 69

def add_controller(controllers)
  for path, klass in controllers
    unless (klass.ancestors.include?(Controller) or klass.ancestors.include?(Publishable))
      klass.send :include, Publishable
    end

    # Automatically mixin controller helpers.

    mixin_auto_helpers(klass)      

    # Customize the class for mounting at the given path.
    #--
    # gmosx, TODO: path should include trailing '/'
    # gmosx, TODO: should actually create an instance, thus
    # allowing mounting the same controller to multiple
    # paths, plus simplifying the code. This instance will
    # be dup-ed for each request.
    #++

    klass.mount_at(path)
    
    # Call the mounted callback to allow for post mount
    # initialization.
    
    klass.mounted(path) if klass.respond_to?(:mounted)
  end

  (@controllers ||= {}).update(controllers)
  
  update_routes()
end

#dispatch(path, context = nil) ⇒ Object Also known as: split_path

Processes the path and dispatches to the corresponding controller/action pair.

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. The dispatcher also handles nested controllers.

Returns the dispatcher class and the action name. – FIXME: this is a critical method that should be optimized watch out for excessive String creation. TODO: add caching. ++



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/nitro/dispatcher.rb', line 170

def dispatch(path, context = nil)
  # Try if the router can directly decode the path.
  
  klass, action, params = decode_route(path)
  if klass
    # This adds parameter values from the setup from the route to the normal
    # query strings.
    context.headers['QUERY_STRING'] ||= ''
    extra = params.map { |k, v| "#{k}=#{v}"}.join(';') if params
    if context.headers['QUERY_STRING'].empty?
      context.headers['QUERY_STRING'] = extra
    else
      context.headers['QUERY_STRING'] << ';' << extra
    end
    
    context.headers['ACTION_PARAMS'] = params.values
    
    # context.params.update(params) if params
    # gmosx, FIXME/OPTIMIZE: no annotation for mount point!! 
    return klass, "#{action}_action", klass.mount_path
  end

  key, * = path.split('?', 2)
  key = key.split('/')
  parts = []    
  
  while (not key.empty?) and (klass = controller_class_for("#{key.join('/')}")).nil?
    parts.unshift(key.pop)
  end  
  
  klass = controller_class_for(ROOT) unless klass

  idx = 0
  found = false

  # gmosx, HACKFIX!
  
  parts.shift if parts.first == ''   

  # 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

  # Check the index action.
  
  unless found
    action = :index
    if klass.respond_to_action? action
      a = klass.instance_method(action).arity
      found = true if a < 0 || a >= parts.size
    elsif klass.respond_to_template? action
      found = true if parts.size == 0
    end
    idx = -1 if found      
  end
    
  if found
    parts.slice!(0, idx + 1)
=begin
    if $DBG
      # Some extra checking of the parameters. Only enabled
      # on debug mode, because it slows down dispatching.
      
      a = klass.instance_method(action).arity
      
      if a > 0 and a != parts.size
        raise ActionError, "Invalid parameters for action, expects #{a} parameters, received #{parts.size}"
      end
    end
=end
  else
    #--
    # FIXME: no raise to make testable.
    #++
    raise ActionError, "No action for path '#{path}' on '#{klass}'"
  end
  
  # push any remaining parts of the url onto the query 
  # string for use with request
  
  context.headers['ACTION_PARAMS'] = parts

  return klass, "#{action}_action"
end

#mixin_auto_helpers(klass) ⇒ Object

Call this method to automatically include helpers in the Controllers. For each Controller ‘XxxController’ the default helper ‘Helper’ and the auto helper ‘XxxControllerHelper’ (if it exists) are included.



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/nitro/dispatcher.rb', line 109

def mixin_auto_helpers(klass)
    klass.helper(Nitro::DefaultHelper)      

    return # FIXME: make the following work again!            
          
    begin
      if helper = Module.by_name("#{klass}Helper")
        klass.helper(helper)
      end
    rescue NameError
      # The auto helper is not defined.
    end
end

#update_routesObject

Update the routes. Typically called after a new Controller is mounted.

Example of routing through annotations

def view_user

"params:  #{request[:id]} and #{request[:mode]}"

end ann :view_user, :route => [ /user_(d*)_(*?).html/, :id, :mode ]



133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/nitro/dispatcher.rb', line 133

def update_routes
  init_routes()
  
  @controllers.each do |base, c|
    base = '' if base == '/'
    for m in c.action_methods
      m = m.to_sym
      if route = c.ann(m).route and (!route.nil?)
        add_rule(:match => route.first, :controller => c, :action => m, :params => route.last)
      end
    end
  end
end