02 February 2007
Refactoring REST: searching for an abstraction
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.
Existing solutions
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:
svn://lukeredpath.co.uk/opensource/plugins/restful_exposure/trunk
A README is bundled and all code is released under the MIT license.
Feedback
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.
Return to home page | Check out my tumblelog
21 Comments on this article
Return to home page | Check out my tumblelog
Commenting on this article is now closed
1. Comment by Palo on 03 Feb 2007 at 08:02
Great job Lluke! there was a bad smell while working with RESTful controllers, but I never paid attention to that. I hope you turn this in production-ready code.. I’d like to use it in my projects.. keep rocking! Paolo
2. Comment by Steve Tooke on 05 Feb 2007 at 10:02
Good stuff! I’ve just finished my first restful application, and have started a second and this exact thing has been nagging me. I haven’t had chance to look at the code in much detail, but I like the way you’ve approached it.
My one comment on the API is that I think it would be a little cleaner if you only needed to call one method. Perhaps you could borrow from the rails filter methods and have something like:
expose_resource :user, :only => [:new, :index, :show]
or
expose_resource :user, :except => [:destroy]
and then default to :all if neither except or only was included?
3. Comment by Luke Redpath on 05 Feb 2007 at 13:02
Hi Steve, yeah that works for me.
4. Comment by mathie on 06 Feb 2007 at 09:02
Great work! It’s been one of the things that’s been bugging me too about RESTful controllers. And I’ve been seeing several people trying to tidy up these new controllers in different ways, so I’ve been doing my lazyweb thing and waiting for somebody else to do it right. :-)
Now all I need is for somebody to tidy up the restful _path/_url widgets so that they work cleanly with nested resources and I’ll be both lazy and happy! :)
5. Comment by Sam Aaron on 07 Feb 2007 at 23:02
Hey Luke, I totally agree with you that some kind of abstraction is necessary for bog standard restful controllers. This looks like a sensible approach :-)
I’ve recently seen a whole bunch of similar attempts, I think that this is an interesting space right now. Hopefully the various abstractions will start to converge at some point.
6. Comment by Thijs van der Vossen on 16 Feb 2007 at 20:02
Looks like this could be useful. We’ll give it a spin asap.
7. Comment by Jamie Hill on 06 Mar 2007 at 23:03
Luke, we use a similar plugin in-house at SonicIQ, I will soon be starting a series of tutorials and realeasing some of the plugins that we use.
In the meantime you can have a play with define_crud found at: http://svn.soniciq.com/public/rails/plugins/iq_define_crud which follows the conventions I blogged about in simply-restful-the-missing-action
This plugin also supplies some default views (which works in 1.2 however not on edge due to a recent change in the way template routes are handled – fix on it’s way).
A quick example:or if you only want the®ead and (U)pdate of CRUD then:
I will do a proper tutorial soon. You may notice that there are no tests as yet… the shame! We’ve been using this since before we got right into TDD so if anyone want’s to contribute some tests or bug fixes, please feel free.
8. Comment by Meekish on 21 Mar 2007 at 18:03
Great to see you blogging again. I too am still waiting for that follow up on RSpec. But at the rate the API is changing right now, I can understand why you’d wait.
As an aside, your blog is one of the few that remains solid when making the text huge (I have a 30” Apple Cinema). Hooray for liquid layouts!
9. Comment by Anton Kovalyov on 05 Apr 2007 at 22:04
Luke, you have a parameter filter_options to a block in restful_actions.rb:46 and it generates a warning: multiple values for a block parameter (0 for 1).
I commented it and now everything is good. What was it for?
P. S. Rails 1.2
10. Comment by Evgeny Zislis on 28 Apr 2007 at 17:04
When I look at code like this : class UsersController < ApplicationController expose_resource :user end
It strikes me odd, that you need to specify the name of the resource. Since in Rails where “convention over configuration is all its about, by default you know that the UsersController is the one handling the User resource.
How about : class UsersController < CrudController end
And you create the CrudController somewhere, that extends ApplicationController. Even though the comment by Jamie Hill is also about removing the word” from the controller, if its there by convention already.
Taking it to extremes … why have a controller at all if all the crud/rest methods are defaults (without modifications).
11. Comment by James Darling on 22 May 2007 at 07:05
Is your svn repo OK? svn://lukeredpath.co.uk/opensource/plugins/restful_exposure/trunk svn: Can’t connect to host ‘lukeredpath.co.uk’: Connection refused
12. Comment by Tomasz Gorski on 28 May 2007 at 11:05
Thanks for very interesting article. btw. I really enjoyed reading all of your posts. It’s interesting to read ideas, and observations from someone else’s point of view… makes you think more. So please keep up the great work. Greetings
13. Comment by Serg on 15 Jul 2007 at 12:07
Looks like this could be useful. We’ll give it a spin asap.
14. Comment by gry on 23 Jul 2007 at 12:07
Hey its good to see you blogging again I was waiting for this moment greetings!
15. Comment by Ogłoszenia on 23 Jul 2007 at 12:07
Thank You for another very interesting article. It’s really good written and I fully agree with You on main issue, btw. I must say that I really enjoyed reading all of Your posts. It’s interesting to read ideas, and observations from someone else’s point of view… it makes you think more. So please try to keep up the great work all the time. Greetings
16. Comment by Noosa on 13 Aug 2007 at 02:08
Great to see you back again Steve. Any update on the part two of your RSpec tutorial?
17. Comment by Steve Roth on 26 Aug 2007 at 08:08
You stopped blogging for a little, I stopped reading for a little longer and here’s what happened – I missed so much. I am right now working on my first restful application and this thread would be of great help for me. Thanks to all who contributed.
18. Comment by Muskelaufbau on 05 Sep 2007 at 13:09
Thanks man, just what I was looking for. Worked like a charm Thanks so much…
19. Comment by Addict on 12 Sep 2007 at 02:09
As always great blog from great guy thx php lover
20. Comment by 搬屋公司 on 20 Sep 2007 at 10:09
Thanks for very interesting article. btw. I really enjoyed reading all of your posts. It’s interesting to read ideas, and observations from someone else’s point of view… makes you think more. So please keep up the great work. Greetings.
21. Comment by Saving Silverman on 17 Oct 2007 at 07:10
Thanks. Good stuff