override not_found sinatra application - ruby

sinatra (= 1.4.5)
I want to render a custom 404 message for specific routes, in this case, the text 'Not found'
class App < Sinatra::Application
not_found do
'You suck'
end
get '/404page' do
halt 404, 'Not found'
end
end
I have found that no matter what I do, the text from the not_found block is invoked any time response status is set to 404, when all I really want is the text from the halt.
May be a duplicate of Override Sinatra default NotFound error page but I have not been able to find the soution.

Overriding error Sinatra::NotFound seems to be a better choice to handle 404 when a specific route is not found.
error Sinatra::NotFound do
'You suck'
end
get '/404page' do
halt 404, 'Not found'
end

You can pass a body to not_found, so your get request would just look something like this.
get '/404page' do
not_found('Not found')
end
This will call halt 404, 'Not found' via the not_found method.
More often than not this is what you will need; if this does not suit your needs then you would need to consider overriding the Sinatra route_missing method, which I have included below.
# No matching route was found or all routes passed. The default
# implementation is to forward the request downstream when running
# as middleware (#app is non-nil); when no downstream app is set, raise
# a NotFound exception. Subclasses can override this method to perform
# custom route miss logic.
def route_missing
if #app
forward
else
raise NotFound
end
end

Related

How to gracefully handle exceptions in a Sinatra API

I'm trying to write an API in Sinatra that accepts a temporary CSV file as a parameter. I want to raise an exception if the filetype isn't text/csv or if the csv doesn't have an email column, and I wanted the confirmation page to simply display the error message. I imagined it to look something like this:
if params[:recipients_file]
raise ArgumentError, 'Invalid file. Make sure it is of type text/csv.' unless params[:recipients_file][:type] == "text/csv"
recipients_csv = CSV.parse(params[:recipients_file][:tempfile].read, {headers: true})
raise ArgumentError, 'Invalid CSV. Make sure it has an "email" column' unless recipients_csv.headers.include?('email')
recipients += recipients_csv.map {|recipient| recipient["email"]}
end
However, any time one of those conditions isn't met, I get really ugly error messages like NoMethodErrors etc. I just want the API to stop execution and to return the error message on the confirmation page. How do I do this?
You should define an error block:
error do
env['sinatra.error'].message
end
See http://www.sinatrarb.com/intro.html#Error for more details, including how to set up different error handlers for different exception types, HTTP status codes, etc.

How do I add custom logging for which route is satisfying a request in Sinatra?

I'm working on a Sinatra app that has a bunch of routes of all sorts. I'd like to add some custom logging that logs the params of the get or post call that ends up generating the response for the request. I realize I could subclass the get/post definition to wrap the block with a logging call. But I suspect there is a more appropriate approach.
You can use Sinatra's before hook in your controller, and print out some information contained on the request
before do
if request.request_method == :get || request.request_method == :post
puts request.path_info, params.inspect # check out the request variable for more info you might like to ouput
end
end

Handling 405's in Sinatra

In Sinatra if I create a simple endpoint such as:
post '/users' do
'posted'
end
curl -v -X GET http://localhost:8080/users returns a 404, when I would expect a 405.
I've looked through the documentation but not found anything. Before digging through the source code, does anyone know how to handle and return 405's in Sinatra? Sort of like the not found method:
not_found do
'Not found - ' + request.path
end
You got an 404 because sinatra can't find a get route /users. If you want return a custom error you may look at Halt
throw a error
The you can return a 405 on get /users like this:
get "/users" do
halt 405
end
For catch multiple http verbs at once you can use multi route
require 'sinatra'
require "sinatra/multi_route"
route :get, :post, '/foo' do
# "GET" or "POST"
p request.env["REQUEST_METHOD"]
end
# Or for module-style applications
class MyApp < Sinatra::Base
register Sinatra::MultiRoute
route :get, :post, '/foo' do
# ...
end
end
source
handle errors
If you want handle error codes in sinatra you can simple do for 404 errors:
not_found do
'This is nowhere to be found.'
end
In your case handle a 405 error:
error 405 do
'Access forbidden'
end
Looks like it's not possible without repeating yourself a lot. From looking through the Sinatra source code, the hash of routes has the verb as the key: https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1513
It then looks up a route using that verb:
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L943
Which is not ideal and I would consider a weakness/bug in the framework as throwing a 405 when a verb isn't allowed is standard HTTP Spec.
I'll maybe raise an issue see what the contributors say. Ideally it would store routes by their url first, and then check the appropriate verb can be executed for a given url. This would make handling something as standard as a 405, much easier.
In fact I found an issue raised on github for the above:
https://github.com/sinatra/sinatra/issues/24
As mentioned nearer the bottom, it's not currently handled and something they may work on for v2.0

Ruby error handling with external request

I am making an external request and using HTTPARTY for the JSON file and then am parsing it.
BUT should the request fail (the file no longer exists or is a bad uri) how can I handle errors so I could still display the page?
Not sure of how best to protect the application from this point of failure and I have not done much in error handling.
def api_fetch(url)
JSON.parse HTTParty.get(url).response.body
end
api_fetch('http://example.com/data.json')['test']
Please help
The below should work. It will check if the method returns nil when you call it.
def api_fetch(url)
begin
JSON.parse HTTParty.get(url).response.body
rescue
nil
end
end

Return error to ajax on action decision

I have a controller action which looks like so -
def upvote
#post = Post.find(params[:id])
if user_has_rated_post?
# I want to interrupt AJAX here
redirect_to[:forum, #post.question], :notice => "You already voted for this post"
else
#post.upvote
add_rating_to_post(1, #post)
#post.save
redirect_to[:forum, #post.question], :notice => "Post Up Voted"
end
end
and I have an AJAX call to up vote a post, which works perfectly fine except I need it to stop the AJAX call if the first condition, user_has_rated_post? is met.
So the question is how can I interrupt or force an error to return to AJAX, or should I be trying to go about this another way?
Yes you have to return something that jQuery(or your AJAX solution) interprets as an error. Then you will have to handle this error in javascript code(or just do nothing if that fits). I think anything above 400 is by convention considered an error. See the list of HTTP status codes and pick the one that suits you most http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html. Here I would bet on 403 - The server understood the request, but is refusing to fulfill it. You can always just render 500, but I think it's better to be specific.

Resources