Trellis Tutorial #2 - Routing in Trellis

Posted by Brian Sam-Bodden Fri, 29 Jan 2010 22:13:00 GMT

Routing in Trellis

Trellis provides a per page routing scheme heavily based on Sinatra’s routing with a few more additions and magic of its own. In this short article I’ll cover the most common usages of routing. For more examples check out the included routing example in the Trellis distribution (examples/routing) and the RSpec specs for the routing classes (test/router_spec.rb)

Absolute Routes

Routing in Trellis works at the page level. Each page in an application has a base default route which is the page class name lowercased and underscored. For example, if you have a page class called ChuckNorrisIsTheMan:

class ChuckNorrisIsTheMan < Trellis::Page
  template do html { body { text %[Chuck can kick you before you blink] }} end
end

Then the page would be accessible at /chuck_norris_is_the_man. For more complex routing needs Trellis provides the “route” class method available on the Page class. The route method takes a routing pattern. The simplest routing pattern is just an absolute route:

class WhoaPage < Trellis::Page
  route '/whoa'

  template do html { body { text %[whoa!] }} end
end

With an absolute route is easy to add route spaces like /admin and the like.

Parameterized Routes

But things only get interesting when we start passing values via our URLs. Let’s say that we wanted to have a page that can be take a routing pattern representing a date. Trellis routing patterns can take named parameters so we could have a page with a complex route in a format like /day_of_the_year/2009/11/21. The page RoutedDifferentlyWithParams below shows how to create such a route:

class RoutedDifferentlyWithParams < Trellis::Page
  route '/day_of_the_year/:year/:month/:day'

  def parse_date
    Date.parse("#{@month}/#{@day}/#{@year}").strftime("%e %B, %Y")
  end

  def on_select
    self
  end

  template %[
    
     

@{parse_date}@ is the @{Date.parse(@month + '/' + @day + '/' + @year).yday.ordinalize.to_s}@ day of the year!


Refresh ], :format => :eruby end

A route pattern can take named parameters using the colon + ruby identifier syntax. Any matched parameters are available as instance variables to the page. For example in the parse_date method we are taking the 3 incoming parameters turning them into a String in the format mm/dd/year, parsing the String into a Date object and finally formatting it with strftime. In the template we then print the parsed date and what day of the year it corresponds to as shown in Figure 1.

Trellis Routing

Figure 1 - Date Route Example

Optional Parameters

Trellis routes can also take optional parameters which should be pre and post-fixed with a question mark (?). For example, you could create a route such as:

route '/?:foo?/?:bar?'

The route above will match ‘/chuck/norris’, ‘/chuck’ and ‘/’ and both ‘foo’ and ‘bar’ will be available to the page as @foo and @bar respectively. In the case of the pattern ‘/chuck/norris’, @foo will be ‘chuck’ and @bar will be ‘norris’. For ‘/chuck’, @foo will be ‘chuck’ and @bar will be nil. While for ‘/’ both @foo and @bar will be nil.

Wildcard Parameters

Trellis page routes also support wildcard parameters via using and asterisk (*). For example a simple catch-all pattern could be expressed as:

route '/*'

Any values captured by the ‘*’ are available in the @splat instance variable which is an array of all captured anonymous parameters. Wildcard parameters can also be mixed with named parameters and absolute paths, for example all the routes below are valid routes:

route '/awesomeness/*/foo/*/*'
route '/mixed/:foo/*'

Also parameters do not need to be delimited only by a forward slash, for example the following pattern is also valid:

route '/downloads/:file.:ext'

Route Sorting

In frameworks with powerful routing capabilities, the order of the route declarations is typically used to determine how they are matched against an incoming request. I felt that this was a somewhat goofy convention. In Trellis routes are sorted according to their complexity. For example, let’s take a look at one of specs for the Trellis router class:

it "should be able to be sorted by 'matchability'" do
  route_1 = Trellis::Router.new(:path => '/hello/:one/:two') 
  route_2 = Trellis::Router.new(:path => '/hello/jim/:last')
  route_3 = Trellis::Router.new(:path => '/hello/*')
  route_4 = Trellis::Router.new(:path => '/hello/*/:foo/*')

  routes = [ route_1, route_2, route_3, route_4 ].sort {|a,b| b.score <=> a.score }

  routes[0].should be(route_4)
  routes[1].should be(route_2)
  routes[2].should be(route_1)
  routes[3].should be(route_3)
end

As you can see route patterns are compiled inside a Router object. Router objects have a score. For the routes above when sorted by score the order ends up being:

'/hello/*/:foo/*'
'/hello/jim/:last'
'/hello/:one/:two'
'/hello/*'

The score value represents the matching power of a route and it is used to try the more specific routes first. For example in any list of routes, the ‘catch all’ route ‘/*’ is always evaluated next to last. The default Trellis route /page[.event[_source]][/value][?query_params] is always evaluated last.

Conclusions

This short article shows the flexibility of Trellis’ page routing scheme. As I mentioned before it is a port of the great work done by the Sinatra framework with some additions of our own. In future installments we will exploit this flexibility to create robust applications with clean “pretty” routes.

Tags ,

Comments are disabled