We have recently started to use a pattern on my project that I am currently in love with. While it is probably too early to call this a "Do This or Question Why You Do Not", I can see it rapidly becoming that way.

The typical Rails controller

The Rails controller that we typically see is something along the lines of the code below. In this code we are using some straight forward patterns of accessing objects from a database, showing them (through a template), checking to see if our new Post object is valid and handle the good and bad cases in an if statement. This is very basic Rails code that any Rails developer would be able to easily recognize and use.

def show
  @post = Post.find(params[:id])
end

def create
  post = Post.new(params[:post])
  if post.save
    flash[:success] = t(:post_created)
    redirect_to post
  else
    flash[:error] = post.errors.full_messages
    render 'new'
  end
end

Rails controllers for API endpoints

However, this is not entirely RESTful and can even hurt in the world of creating APIs. Let us assume, for the moment, that we are creating these same endpoints for a single page Javascript client. The code would look similar to the following (I'm assuming a templating language like rabl):

def show
  @post = Post.find(params[:id])
end

def create
  @post = Post.new(params[:post])
  unless @post.save
    head :unprocessable_entity
  end
end

Here we are doing much the same work and are a little more restful. We are returning 200 and 422 and 500 error responses. This is not quite restful, though. We should really handle the situation in show where an invalid id is being requested.

def show
  @post = Post.where(params[:id]).first
  head :not_found unless @post
end

And, now, we are actually going to populate these posts with images from Gravatar. We need to fetch some links.

def show
  @post = Post.where(params[:id]).first
  @gravatar = GravatarService.fetch_for(@post.user)
  head :not_found unless @post
end

But, it is possible for the Gravatar service to time out or explode in some expected way that returns a 500 error. That 500 error is actually right in its class (Server Error) but wrong in its specificity (it is probably a 503 Service Unavailable error). So, we need to be a little better about handling that error

def show
  @post = Post.where(params[:id]).first
  begin
    @gravatar = GravatarService.fetch_for(@post.user)
  rescue GravatarServiceError
    head :service_unavailable
    return
  end
  head :not_found unless @post
end

And, now we're off to the races. Our clean PostsController#show and PostsController#create has turned ugly.

Use Exceptions

So, we're going to use exceptions to clean this up correctly. Here's how I would write those in the new pattern

def show
  @post = Post.find(params[:id])
  @gravatar = GravatarService.fetch_for(@post.user)
rescue Mongoid::Errors::DocumentNotFound # Or AR equivalent
  head :not_found
rescue GravatarServiceError
  head :service_unavailable
end

def create
  post = Post.new(params[:post])
  if post.save
    head :created
  else
    head :unprocessable_entity
  end
end

And, if I really had my druthers (which I do on my project), we'd be happier to use a Service layer instead of AR.new/AR.create.

def create
  PostService.create(params[:post])
  head :created
rescue PostServiceError
  head :unprocessable_entity
end

So, we are now looking down the barrel of a very RESTful API endpoint. It is very clean and describes exactly the response that we expect based on how the server is behaving. If we cannot create a new Post (for whatever reason.. we don't know what it is here), we return a response of 422 Unprocessable Entity to the client.

Again, I am really liking how clean this is. However, I can see some issues with it: we are currently using this for an API-only area of our application. It looks to be working wonderfully for us. However, if we extend this to all parts of the application I wonder about spaghetti exceptions or introducing exceptions in one part of the code that effects multiple endpoints.

If we maintain this in only the API part of the application, we have a conflict of styles if I'm using a service in both parts of the application. One will not expect the service to throw exceptions and the other will expect it.

All in all, though, this seems to be a great start to correctly handling many different return codes from an HTTP resource. If we're trying to be more RESTful with out resources, we need a way to handle different kinds of errors at various points in the process and exceptions are looking to be a good way to solve it.