SignOut: Part 1

Posted by Koz Tuesday, February 13, 2007 14:26:00 GMT

Randy Schmidt sent us a project of his called SignOut, which he wrote for work and which “lets people sign in and out for meetings/lunch from their browser.” It’s a pretty simple application; an administrator first uses the admin interface to add the employees to the system, and employees can then go in and mark themselves as “away” or “not away”. (The repository is here, and we’ll be reviewing revision 47…which I can’t link to directly, unfortunately.)

As it stands, there are only a few things about the application that make me cringe; for the most part I have no complaint with how it was architected. So instead of pointing out the little nitpicks, I thought this might be a prime opportunity to demonstrate the thinking processes that go into taking an existing non-RESTful application, and redesigning it as a RESTful app.

What is REST?

REST is a term that is getting a lot of traffic these days. At the risk of grossly oversimplifying, it basically refers to a way of writing your app such that the underlying resources are exposed via a consistent, URL-based interface formed from the basic HTTP operations of “get”, “post”, “put”, and “delete”. (I have absolutely no doubt that some will take issue with this definition; please feel free to expound on your own view of the technique by commenting below.)

Sound like a lot of work? It’s not, really, especially if you do it RESTfully from the get-go, instead of reworking an existing application after the fact. But even if it is easy, why should you bother? That’s a valid question, and one worth addressing before going into this any further.

Why REST?

First of all, a RESTful application will generally have more controllers, but each controller will be smaller. Smaller objects are happy objects. Smaller objects are easier to maintain. Smaller objects are easier to refactor. Smaller objects are, quite frankly, better than bigger objects.

Secondly, a RESTful application in Rails is guided by convention, which means all RESTful Rails apps will share some basic things, like the format of their URLs. This means that we can now write a framework for consuming RESTful web services, and have that framework easily communicate with any application that conforms to those Rails conventions. And guess what? That framework already exists, and is called ActiveResource.

Thirdly (and I openly acknowledge that this is largely a matter of opinion), RESTful apps look a lot cleaner on the inside. Each line of code is nicely groomed and placed on it’s shelf. Order prevails. Chaos quails.

Convinced yet? Alrighty then. Let’s move on.

Identifying Resources

So, when writing a RESTful app, the first step is to identify the resources that you want to expose, and then create at least one controller for each of those resources. It is tempting to think of your models as this set of resources, but that’s not actually correct. In the SignOut application, there is only one model: Employee. But by looking at the actions of the two existing controllers, I was able to extract three different resources.

As the application is written, here’s the list of all of its actions:

AdminController
  • #index: show all employees
  • #add: creates a new employee record
  • #edit: displays the employee edit form (if called via GET) or updates a specific employee (if called via POST)
  • #delete: deletes an existing employee
EmployeeController
  • #set_view: just sets a :view key in the session, which determines whether the status view should be brief or verbose.
  • #status: shows a list of all employees in the system, along with their status. From this view, employees may mark themselves as “away”, or “available”.
  • #periodic and EmployeeController#search: returns all employees matching the given criteria. These are pretty much the same operation, with the difference that #periodic is called every 10 seconds (so the view remains up-to-date).
  • #set_away_info: returns the HTML form (via AJAX) that employees may use to make themselves as “away”. It allows things like the reason to be specified, along with the state.
  • #set_away: updates the Employee record with the information specified by the #set_away_info form.
  • #come_back: clears the away status information from the Employee’s record.

First off, note that the AdminController is the part that actually controls the employees, creating, reading, updating, and deleting them. Thus, in my refactoring, the AdminController gets renamed to EmployeesController (note the plural; controllers in a RESTful Rails app are named for the plural of the resource they control).

Second, note that the EmployeeController in the original version doesn’t really control the employees, it controls the employees’ statuses. So, I add a StatusesController. Lastly, note the EmployeeController#set_view action. This manipulates neither the employee, nor an employee’s status. It changes the view of an employee’s status, which suggests a third resource: views, which will be managed by a ViewsController.

Koz says:

Status is a fairly generic name for the messages about a person’s status. Judging by the application the only thing these are used for is a message indicating why someone’s away. Why not call them AwayMessages?

Also, instead of relying on a value in the session, you could move the choice of minimal/full into the URL, making your application more stateless, and remove the need for an entire controller.

So in my refactoring the three controllers and their actions are:

EmployeesController
  • #index: show all employees
  • #new: display the “new employee” form
  • #create: create a new employee
  • #show: display the “edit” form for a specific employee
  • #update: save an existing employee’s data
  • #destroy: delete an existing employee
StatusesController
  • #index: show the statuses of all employees. Specify a query parameter of :q for searches that filter this view.
  • #show: show the away-info form for a specific employee
  • #update: mark an employee as away
  • #destroy: mark an employee as having come back from being away (“destroy” the away status)
ViewsController
  • #show: sets the requested view in the session, and redirects back to StatusesController#index.

I’ll go through and implement these in subsequent articles, but don’t let that discourage you! Go ahead and think about how you would reimplement some of these yourself. Perhaps you’d even break the existing application into different resources than I did; feel free to try it out and see how it works.

Comments

Leave a response

  1. Morgan RoderickFebruary 13, 2007 @ 02:38 PM

    This should be a very interesting read :)

    I really enjoy the stuff posted on The Rails Way … there’s always some stuff to consider.

    Just a little question, isn’t the rails convention to name controllers with singular names?

    I.e.

    StatusesController => StatusController?

  2. David DemareeFebruary 13, 2007 @ 03:31 PM

    Morgan: Regardless of whether it was a convention before REST (I never really followed it, just because /articles/show/## just seemed less awkward than /article/show/##), the REST features in Rails 1.2.1 assume that resource-oriented controllers are plural, e.g. map.resources(:statuses) => StatusesController.

    Rails 1.2.2, however, does add a new feature allowing for singleton resources, such as map.resources(:session) => SessionController. So instead of the amazingly awkward DELETE /sessions/#### to log someone out of a session, I can just use DELETE /session.

    Of course, this is more than just a change in naming. A singleton resource (from all the examples I’ve seen and the limited uses I’ve found for it so far) seems like something of a compromise between old-school RPC-style actions (like /login or /logout, which could be replaced with GET /session/new, POST /session and DELETE /session) and the new RESTful way of doing things.

  3. Luis de la RosaFebruary 13, 2007 @ 05:41 PM

    Looks like the start of a good series. We need to discuss good REST practices more as a community.

    Why no StatusesController.new/create? It would take an employee instance, and the away message. It would create a new away Status and associate it with the employee.

  4. JamisFebruary 13, 2007 @ 05:48 PM

    Luis, you’re right, StatusesController#create makes more sense than update. That’s probably the better way to do it. However, the need for #new (or #edit) is obviated since statuses have no permalink; we can just use #show to get the form that will be used to create the new status. Use of #new and #edit should be avoided whenever possible; they are really just hacks to fit things that browser-based UI’s need into a RESTful URL scheme. An edit form or a new record form are not, themselves, resources; they are merely aids to the user in composing the data that will form the subsequent create or update request.

  5. Adam T.February 13, 2007 @ 06:27 PM

    My suggestion isn’t RESTful, but if you decide NOT to go down that path (although I do recommend you do), I recognized a place in the description (although I haven’t checked the source, so forgive me if it’s inaccurate) where one might be able to refactor to simplify the controller just by adding another route. Consider the following URLs in the original application:

    http://yourapp.com/employee/set_away/1 http://yourapp.com/employee/come_back/1

    While the URL structure is verbose, which is usually a good thing, you can simplify the underlying actions by doing something like this in your routes.rb file:
    1
    2
    3
    4
    
    map.with_options(:controller => "employee", :action => "update_status", :id => /\d+/) do |obj|
      obj.employee_set_away("/employee/set_away/:id", :status => "0")
      obj.employee_come_back("/employee/come_back/:id", :status => "1")
    end
    That way, you can just check the params[:status] variable in the update_status action instead of having two separate actions handle the call.

    Again, if you’re looking to gut and refactor your code entirely to go RESTful, this might not be the path to go down, but if you’d like to go at it from a different angle, it might be something to consider.

  6. Daniel HaranFebruary 13, 2007 @ 06:29 PM

    Jamis – How about having the show action for a user display the status (like in many IM clients)? A drop-down with your current status selected, you can select “Available”, and the form is posted to update.

  7. JamisFebruary 13, 2007 @ 07:21 PM

    Daniel, that sounds reasonable. For this series of posts, though, I’m not adding any functionality over what already exists in the app; I’m only refactoring what is there into a RESTful architecture.

  8. Sam AaronFebruary 14, 2007 @ 12:23 AM

    Hi Jamis, well written, thanks for taking the time for this :-)

    My only question is regarding the ViewsController. You’re using the show action to change the state of the application (set a session variable). Using my meagre knowledge of ‘The RESTful Way’ (maybe an idea for a new best practice site!) I thought that URLs that changed state shouldn’t be called by a HTTP get request.

    I’m sure I’m wrong, but to my mind shouldn’t it be something like a post call to update?

  9. HughFebruary 14, 2007 @ 02:46 AM

    I like the idea of peer reviewing peoples apps because its such a great learning source for everyone. Maybe a mechanism needs to be setup (ie. a rails app) that allows people to submit their app for peer review and then have peers post their deconstruction/review.

    Review quality rating could be a feature/incentive too. Integration with workingwithrails.com profiles would also be really cool.

  10. JamisFebruary 14, 2007 @ 04:12 AM

    Sam, good observation! You’re right—a GET that changes state is not a good idea. Since the user viewing the application can only have a single view active at a time, perhaps it makes sense to use a nested singleton resource (”/view”) that you change by sending a PUT request to it. I do like that better.

    Hugh, sounds like you’ve found yourself a project. :)

  11. Anthony RichardsonFebruary 14, 2007 @ 06:40 AM

    I can’t help but think in the context of how a user perceives the system that a #Destroy doesn’t make sense to the user. It sounds way more destructive that simply “hey, honey, I’m home”. If you want to keep the model of status present = away and not present = in the office then I would consider renaming #Destroy to #Clear.

    But that is just a personal preference really. At a technical level #Clear would imply a “reset to default” rather than destroy. So I guess I’m uncomfortable with the modelling of status as an object that is or isn’t present.

  12. Morgan RoderickFebruary 14, 2007 @ 10:29 AM

    Plural is new the blue, got it! ;-)

    Just need more time to get new knowledge crammed into my skull, and after that, get my project on 1.2.x

  13. BenFebruary 14, 2007 @ 02:13 PM

    Thank you so much for this. I spent a fair amount of time browsing round for an article that would help me work through the thinking process of developing a RESTful application. Articles such as yours are special because they talk about the reasons why things are done rather than simply how.

  14. JamisFebruary 14, 2007 @ 03:03 PM

    Anthony, the term “destroy” is not visible to the user, even in the URL. Using REST to design the app, that term is merely the name of the action; the URL itself will be the name of the resource (“statuses”), with the HTTP verb being used to select the appropriate action.

  15. Mark CareyFebruary 14, 2007 @ 04:09 PM

    I’ve seen all the arguments for refactoring to REST and in many cases I can see that effort being justified. The question I have is: If an application is not intended to be consumed as a web service, should we be refactoring to REST? I know it can reduce complexity, but there are times when complexity is a necessary evil.

  16. JamisFebruary 14, 2007 @ 04:29 PM

    Mark, web-service production is definitely not the only reason for refactoring to REST. If a reduction in complexity isn’t enough of a reason for you, then how about improved maintainability? Yes, “there are times when complexity is a necessary evil”, but that doesn’t mean you ought to beat yourself over the head with it if you can take a route that simplifies things at all. And improved maintainability pays BIG dividends in the long run.

    Now, should every web application be written RESTfully? I won’t go so far as to say that, since I’m still feeling my own way around this new (to me) area. However, I will say that I’ve yet to see an application that couldn’t benefit from being architected RESTfully.

  17. Anthony RichardsonFebruary 15, 2007 @ 02:50 AM

    Thanks for the clarification Jamis.

  18. Sam AaronFebruary 15, 2007 @ 09:56 AM

    Hey Jamis, nested singleton resources sound really interesting.

    Didn’t they only just come along… I’d really love it if you were able to cover singleton resources in further posts. For example, I’m currently not sure if a singleton resource is a singleton for the perspective of a user, or for the entire application. I’m also interested in how both the model and route(s) would look like.

    This is all good stuff, and prompting some really interesting debate. Thanks :-)

  19. John TopleyFebruary 15, 2007 @ 04:06 PM

    I view the REST style as just another useful convention. So I can dive into the code of a RESTful Rails app and know straightaway which action is going to be updating an object, in the same way that with Rails I know straightaway where to go to look for the controller code.

    Having spent the past day effectively reverse engineering a Struts application in order to be able to modify it, I can say that anything that reduces the amount of digging around in the code you have to do is great!

  20. Sam AaronFebruary 15, 2007 @ 04:53 PM

    Just to further the discussion/introduction of REST in general, Ryan Daigle has written a truly beautiful 3 page introduction to RESTful development for Chapter 6 of the O’Reilly Rails Cookbook.

    Totally worth 3 pages of anybody’s time in my book :-)

  21. Sandro PaganottiFebruary 16, 2007 @ 07:55 AM

    Thank you for this article ! Now It’s time for me to write my first RESTful application ! :)

  22. JeffFebruary 21, 2007 @ 07:42 PM

    I think there is a common misconception that the idea of REST is to have a one-to-one relationship between models and controllers. So your section on identifying resources was refreshing and very valuable to me. Thanks.

Comment