Getting Started with Merb vol.2: Intro to Routing and Controllers

3 June, 2008

An intro to routing and controllers.

I wrote recently about getting started with Merb. Yesterday, after having refer to my own post to remember how it all worked, I started on a new Merb app as part of the Great Grabb.it Rewrite of 2008. I very quickly ran into another area of Merb that's a bit thin on documentation: Controllers. After some googling, some fiddling, and some brainstorming in the #merb irc channel I managed to figure out enough of the basics to get things up and running.

Right now, Merb's sweet spot seems to be as an API server, which is exactly how we're planning on using it. This usage means that while there might be a little bit of model integration for access control, most of the job is in routing URLs and data formatting. That means we'll be spending most of our effort in config/router.rb and in our controllers. Specifically, we'll need to know a lot about how Merb provides responses in various formats depending on the request url, i.e. it's equivalent to Rails' respond_to method. Let's start by laying out our requirements and then I'll show you how I translated them into Merbish.

For this app, I needed to take URLs within a namespace and route them to a single controller setting various params based on the rest of the request path. In other words, I wanted to provide URLs like:

http://myapp.com/music/artist.jspf

and

http://myapp.com/music/artist/title.mp3

where the first url would give you a playlist of all of an artist's tracks in JSPF (Javascript Shareable Playlist Format) and the second would provide an mp3 of the song with the given title by the given artist.

So, the first thing I needed was a series of routes that would recognize all URLS starting with /music and then grab the path after the first slash into an :artist param and the path after the second slash into a :title param. Finally, it would need to look for a format after the dot and pass that through as well if available.

It turns out the best way to do this in Merb is with nested routes, thusly (inside of the Merb::Router.prepare block in config/router.rb):

r.match('/music') do |music|
music.match("/:artist.:format").to(:controller => 'music', :action => 'artist')
music.match("/:artist/:title.:format").to(:controller => 'music', :action => 'track')
end

This is pretty self explanatory if you're familiar with Rails routes, though it is probably worth noting that an early version of this I tried which eschewed the nesting in favor of including the /music part of the path in each route separately seemed to have problems detecting the format correctly (though it is more than likely the cause was driver error on my part rather than anything that Merb might do differently in that situation). There's pretty thorough documentation on Merb routing if you need more specific help and that's definitely not the case for the Controller APIs, so let's tackle those right away.

Now, how do we consume those URLs in our application? We need a controller, thusly:

class Music < Application
provides :jspf
def track
display Track.new params[:artist], params[:title]
end
def artist
display Artist.new params[:artist]
end
end

There's two main things going on here that distinguish Merb's controllers from Rails': the 'provides' class method and the 'display' method we're calling in each action. These two methods work together to let us return our data in the right format on every request. And, while they might look confusingly different from the familiar Rails respond_to block at first, they turn out to make things much cleaner, eliminating the need to redeclare common formats over and over in each controller method.

So, here's how it works: provides sets up a list of formats that we're willing to consume and then display makes sure that we provide the right one for each request. When a request comes in ending in .jspf, display calls to_jspf on the object that gets passed to it (in this case new tracks and artists). If we wanted to provide javascript, xml, html, etc. we'd just add those symbols to provides' arguments and then make sure the objects we passed to display could respond to to_xml, to_html, to_etc with the right data (there's an additional option for html of using a template in the views directory that Merb can automatically detect; that case works just like Rails; also: html seems always to be installed as a format regardless of whether or not you pass :html to provides).

While this should be enough to get you up and running writing Merb controllers, there are a few other details worth noting. First of all, if you want to provide alternative formats besides, js, html, xml, and the usual suspects (like we do here with jspf), you'll need to register a mime-type. We configured that by adding a single line to config/init.rb:

Merb.add_mime_type(:jspf, :to_jspf, %w[application/jspf+json])

There's lots of other additional information about the request available to your controller actions as well, such as content_type. Secondly there's other options besides display for returning content from your action. Calling "render" will attempt to automatically detect a template with the right format in the appropriate views directory. And you can always just render a raw string. Again, more details are available here.

There's the beginnings of a great open source book being written by some folks on GitHub: Life On The Edge With Merb, DataMapper & RSpec.They've done a pretty solid job covering the basics of app initialization, modeling with the DataMapper ORM, and testing with RSpec so far, but their section on Controllers is blank so far. If someone wants to pick up the thin thread I've laid down here and run with it, that would be a great place for more documentation to accumulate.

Tagged: , , ,