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!

Sign up for The Viget Newsletter

Nobody likes popups, so we waited until now to recommend our newsletter, a curated periodical featuring thoughts, opinions, and tools for building a better digital world. Read the current issue.