RESTless design - do I need a session controller/model?

19 Aug 2006

Today the underground team of rorBB (an as-yet-unannounced Rails BB) saw that the application was almost completely RESTful - except in it’s login/logout actions on the users controller. It was Ben who pointed out that it was the single thing destroying our otherwise pretty URLs and we decided to do something about it.

I admit that I’ve been slow to get on the REST bandwagon. Why is that you proverbially ask? To tell the truth it’s largely because I CAN’T FIND A DEFINITION ANYWHERE. I went to Wikipedia, oracle of all wisdom, only to be told how it differs from RPC. I have a long, rich history of coding in PHP. That means I dont know shit about real programming and I had to then go and lookup RPC.

So forgive me if I act like this is the biggest, most amazing discovery ever. I’m just really happy that I discovered it before everybody had moved on to the Next Big Thing©.

So, regarding our controllers: the code we had was fairly generic. On the users controller there were the standard new, create, update, edit, etc. actions but then we had two others:

UsersController with RESTless actions:

    def login
      if request.post?
        user = User.authenticate(params[:name], params[:password])
        unless user.nil?
          session[:user_id] = user.id
          redirect_to users_url
        else
          flash[:error] = 'Username or password is incorrect'
        end
      end
    end

    def logout
      reset_session
      redirect_to :action => 'login'
    end

In other apps I’ve written this was pretty standard. Sometimes they’d be in a ‘users’ controller, sometimes in an ‘auth’ controller that contained just these two actions.

It wasn’t until I saw a lot of high-quality REST programming that I started to see there was a better way of doing things than just throwing actions in controllers that might fit.

So we talked about the possibility of overhauling our code to allow for a sessions controller that followed the basic verbs of REST. I figured it would take a while but might eventually pay off.

Boy was I surprised to find that even an idiot like me could put it together quickly. Here’s the final code from the sessions controller:

    class SessionsController < ApplicationController

      def create
        user = User.authenticate(params[:name], params[:password])
        unless user.nil?
          session[:user_id] = user.id
          redirect_to users_url
        else
          flash[:error] = 'Username or password is incorrect'
          redirect_to new_session_url
        end
      end

      def destroy
        reset_session
        flash[:notice] = "You are now logged out"
        redirect_to new_session_url
      end

    end

If you ignore a couple of slight improvements in the second piece of code you might notice that I ONLY RENAMED THINGS. Can you believe it? Our whole app fit back into REST with some very minor changes.

So, what’s the benefit of the sessions controller? Well, besides getting a free API (though you may never need to build a session over API) the tests got simpler, the routes.rb file got WAY, WAY simpler (back to just one line of code for all our controllers), and our team got just a little bit happier.

And who doesn’t like to be happy?

Update: By request, here’s the original and the modified routes.rb file:

    ActionController::Routing::Routes.draw do |map|

      map.connect '', :controller => 'forums'

      map.resources *%w[forums topics posts users]

      map.resources :users, :collection => { :login => :get }
      map.resources :users, :collection => { :login => :post }
      map.resources :users, :collection => { :logout => :post }

      map.connect ':controller/:action/:id'

    end
    ActionController::Routing::Routes.draw do |map|

      map.home '', :controller => 'forums'

      map.resources *%w[forums topics posts users sessions]

      map.connect ':controller/:action/:id'

    end

You might notice that even in the first file the routes are using the new map.resources command (normally used for this REST stuff).

  • Chris said: What's your one route? :controller/:action/:id Any chance of explaining a bit more about what REST actually *is*? Coming, like you, from a PHP-I-don't-know-crap-about-real-programming background I am loving the idea of REST, but only in the way I love the idea of Quantum Mechanics, which is, from a very long way away! Couple of paragraphs for the masses on what REST means to you?
  • Ben said: Right on Danger, looks good to me. @Chris This is what REST is to me. Intead of using keywords in the url for CRUD operations you use HTTP Methods. I addtion to the better know GET and POST, there are PUT, and DELETE. These map to CRUD. I think its Create/POST, Read/GET, Update/Put, Delete/DELETE. Basically you're utilizing the existing HTTP protocol to impose a convention on your controller. Your controller then becomes a resource. I think this comes from REST Web Services. The problem was that you had to know the names of the methods to call. But with REST you just have to know the resource, and the operations are part of the protocol. In Rails the more important permutation of this is the "love the CRUD" syndrome. To fit into REST, everything must be CRUD. Like with all things Rails this imposes a convention onto your controllers. Once you get used to it you can forget about it, and worry more about the domain.
  • Danger said: Right, the routing file might be helpful. I'll add that to the post.
  • Danger said: By 'one line' I meant that the routing file had one app-related line. The default is still there as is the home page.
  • Chris said: @Ben - Thanks for the overview, very clear, I've done a bit of reading and combined with your intro I think (I hope) I'm getting the general idea. One thing I still don't get though is how one is supposed to handle more complex models. For example, I have an app under construction at the moment in which the shopkeeper enters product details into a fairly standard 'new product' screen. Now as part of that screen they can create new instances of other types of model , for example a new tax, manufacturer or product option (like colour, size etc). Now, should the product entry screen simply be a collection of compartmentalised little models, so Tax with a CRUD interface, Manufacturer with a CRUD interface, etc all ajaxified together? The 'big beast' being constructed out of simple little CRUD blocks as it were? Or am I still missing the point massively? (which wouldn't be the first time!) PS - Thank you Danger for the very interesting post, I jumped straight into questions and forgot to thank you in my first post :0)
  • Danger said: I think I need to be thanking you Chris - you're questions have made this post far more useful than it was on it's own. I'm developing an app with the same issue. On the user signup screen (controller: user, action: new) there's a form that has the user's details, credit card details (I'm calling the model 'subscription') and an optional other model. The way I chose to handle this was to choose which model would be the most important one on the page (user) and use that controller. Then, in my controller's 'create' action I have it handle all the models. It's true that this deters slightly from the strictest interpretation of REST but it still works pretty well. It's a LOT better than what I had before where there was a 'signups' controller handling all kinds of users/people each with extra models thrown in. So with your shopkeeper app I'd suggest the same approach. If you want to see my code I'd be happy to post it/send it to you.
  • Chris said: A code example would be great to lookover and see how you're structuring things, (I added email so you can post or send depending on if you want the code public or not). I watched DHH's keynote and have been trying to re-examine my domain for 'missing' relationships, the problem is it's easy to spot them when someone else points them out, less easy when you have to have the eureka moment yourself! :D Since my app is an 'at-my-own-pace' project I think I'm going to go back and try restructuring it RESTfully throughout (it's mainly a patchwork of code so far anyways) Do you know of any open source rails apps that have been done using REST principles? It would be good to download and pick through their code to see their approaches
  • Chris said: Found this which gives an example REST app, too late for me to start playing with it tonight but thought I'd post the link: - http://randomoracle.org/2006/08/02/restblog-an-example-of-the-new-rest-and-crud-rails-style (PS - on a side note this is the reason I prefer ID's for blog post links, makes the links a lot smaller, and more flexible for future change - sorry it screws with your design!)
  • Danger said: My beautiful blog - you broke it! No worries Chris, thanks for the link.
  • Ben Kittrell said: BTW, this inspired my to write an article http://garbageburrito.com/blog/entry/35
  • Ben Kittrell said: BTW, this inspired me http://garbageburrito.com/blog/entry/35

Please if you found this post helpful or have questions.