I have a simple Sinatra rest and I am having trouble trapping an error. I also admit I am fairly new to Ruby and Sinatra.
When I raise and error in the post endpoint I want to report the incoming document. I need to either 1) handle the error within the post result (where I have access to #incoming) or 2) pass the incoming document to the error and report it there.
What is a better option, option 1 or option 2?
If I stay with option 1, how do I prevent error from picking up the error (as it seems to be doing now)
If I go to option 2, how do I pass incoming to error?
Below is a sample of my code:
post ('/result') do
begin
#incoming = JSON.parse(request.body.read)
//do something that causes an error
rescue
e = env['sinatra.error']
url = request.url
ip = request.ip
#actlogpassblock = { :message => e.message,
:path => url,
:ip => ip,
:timestamp => Time.new,
:type => "500",
:sub => "RES",
:payload => #incoming
}
action_log.insert(#actlogpassblock)
status 500
end
end
error do
status 500
e = env['sinatra.error']
url = request.url
ip = request.ip
backtrace = "Application error\n#{e}\n#{e.backtrace.join("\n")}"
#actlogpassblock = { :message => e.message,
:path => url,
:ip => ip,
:timestamp => Time.new,
:type => "500",
:backtrace => backtrace
}
action_log.insert(#actlogpassblock)
{:result => 'Ah Shucks! Something went wrong'}.to_json
end
If I stay with option 1, how do I prevent error from picking up the
error (as it seems to be doing now)
According to the docs:
The error handler is invoked any time an exception is raised from a
route block...
However, that only applies to uncaught exceptions. Try this:
require 'sinatra'
set :show_exceptions, false
get '/' do
begin
raise ZeroDivisionError
rescue ZeroDivisionError
"rescue clause"
end
end
error do
"sinatra error handler"
end
Then try this:
get '/' do
raise ZeroDivisionError
end
error do
"sinatra error handler"
end
Also, you can tailor the error handler to only catch certain exceptions thereby avoiding some exceptions, e.g.
error IndexError do ...
or
error MyCustomException do ...
or
error 400..510 do
But for the catch all version:
error do
you can't stop that from executing when an uncaught exception occurs in a route block...unless:
The error handlers will only be invoked, however, if both the Sinatra
:raise_errors and :show_exceptions configuration options have been set
to false...
:raise_errors defaults to true in the "test" environment and to false
on other environments.
:show_exceptions value defaults to true in the
"development" environment and to false on other environments
The author of Sintra has said: "This [behavior] is
intentional. The idea is that error blocks will hide the issue and you
usually don't want to do this in development mode.
https://github.com/sul-dlss/sdr-services-app/blob/master/Sinatra-error-handling.md
If I go to option 2, how do I pass incoming to error?
An #variable that is created inside a route block can be seen inside an error block. Try this:
require 'sinatra'
set :show_exceptions, false
get '/' do
#incoming = "hello world" #create #variable
raise ZeroDivisionError
end
error ZeroDivisionError do
#incoming #retrieve #variable
end
After entering the url http://localhost:4567 in your browser, you should see "hello world" on the returned web page.
The reason that works is because an #variable attaches itself to whatever object is self at the time the #variable is created; likewise when an #variable is summoned, it is retrieved from whatever object is self at that time. When Sinatra executes either the route block or the error block it sets self to the same object.
Option 2 seems nice because it separates the error handling code from the application code.
Related
So I have a Sinatra API containing this piece of code in a model:
def self.delete(account_id)
# using Sequel, not ActiveRecord:
if Account[account_id][:default] == true
abort("Impossible to delete a default account. Please first set another account to default.")
end
# rest of the code
end
then, in app.rb :
delete '/account/:id' do
if Account.delete(params[:id]) == 1
status 200
else
status 500
end
end
On the client side (vuejs app), I would like the error message to be displayed. Instead, when the error produces, I get a SystemExit with the error message.
How do I send that SystemExit message to the server?
In Ruby in general you want to break out of execution either by using return, exceptions or throw/catch. abort is rarely if ever used as it will immediately halt execution of the entire program - and prevent you from doing stuff like cleaning up or actually sending a meaningful response to the client besides whatever error page the web server will render if you just quit the job half way though.
You can easily implement this by creating your own exception class:
class AccountDeletionError < StandardError
end
def self.delete(account_id)
# using Sequel, not ActiveRecord:
if Account[account_id][:default] == true
raise AccountDeletionError.new, "Impossible to delete a default account. Please first set another account to default."
end
end
delete '/account/:id' do
if Account.delete(params[:id]) == 1
status 200
end
rescue AccountDeletionError => e
status 409 # not 500.
content_type :json
{ error: e.message }.to_json
end
end
Now that you know how to handle errors you should probally address the next possible one - when an account cannot be found.
I want to write an RSpec test which verifies that, should a 500 error occur in my Sinatra-powered API, the error will be caught by a Sinatra error definition and returned to the client in a JSON format. That is, rather than returning some HTML error page, it returns JSON like this to conform with the rest of the API:
{
success: "false",
response: "Internal server error"
}
However, I'm unsure how to actually trigger a 500 error in my Sinatra app in order to test this behaviour with RSpec. I can't find a way to mock Sinatra routes, so currently my best idea is this route which deliberately causes a 500. This feels like a pretty dreadful solution:
get '/api/v1/testing/internal-server-error' do
1 / 0
end
Is there a way to mock Sinatra routes so that I can have, say, /'s route handler block raise an exception, therefore triggering a 500? If not, is there some other way to deliberately cause a 500 error in my app?
When facing a situation like this, what I usually do is separate concerns, and move logic outside of the Sinatra get ... block. Then, it is easy to stub it and make it raise an error.
For example, given this server code:
# server.rb
require 'sinatra'
class SomeModel
def self.some_action
"do what you need to do"
end
end
get '/' do
SomeModel.some_action
end
You can then use this code to have the model, or any other class/function you are using to actually generate the response, raise an error, using this spec:
# spec
describe '/' do
context 'on error' do
before do
allow(SomeModel).to receive(:some_action) { raise ArgumentError }
end
it 'errors gracefully' do
get '/'
expect(last_response.status).to eq 500
end
end
end
For completeness, here is a self contained file that can be tested to demonstrate this approach by running rspec thisfile.rb:
# thisfile.rb
require 'rack/test'
require 'rspec'
require 'sinatra'
# server
class SomeModel
def self.some_action
"do what you need to do"
end
end
get '/' do
SomeModel.some_action
end
# spec_helper
ENV['APP_ENV'] = 'test'
module RSpecMixin
include Rack::Test::Methods
def app() Sinatra::Application end
end
RSpec.configure do |c|
c.include RSpecMixin
end
# spec
describe '/' do
context 'on error' do
before do
allow(SomeModel).to receive(:some_action) { raise ArgumentError }
end
it 'errors gracefully' do
get '/'
expect(last_response.status).to eq 500
end
end
end
Use the halt method:
require 'sinatra'
get '/' do
halt 500, {
success: 'false',
response: 'Internal server error'
}.to_json
end
I am unable to send emails. i have already tried the following:
Converting type of handler from text to long text does'nt work as i am using postgres.
I have also tried restarting my workers and delayed jobs
and my delayed job is version is 3.0.1.
I am having problem on the line:
Notifier.delay.delivery_alert(u)
and my delivery_alert method is
def delivery_alert(user)
#user = user
#deliveries = #user.issues.includes(:copy => [:rentable]).current.by_last_status("to_be_delivered").map(&:copy).map(&:rentable)
#returns = #user.issues.includes(:copy => [:rentable]).current.by_last_status("marked_for_return").map(&:copy).map(&:rentable)
mail(:to => #user.email)
end
While on localhost i am getting the error:
[Worker(host:ubuntu pid:12169)] Class#delivery_alert failed with NoMethodError: undefined method `delivery_alert' for # - 5 failed attempts
for which i have added a patch in my lib folder
require 'yaml'
module Delayed
module Backend
module Base
def payload_object
YAML::ENGINE.yamler = 'psych'
#payload_object ||= YAML.load(self.handler)
rescue TypeError, LoadError, NameError, ArgumentError => e
raise DeserializationError,
"Job failed to load: #{e.message}. Handler: #{handler.inspect}"
end
end
end
class PerformableMailer
def perform
double = object.is_a?( String ) ? object.constantize : object
double.send(method_name, *args).deliver
end
end
end
by taking reference of
https://gist.github.com/oelmekki/2181381
but still my email are not going. Thanks in advance?
I would like to define an error block (or something) that would return all exceptions formatted in JSON somehow plus returning the standard http code (e.g. 404 for not found, 303 for auth errors etc).
Something like:
error do
e = env['sinatra.error']
json :result => 'error', :message => e.message
end
Thanks!
This should work:
require 'sinatra'
require 'json'
# This is needed for testing, otherwise the default
# error handler kicks in
set :environment, :production
error do
content_type :json
status 400 # or whatever
e = env['sinatra.error']
{:result => 'error', :message => e.message}.to_json
end
get '/' do
raise 'hell'
end
Test it with curl to see that it works.
I want to be able to rescue I18n::MissingTranslationData like so:
begin
value = I18n.t('some.key.that.does.not.exist')
puts value
return value if value
rescue I18n::MissingTranslationData
puts "Kaboom!"
end
I tried the above, but it doesn't seem to go into the rescue block. I just see, on my console (because of puts): translation missing: some.key.that.does.not.exist. I never see Kaboom!.
How do I get this to work?
IMO, it's pretty strange but in the current version of i18n (0.5.0) you should pass an exception that you want to rescue:
require 'i18n'
begin
value = I18n.translate('some.key.that.does.not.exist', :raise => I18n::MissingTranslationData)
puts value
return value if value
rescue I18n::MissingTranslationData
puts "Kaboom!"
end
and it will be fixed in the future 0.6 release (you can test it - https://github.com/svenfuchs/i18n)
Same as above but nicer.
v = "doesnt_exist"
begin
puts I18n.t "langs.#{v}", raise: true
rescue
puts "Nooo #{v} has no Translation!"
end
or
puts I18n.t("langs.#{v}", default: "No Translation!")
or
a = I18n.t "langs.#{v}", raise: true rescue false
unless a
puts "Update your YAML!"
end
In the current version of I18n, the exception you're looking for is actually called MissingTranslation. The default exception handler for I18n rescues it silently, and just passes it up to ArgumentError to print an error message and not much else. If you actually want the error thrown, you'll need to override the handler.
See the source code for i18n exceptions, and section 6.2 of RailsGuides guide to I18n for how to write a custom handler
Note that now you simply pass in :raise => true
assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) }
...which will raise I18n::MissingTranslationData.
See https://github.com/svenfuchs/i18n/blob/master/lib/i18n/tests/lookup.rb