Refactoring REST: searching for an abstraction
Fri Feb 02 22:12:00 UTC 2007
It’s been a while since I blogged. Sorry about that. Needless to say, it’s been a hectic couple of months. Birthdays, Christmas, New Year, a new job; all found their way towards keeping me from that big shiny text-box in Mephisto’s admin interface. For those still waiting for part two of my RSpec tutorial – and I know I’ve said this several times already – it will be coming; I’m just not sure when yet. There are some interesting developments afoot in the world of RSpec and I’ll be waiting until these make it into the next major release before tackling the article.
In this article, I’d like to talk about REST. The world of REST and resources has been a part of the Rails edge for a while now, since the last RailsConf in fact and whilst skeptical at first, I’ve really come around to the concept of RESTful resources. Whilst I still feel that it is not always suitable I’ve found that the best approach is combination of RESTful controllers and RPC-style controllers where necessary. One of the great things about the RESTful approach is that it really brings the idea of good object-oriented design – specifically the idea of objects having a clear focus on a particular task – to Rails controllers. The downside is that normalization of REST-style controllers has led to, in my experience, a lot of repetitive code. And as every good programmer knows, duplication should be hunted down and destroyed whenever possible.
The wonderful world of REST
The RESTful resources concept focusses on seven core actions: index, show, new, create, edit, update and destroy. These actions expose four core behaviours of your ActiveRecord objects through some of the various verbs available in the HTTP spec: retrieval (GET), creation (POST), updating (PUT) and deletion (DELETE). If you’ve been working with RESTful resources for any length of time you will find that the code for each of these actions tends to look very similar largely due to the use of convention over configuration that Rails favours heavily.
Whilst looking at refactoring portions of our company intranet, it struck me that such duplication was rife and I set about eliminating it. Ever mindful that the best abstractions are extracted (just like Rails was), all of the ideas and code that follows have been extracted directly from our intranet application.
I’m not the only person who has recognised the duplication that tends to result from taking the RESTful approach and consequently some solutions for making development of RESTful applications exist already in the form of generators and scaffolding.
The problem with generators is that they don’t actually solve the problem of duplication in the code – they simply make it easier to get up and running by generating boilerplate code for you to modify. But once the code is in place it is still something to be maintained.
The problem with scaffolding is the same problem that plagues the scaffolding that originally came with Rails – it provides a very generic abstraction with little in the way of flex points which means you usually end up overriding the scaffolded methods anyway. You can usually generate scaffolding directly into the source itself but this has all of the same problems as generators.
An alternative approach
My approach was to first attempt some small, fine-grained refactorings on my controllers such as Extract Method to make sure that the REST actions focussed on their core responsibilities and delegated to template methods for specific behaviour. A common example is the create action, which usually looks something like this:
def create @user = User.new(params[:user]) if @user.save flash[:notice] = 'User created successfully' redirect_to user_url(@user) else flash[:error] = 'User creation failed' # some specific error handling render :action => 'new' end rescue SomeException # exception handling here end
After extracting functionality to template methods, I ended up with code like this:
def create user = User.new(params[:user]) if user.save handle_successful_create_for user else handle_failed_create_for user end rescue handle_exception_for_user end
protected def handle_succesful_create_for user flash[:notice] = 'User creeated successfully' redirect_to user_url(user) end
def handle_failed_create_for user @user = user flash[:error] = 'User creation failed' render :action => 'new' end
def handle_exception_for user # exception handling here end
The same approach is taken for other methods and after making this refactoring I am now left with identical RESTful actions across my application. Some might feel that the next step was to extract a super-class but there was still some variation that hadn’t been eliminated – each controller worked with a different model. I was also somewhat uneasy about using inheritance to eliminate duplication – it is often a good idea to favour composition where possible1. However a solution that uses composition is awkward because the object model for Rails controllers is abstracted in the framework. Fortunately Ruby provides us with an alternative that allows us to avoid modifying the inheritance tree in the form of mixins. Using include and extend we are able to add functionality to classes and their instances. This is the approach I decided to take but that still left that pesky model variation to deal with.
Refactoring further: extending the DSL
Once again Ruby comes to the rescue with it’s excellent meta-programming functionality. We take advantage of that functionality all of the time in Rails when we use it’s macro-style class methods; filters and ActiveRecord associations and validations are great examples. Using meta-programming, it was relatively easy to come up with a small DSL for exposing resources with a controller. Here is the first part:
class UsersController < ApplicationController expose_resource :user end
As it clearly states, this tells the UsersController that we want to expose our User model. We can use this value throughout whatever code we write to generate each action. The next thing to tackle was how to generate these actions at runtime. I studied the scaffolding code that comes with Rails but wasn’t really comfortable with treating code as a template and running it through an eval statement. I decided to take a more object-oriented approach and created a series of command objects to represent each action. This solution was easy to test and extend where necessary.
Finally, there needed to be some way of specifying what actions RESTful actions you want to expose – you might not want all seven. This lead to the second part of the DSL:
class UsersController < ApplicationController expose_resource :user, :except => [:update, :destroy] end
Of course, you might only want a few of the actions:
class UsersController < ApplicationController expose_resource :user, :only => [:index, :show] end
So where does that leave us? All of the core functionality is in place and all that the developer has to provide is an implementation of the various callback handlers. With some sensible default implementations to these handlers built-in, the developers life is made even easier.
One size does not fit all
It is worth noting that there isn’t a callback handler designed for every situation. Besides the sheer difficulty in guessing all of the flex-points that a developer might need, it would require a lot of bloated unnecessary code just to cater for edge cases. The above abstraction was never designed to cover all cases and I don’t believe any abstraction ever can. However it does make it possible to add functionality quickly at least 80% of the time and it handling the remaining 20% of cases is as simple as the developer providing their own hardcoded action.
All of the code discussed above is available as a Rails plugin that I’ve called restful_exposure. It can be checked out with Subversion at the following URL:
A README is bundled and all code is released under the MIT license.
At present, most of the callback handlers are lacking implementation. This code is only a couple of days old and is not considered production-ready by a long shot. For starters, it is completely lacking any form of test coverage. I used our existing test coverage for our intranet to guide my way during refactoring but it is my plan to add a good suite of RSpec specifications. This isn’t even live on our intranet yet and currently lives on a branch of our intranet code.
In addition to the above functionality there are a few other features that came about as a direct result of extracting this from our intranet application. The first is the introduction of a parameterized index action. It is common to have some alternative collection actions as well as index – archives, search and recent are good examples. They both retrieve a collection of objects but the number of items retrieved, their order and how they are filtered varies. This variation is encapsulated by the index parameter which is passed into ActiveRecord::Base#find as its options hash. This lets you do things like this:
def recent index :limit => 5 end
Another thing that it was important to handle was nested resources, representing belongs_to/has_x relationships. One of the impacts this had on the code was in the creation of new objects – the new action had to ensure that the new object created (to be bound to the new form) was linked to its parent object. Getting the parameters for this is made easy by convention and URL parameters made available by nested resource routes so it wasn’t a problem to extract. This lead to the first option for expose_resource:
class FilesController < ApplicationController expose_resource :file, :nested_under => :folder end
The second option came from the need to link new objects to their parent object when working with tree-like nesting of a single model using acts_as_tree. Support for this is built-in using the as_tree option:
class FolderController < ApplicationController expose_resource :folder, :as_tree => true end
I’m well aware that the Rails community is an opionated one (naturally) and that this might not appeal to some people. Some people might perceive this as “too much magic”. Personally I’d disagree – I think this is no more magic here than has_many and acts_as_foo. It has been extracted from a real working application and I think that is important. I have a “3 strikes” rule when it comes to abstractions like this – if I’ve done the same thing three different times or on three different apps then its time to look for that abstraction and reduce the amount of code that needs maintaining. This “3 strikes”/abstract through extraction approach is exactly the same one that lead to my crypted_authentication plugin.
If you are working with RESTful resources and have found yourself typing the same thing over and over again, please do checkout the plugin and give it a try. I repeat, this is not production-ready but I’m really interested in people’s feedback on the approach and any suggestions. Do dig into the source to get an understanding of how I’ve approached the implementation. I’ve tried to avoid Ruby-fu “magic” wherever I can in favour of traditional object-oriented approaches.
Are there any areas such as the nesting/tree-like structures above that you find yourself doing often that would be a good fit for abstraction? What do you think of the API? Comments are open!
Update 05/02/2007: As per Steve’s suggestion in the comments below, I’ve modified the interface so that it uses just the single expose_resource call, with :only and :except options (:all by default) as per the Rails filters API. I’ve updated the code samples above to reflect this. expose_resource_actions has been removed.
1 It is a generally accepted object-oriented design rule – and one that I agree with and try to adhere to – that composition leads to less coupling and polution of the inheritance tree (which should really only really be used when objects really are of similar type). I also find it leads to more easily testable and elegant solutions.On Google.