Error Handling with Blocks

I recently built an API with Sinatra and ran into a recurring challenge when dealing with resource-specific routes (like /objects/:id). The first thing I had to handle in each of those routes was whether or not a record for both the resource type and id existed. If it didn't, I wanted to send back a JSON response with some meaningful error message letting the API consumer know that they asked for a certain kind of resource with an ID that didn't exist.

My first pass looked something like this:

get '/objects/:id' do |id|
  object = Object.find_by_id(id)
 
  if object.nil?
    status 404
    json(errors: "Object with an ID of #{id} does not exist")
  else
    json object
  end
end

put '/objects/:id' do |id|
  object = Object.find_by_id(id)
 
  if object.nil?
    status 404
    json(errors: "Object with an ID of #{id} does not exist")
  else
    if object.update_attributes(params[:object])
      json object
    else
      json(errors: object.errors)
    end
  end
end

Seems ok, but there would be a lot of duplication if I had these if/else statements in every resource-specific route. Lately, I've looked for common if/else conditionals like this as an opportunity for method abstraction, particularly with the use of blocks and yield. The following methods are an example of this kind of abstraction:

def ensure_resource_exists(resource_type, id)
  resource = resource_type.find_by_id(id)
  
  if resource.nil?
    status 404
    json(errors: "#{resource_type} with an ID of #{id} does not exist")
  else
    yield resource if block_given?
  end
end

Then the initial example would look something like:

get '/objects/:id' do |id|
  ensure_resource_exists(Object, id) do |obj|
    json obj
  end
end

put '/objects/:id' do |id|
  ensure_resource_exists(Object, id) do |obj|
    if obj.update_attributes(params[:object])
      json obj
    else
      json(errors: obj.errors)
    end
  end
end

It hides away the distracting error case handling and gives us a readable, declarative method body.  Next time you find yourself dealing with repetitive error cases, use blocks like this for great justice!

Ryan is a developer in Viget's Falls Church, VA, HQ, where he believes in being a liason for both the technical and non-technical. He builds elegant tools for clients such as Bozzuto and Millitello Capital—as well as internal tools that we use at Viget every day.

More posts by Ryan