Keeping your routes RESTful
A while ago I blogged about RESTful routing in Rails. While Rails does help us in getting started with RESTful routing, it’s still very easy to lose your way and make a real mess of your routes.
To illustrate this, I’m going to recreate a problem I recently came across when working on RapidFTR – one of the social projects I’m involved in at ThoughtWorks.
Let’s see some code
To start off, I’m going to extend the example I was using in my last post about routing. I’m basically managing products – I’ve created a Product model, a Products controller and I have a single entry in my routes configuration.
When I run rake routes I get the following list of routes.
Right, now we have a new requirement – we need to be able to mark a product as a duplicate. We might have 2 admin users adding products and somehow we end up adding the same product twice. We don’t want to delete it (since some users might have bought the duplicate, etc) so we’re only going to mark it as a duplicate and link it to the non-duplicate product.
At the moment I can perform two actions on each product – view and edit. So I now want an additional action – mark as duplicate. This should take me to a page where I can select the non-duplicate version of the product.
Doing it the wrong way
Sounds easy enough – I’m probably going to need two additional actions on my controller – one to view the page and one to handle the page being submitted. To get started, let’s add 2 new routes.
Running rake routes reveals that we now have two additional routes.
Now I just need to add two method to my Products controller. This could be done better (moving some of the functionality into the Product model, for example) – I put all the code into the controller to make it clear what’s going on.
We can mark a product as duplicate! Hurrah! Are we done?
Doing it the right way
While this code certainly works, I don’t think it’s really the Rails way of doing it. It’s actually quite easy to end up with too many methods on your controllers – to avoid this, it helps to think in terms of resources, rather than models and controllers.
In this case, we can think of duplicates as being an additional resource. So instead of marking an existing product as a duplicate, we’re creating a new duplicate resource. As you’ll see, this enables us to use the regular REST verbs – new, create, edit, update, etc. Enough talking, let’s see how this would actually work!
I’m going to start off by changing the routes. Here I’m using a nested resource.
Now our routes look a bit nicer.
You’ll notice that our route now includes the :product_id, instead of just adding an id at the end of the route. This enforces the idea of a duplicate resource, which is available on product resources.
Now I only need to move my two actions to the newly created Duplicates controller.
More Examples
I first came across this concept in The Rails 3 Way – the common example seems to be in creating sessions. Instead of adding login and logout methods to the Users controller, we can think of sessions being a resource. So we might end up with a Session controller with new, create and destroy actions. Which actually makes a lot more sense.
If you would like to have a look at the code you can find it on Github. Happy coding.