Rack Routing

I <3 Rack

Rack is great for thin webservices or simple sites, it even has simple support for dealing with sessions, authentication, OpenID and more.

Where Rack really starts to break down is when you want to do any kindof non-trivial routing.

So, what are some ways we can add routing to our Rack apps?

case / when

The simplest routing, ofcourse, is via simple case statements. Any Rack app is likely to start out with some kindof simple routing, like this:

 1 #! /usr/bin/env ruby
 2 # 
 3 # example of case/when style rack routing
 4 #
 5 %w( rubygems thin ).each {|lib| require lib }
 6 Rack::Handler::Thin.run lambda { |env|
 7   path = env['PATH_INFO']
 8   case path
 9 
10   when ''
11   when '/'
12     [200, {}, "routed #{path} to index"]
13 
14   when /chunky/i
15     [200, {}, "bacon!"]
16 
17   else
18     [200, {}, "route not found for #{path}"]
19 
20   end
21 }

Rack + Merb Router == DIY DSL

Simple case/when routing logic is great. But what if we want to do more complicated routing logic?
The Merb Router comes out of the box with all kinds of cool functionality. Did you know that you can even perform authentication in the Merb Router? And you can return directly from it without calling any controllers or anything?

Watch some of the MerbCamp Videos to see the awesomeness of the Merb Router.

So, what if you want to delegate routes to classes and methods easily? Or build your own web framework / DSL? The Merb Router can be used outside of Merb to help you do this. Essentially, all the Merb router really does it help you may URIs to hashes and then you can do whatever you want to with the hash.

While this example is obviously much longer than the simple case/when, you can see how much more powerful this can be. Being able to easily map URIs to hashes and then … do whatever!

 1 #! /usr/bin/env ruby
 2 # 
 3 # example of rack routing using the merb router
 4 #
 5 %w( rubygems thin merb-core/dispatch/router extlib ).each {|lib| require lib }
 6 
 7 # we need to call *something* to render our routes, so let's make some little classes
 8 class Actions
 9   def self.index
10     "hello from index!"
11   end
12 end
13 class MoreActions
14   def self.default
15     "route not found"
16   end
17   def self.chunky
18     "bacon!"
19   end
20 end
21 
22 Rack::Handler::Thin.run lambda { |env|
23 
24   # setup some routes
25   Merb::Router.prepare do
26     match('/').to       :class => 'Actions',     :method => 'index'
27     match(/\/chunky/).to :class => 'MoreActions', :method => 'chunky'
28   end
29 
30   # execute the merb router, getting back a hash for the found route
31   request = Struct.new(:path, :method).new(env['PATH_INFO'], :get)
32   route   = Merb::Router.match(request).last
33 
34   # ok, we got a hash back for our path ... do something with it!
35   if route.empty?
36     response = MoreActions.default
37   else
38     response = Kernel.const_get(route[:class]).send route[:method]
39   end
40 
41   [200, {}, response]
42 }

If you’re anything like me, you look at the above code and think “man, it sure would be easy to make a nice purty DSL like Sinatra’s this way!” and you would be right.

Who needs Metal and all that jazz? Rack’s awesome all by itself :)

PS. thanks to Marc Chung for the idea of using the Merb router outside of Merb