In building Sinatra or Padrino apps, I often write code like
get '/resource/:id' do
resource = Resource.find(params[:id])
return status 404 if resource.nil?
# ..
end
Or actually, I like to
flash[:warning] = "A Resource with id #{params[:id]} coud not be found".
redirect back
I think in Rails this is modeled via "Ressources". My controllers tend to be mixed, part of the routes will depend on a resource id (which will be fetched from whatever db), other do not.
Which patterns can be used to dry this up? I know of before
handlers
(pseudo code, but have not seen a really smart implementation - it is sure out there somewhere!)
before "*" do
@resource = Resource.get(params[:id])
redirect_with_flash if @resource.nil?
end
or to put similar code in a method to call first in each route with that requirement.
Still, I see similar pieces of code in nearly every Sinatra tutorial, isn't there a better option? I am especially interested in a padrino-approach to that, if I overlooked it.
Here is how the code I would like to have could look like
MyPadrinoApp::App.controllers :user do
associated_resource = User
associated_resource_error_flashs = { "404": "A User with %s could not be found" }
get :show, :with => :id, :resource_bound => :user do
render '/user/show' # in which @user is available
end
end
If you want to stop processing a request as soon as you know the request is invalid / an error occurred you can use Sinatras halt
. It stops further processing immediately and allows you to define a http status code and a message to display, if your App is not about a REST API you can define the corresponding error template.
In your example the request becomes invalid because the requested resource doesn't exists. To answer with a 404 is correct and you can tell halt
to use this status code in the response.
A very simple implementation can look like this:
get '/resource/:id' do
resource = Resource.find(params[:id])
halt 404, "A Resource with id #{params[:id]} could not be found" if resource.nil?
# ..
end
A more elegant way is loading the resource using a helper method which cares about the error handling and you are good to use the same call in all your routes.
helpers do
def load_resource
Resource.find(params[:id]) || halt(404, "A Resource with id #{params[:id]} could not be found")
end
end
get '/resource/:id' do
# load the resource via helper method
resource = load_resource
# if the resource doesn't exists, the request and processing is already dropped
..
end
There are even more output options for halt
, as mentioned you can return a erb template, you can also return JSON instead of plain text and so on. Check the docs here.