I have a Rails controller with this method being triggered as a before_action:
def authenticate_user
Knock::AuthToken.new(token: token).entity_for(User)
rescue Mongoid::Errors::DocumentNotFound
render nothing: true, status: 401
end
Even though I can verify that it's rescuing the error(a byebug breakpoint gets triggered under the rescue statement), it still manages to be raised immediately after:
Mongoid::Errors::DocumentNotFound (
message:
Document(s) not found for class User with id(s) 1.
summary:
When calling User.find with an id or array of ids, each parameter must match a document in the database or this error will be raised. The search was for the id(s): 1 ... (1 total) and the following ids were not found: 1.
resolution:
Search for an id that is in the database or set the Mongoid.raise_not_found_error configuration option to false, which will cause a nil to be returned instead of raising this error when searching for a single id, or only the matched documents when searching for multiples.):
app/controllers/api/base_controller.rb:12:in `authenticate_user'
I've been using the rescue keyword for years in Ruby and have never encountered this.
What I'm running:
Ruby 2.5
Rails 5.2
Mongoid 7.0.1
Why is it that the error gets raised even when I rescue it, and how can I prevent the error from getting raised?
I'm not sure why it gets raised even when you rescue it, but I'll answer the "how to prevent it" part.
In your mongoid.yml, you need to set
raise_not_found_error: false
See this section in the Mongoid docs for an example.
Related
I am trying to handle errors in a Modular Sinatra App. We raise various error through out the app and I wrote some thing alike to catch errors thinking it will happen hierarchically.
My the file I use to error handle looks like the following.
#!/usr/bin/env ruby
# #class class RApp
# #brief Sinatra main routes overloading for App class
class RApp < Sinatra::Base
# #fn not_found do {{{
# #brief Not found error page
not_found do
render_error 404, _('Not Found'), env['sinatra.error'].message
end # }}}
# #fn error ServiceNotAvailableError do {{{
# #brief Handle ServiceNotFoundException, commonly associated with communication
# errors with external required services like Ambrosia, Saba
error ServiceNotAvailableError do
render_error 503, _('Service Not Available'), env['sinatra.error'].message
end # }}}
# #fn error Exception do {{{
# #brief Handle general internal errors
error Exception do
render_error 500, _('Internal Server Error'), env['sinatra.error'].message
end # }}}
error DBC::InvalidUUIDError do
"Invalid UUID Error"
end
# #fn def show_error code, title, message, view = nil {{{
# #brief Displays the proper message (html or text) based on if the request is XHR or otherwise
def render_error code, title, message, view = nil
view = code.to_s if view.nil?
if request.xhr?
halt code, message
else
halt code, slim(:"error_pages/#{view}", locals: {title: title, message: message})
end
end # }}}
# Just for testing
get '/errors/:type' do |type|
raise Object.const_get(type)
end
end # of class RApp < Sinatra::Base }}}
# vim:ts=2:tw=100:wm=100
I was thinking it will try in the order in the file which seem to be true.
How ever the issue is Exception doesn't catch all exceptions.
For example I have DBC::InvalidUUIDError which is as such
DBC::InvalidUUIDErrror < DBC::Error
DBC::Error < RuntimeError
which I understand as in Ruby RuntimeError < Exception
But it error Exception doesn't catch all Exception as I thought.
Am I doing some thing wrong? Or is it not possible to catch all exceptions generally?
Note: Additionally to the answers provided (both works), I had set :raise_errors, true. You need to not set it in development and production. By default it is set for 'test' env.
Nature of the matter was some exceptions been handled and some not.
Add this to prevent Sinatra's own error page from interfering with your custom error handlers:
set :show_exceptions, false
By default, the setting is true in development mode, and false in production.
Note that if you follow the Sinatra readme's advice about setting set :show_exceptions, :after_handler, that will enable your error handlers to run even in development mode (at least for some exception classes), but it will also enable the built-in error page in production for uncaught exceptions. And it's unclear to me which error handlers it will respect, and which ones it will ignore in favor of the built-in debug page.
Edit: I realised you also asked about the order the error handlers are defined in. It doesn't matter; Sinatra looks for exact matches to the exception class in the app class first, and then in its superclasses. If it doesn't find any, it repeats the search for the exception's superclass, etc. So the handler for Exception will only get called for exceptions for which there isn't a closer match.
Here is the relevant info from the Sinatra docs:
Sinatra installs special not_found and error handlers when running under the development environment to display nice stack traces and additional debugging information in your browser.
What this means, is that when you are running in development, Sinatra has a creates a default "catch-all" error handler which has a higher precedence than your error Exception do .. end block.
Once you enter production mode, (or disable in dev via disable :show_exceptions
), your error Exception do .. end block should catch all of your exceptions.
Note that the order of these error blocks defined is irrelevant.
Here is a simple app:
class App < Sinatra::Base
set :show_exceptions, false
not_found do
slim :err_404
end
post "/doit" do
user ||= User.find(params["userid"]) || halt(404)
end
end
When given an invalid userid, the 404 error block should trigger and then render the 404 page. Instead, Sinatra shows "Internal Server Error" on the page, and this stacktrace is printed to console:
Problem:
Document(s) not found for class User with id(s) 53d06a8ca7b7d52d11300003.
Summary:
....
I'm guessing the halt(404) isn't being called. I was following this blog post about error handling, so why wouldn't the same thing work here?
Assuming you are using ActiveRecord, User.find(params["userid"]) will raise an ActiveRecord::RecordNotFound exception if there is no matching record. This exception is thrown before the 404 handler gets to run and ends the route handling, resulting in the internal server error.
To fix it you could check for the exception, and call the 404 handler if it is raised:
post "/doit" do
begin
user ||= User.find(params["userid"]
rescue ActiveRecord::RecordNotFound
halt(404)
end
end
A better solution might be to use find_by_id instead, which avoids using exceptions for flow control:
post "/doit" do
user ||= User.find_by_id(params["userid"]) || halt(404)
end
Im trying to get the backtrace in sinatra in case of an error.
I know rails has one in
Rails.respond_to?(:backtrace_cleaner)
and I saw that sinatra is suppose to have one (by default enabled) in STDERR
So i tried
STDERR.inspect
and I got #<IO:<STDERR>>
When rescuing the exception, catch the exception object.
begin
raise "hello"
rescue => e
e.backtrace
end
In Ruby you can call a method caller at any place and get a full backtrace as an array.
having just started to look at DataMapper for Ruby ORM I've run into an issue that confuses me to no end.
The default behaviour when saving instances in DataMapper (through DataMapper::Resource.save) is, as far as I understand, to silently fail, return false from the save method and collect any errors in the errors collection. So far so good, this works as expected. The issue I see is with natural primary keys, where setting duplicate ones will throw an exception instead of silently returning false from the save method, blatantly disregarding the current setting of raise_on_save_failure. Consider the following snippet;
require 'data_mapper'
class Thing
include DataMapper::Resource
property :name , String, :key=> true
property :description, String, length: 2..5
end
DataMapper.setup :default, "sqlite:memory"
DataMapper.finalize
DataMapper.auto_migrate!
#Create a thing and have it properly silently fail saving
t1=Thing.new(:name=>"n1",:description=>"verylongdescription" )
t1.save #ok
puts("t1 is saved:"+t1.saved?.to_s)
t1.errors.each do |e|
puts e
end
#Create two things with the same key, and fail miserably in silently failing the second save...
t1=Thing.new(:name=>"n1",:description=>"ok" )
t2=Thing.new(:name=>"n1",:description=>"ok" )
t1.save #ok
t2.save #not ok, raises Exception event though it shouldn't?
puts("t2 is saved:"+t1.saved?.to_s)
t2.errors.each do |e|
puts e
end
The first save, on an instance failing a validation rule for the :description property behaves as expected. The third save, of an instance with duplicate keys, however does not, since it raises an execption instead of nicely just returning false.
Why is this? It is obviously possible to work around, but it doesn't feel very clear. Is the case that the silent behaviour will only apply to validation errors in the DataMapper layer, and any hard errors from the underlying datastore will be propagated as exceptions to the caller?
Thanks!
As another user pointed out in a comment, this happens because setting raise_on_save_failure to false doesn't mean that no exceptions will occur. It will always return false (with no exceptions) when validation errors occur.
Database errors will blow up, as you noticed, in the form of an exception. Such errors could happen due to a large number of factors (the connection failed, no disk space), including mundane ones like a duplicate key. DataMapper can't know whether the object you're trying to save possesses a duplicate key, so it just validates it and sends to the database, where the error actually occurs.
I have a Slack Bot that needs to respond in an error condition. If the error has certain text in it, I want to append some additional information to the return message. This block of code works fine if I comment out the message += line but breaks if I do not. When I try to replicate this in irb everything works fine too.
Does something look obviously wrong here?
begin
scan = #nsc.scan_devices(devices)
rescue Nexpose::APIError => e
puts "[!] API ERROR: Most likely caused by an orphaned asset (#{device_ids})"
puts "[!] #{e}"
$slackbot_logger.error("[!] API ERROR: Most likely caused by an orphaned asset (#{device_ids})")
$slackbot_logger.error(e)
# Message back to Slack
message = "<##{user_id}> scan for #{ip_list} *failed* :sob:"
message += 'There is a scheduled blackout Tues/Thurs until 1000 CST' if e.include? 'blackout'
SlackFunctions.slack_send_message(message, channel)
return
end
This particular error object (and maybe all error objects) did not have an include? method. Therefore using e.to_s seems to do the trick.