Finished initial releasable version of MRouter, the Rack Merb Router.
[maraby.git] / mrouter / mrouter.rb
blob9065075922f8dc94eb4092e99e5753f593406187
1 #--
2 #  Created by Matt Todd on 2008-01-02.
3 #  Copyright (c) 2007. All rights reserved.
4 #++
6 #--
7 # dependencies
8 #++
10 begin
11   %w(rubygems merb/core_ext merb/router).each {|dep|require dep}
12 rescue LoadError => e
13   abort "Merb must be installed for Routing to function. Please install Merb."
14 end
16 #--
17 # module
18 #++
20 module Rack
21   
22   # = Routing
23   # 
24   # Handles routing.
25   # 
26   # == Usage
27   # 
28   #   class Xy
29   #     route do |r|
30   #       r.match('/path/to/match').to(:action => 'do_stuff')
31   #       {:action => 'not_found'} # the default route
32   #     end
33   #     def do_stuff(params)
34   #       [200, {}, 'OK']
35   #     end
36   #   end
37   # 
38   # == Default Routes
39   # 
40   # Supplying a default route if none of the others match is good practice,
41   # but is unnecessary as the predefined route is always, automatically,
42   # going to contain a redirection to the +not_found+ method. (So your app
43   # should implement a +not_found+ method.)
44   # 
45   # In order to set a different default route, simply end the call to +route+
46   # with a hash containing the action to run along with any other params.
47   # 
48   # == The Hard Work
49   # 
50   # The mechanics of the router are solely from the efforts of the Merb
51   # community. This functionality is completely ripped right out of Merb
52   # and makes it functional. All credit to them, and be sure to check out
53   # their great framework: if you need a beefier framework, maybe Merb is
54   # right for you.
55   # 
56   # http://merbivore.com/
57   class MRouter < Merb::Router
58     
59     def initialize(app)
60       @app = app
61     end
62     
63     def call(env)
64       env['merb.router'] = self
65       env['merb.route'] = self.class.route(env)
66       @app.call(env)
67     end
68     
69     # Retrieves the last value from the +route+ call and, if it's a Hash, sets
70     # it to +@@default_route+ to designate the failover route. If +route+ is
71     # not a Hash, though, the internal default should be used instead (as the
72     # last returned value is probably a Route object returned by the
73     # <tt>r.match().to()</tt> call).
74     # 
75     # Used exclusively internally.
76     def self.default_to route
77       @@default_route = route.is_a?(Hash) ? route : {:action => 'not_found'}
78     end
79     
80     # Called internally by the +call+ method to match the current request
81     # against the currently defined routes. Returns the params list defined in
82     # the +to+ routing definition, opting for the default route if no match is
83     # made.
84     def self.route(env)
85       # pull out the path requested (WEBrick keeps the host and port and protocol in REQUEST_URI)
86       uri = URI.parse(env['REQUEST_URI']).path
87       
88       # prepare request
89       path = (uri ? uri.split('?').first : '').sub(/\/+/, '/')
90       path = path[0..-2] if (path[-1] == ?/) && path.size > 1
91       req = Struct.new(:path, :method).new(path, env['REQUEST_METHOD'].downcase.to_sym)
92       
93       # perform match
94       route = self.match(req, {})
95       
96       # make sure a route is returned even if no match is found
97       if route[0].nil?
98         #return default route
99         @@default_route
100       else
101         # params (including action and module if set) for the matching route
102         route[1]
103       end
104     end
105     
106   end
109 # The Helper that provides the +route+ class method and +route+ instance method
110 # to setup routes and to actually route the request.
111 module MRouterHelper
112   
113   # Matches the request information in +env+ to the appropriate route.
114   def route
115     Rack::MRouter.route(@env)
116   end
117   
118   module ClassMethods
119     # Yields the router which allows routes to be set up.
120     def route
121       if block_given?
122         Rack::MRouter.prepare do |router|
123           Rack::MRouter.default_to yield(router)
124         end
125       else
126         abort "Halcyon::Server::Base.route expects a block to define routes."
127       end
128     end
129   end
130   
131   # Hook to include the class methods into the receiver.
132   def self.included(receiver)
133     receiver.extend(ClassMethods)
134   end
135