Catching Same Exception between Methods/ Classes - ruby-on-rails

I'm calling a function to get data from types of source, so I created a superclass called FileReader and several classes, each for FTP, SFTP, Google Drive etc.. Each of them will inherit and implement a get_data function.
I want to catch the exceptions returned by gems so that we don't report the outside factors as a bug report to Rollbar, so we rescue the exceptions and return proper message to the users when the file is not found, request timed out etc. Rescue block is on superclass because exceptions like SocketError, EOFError is common on all subclasses so I'm trying to keep my code dry.
class FileReader
def get_data
nil
end
def read
data = nil
begin
data = get_data
rescue EOFError => e
# return error message
rescue SocketError => e
# return error message
rescue OpenURI::HTTPError => e
# return error message
rescue Net::FTPPermError => e
# return error message
rescue Net::SFTP::StatusException => e
# return error message
rescue Errno::ETIMEDOUT => e
# return error message
rescue Exception => e
puts e.message
puts e.backtrace.join("\n")
Rollbar.error(e, :source => self)
# return error message
end
return data
end
end
FtpFileReader inherits FileReader class to implement the get_data function.
class FtpFileReader < FileReader
def get_data
# connect to ftp, get file, return data
# no begin and rescue block here
end
end
and my read method is called from my Rails model.
But catching such exceptions are tricky to me.
The calling method read is catching this exception, do FileReader#get_data method catches the same exception too if I implement a rescue block in it?
Should I catch these exceptions in FileReader get_data method instead of read? Or should I catch these specific exceptions in subclasses itself and return the error message in each get_data method in subclass? Eventually the classes to catch will be bigger but duplicating EOFError, SocketError is not so dry. What will be the best way of handling exceptions in cases like this?

First, never rescue from Exception (unless you are re-raising, and even then, ask yourself why you are doing it). Rescue from StandardError instead. Read about it here.
I would define rescues for subclass-specific errors only in those subclasses so that that specific logic can be completely isolated to the subclass in question. You can do this as you suggested, by moving the specific error catching to the get_data method. But you might consider defining a new method so you can use the non-rescuing method (maybe just for debugging purposes, or from another controller where you want to handle errors differently). Here's one way I'd consider doing that:
class FtpFileReader < FileReader
def get_data
get_data!
rescue Net::FTPPermErro => e
# return error message
end
def get_data!
# do stuff
end
end

Related

Best way to break and return a value

I have a method that's nested within a couple other methods, and I want this method to break from all recursive methods with a returned error. For instance, if I have pay_order being called here:
class API::StripeController < ApiController
def my_api_action
# ...
order = create_order
pay_order(order, token)
# do things if payment is successful
end
end
And pay_order is defined as:
def pay_order(order, token)
begin
order.pay(source: token)
rescue Stripe::CardError => e
break e
end
end
How do I break out of all parent methods and return the card error coming from the failed payment? I need something like break and return e or return e and break. But I know both the break and return statements immediately return, so I can't chain them (I don't think).
I could just add return statements to each function being called, but I plan on using this method in lots of places, and don't plan on ever needing it to behave differently, so I'm looking for the most reusable way to write it.
Why do you rescue inside the pay_order method? I'd rescue on the outer loop.
Given the following:
def method_a
10.times do |loop_a|
method_b
end
end
def method_b
5.times do |loop_b|
pay_order
end
end
def pay_order
...
end
I'd rescue inside method_a, for example:
def method_a
10.times do |loop_a|
method_b
end
rescue Stripe::CardError => e
# do whatever. no need to break
end
All the loops are "broken" automatically by the raising of the exception.
If you want to do something with the exception inside the pay_order method, then I would suggest to rescue and raise again:
def pay_order
order.pay
rescue Stripe::CardError => e
# do your stuff
raise e
end
Basically you can use throw, or raise (respectively fail, see here for a discussion whether to use raise or fail).
The difference is that with raise/fail, you are producing an exception (which you catch with rescue), and the Exception object can contain as payload the data you want to return.
With throw, you are simply doing kind of a goto upward the call chain. The landing point is defined using catch (see for instance here). This does not have any payload, which means that you need to "transport" any data back using an object which is accessible on both sides (for instance an instance variable).

How to determine where an exception is being rescued in Ruby?

Some background: I have some Ruby code in a large codebase (Rails) that raises an exception under certain conditions. The exception however does not "occur" as expected, it is silently discarded. I assume that some other code (a gem) rescues the exception, maybe accidentally.
How can I determine where that exception is being rescued?
I do have full control over the exception. So maybe there's a way for an exception to know when or that it is being rescued?
Contrived example:
# code outside my control
def foo
yield
rescue
end
def black_box(&block)
foo(&block)
end
# my code
black_box do
puts 'about to raise'
raise
puts 'never gets here'
end
Output:
about to raise
So the exception was rescued. How can I identify (from within "my code") that it was rescued in foo?
The only way I can think of (right now) is manual debugging/inspection.
When you're about to raise that exception you'd like to track, inspect current caller. This gives you a call stack. Now visit each line/method in your editor and look for rescues that are too much greedy.
As for more "automatic" ways, I don't see any. Ruby exceptions don't have on_rescue callbacks or anything like that, so they can't know they're being rescued.
[after 3 years, I'm answering my own question here]
At least in MRI, rescue is handled just like when in a case statement!
For example,
begin
# ...
rescue A, B
# ...
rescue C
# ...
rescue
# ...
end
is evaluated like:
case exception
when A, B
# ...
when C
# ...
when StandardError
# ...
end
Under the hood, both of the above execute: (until a match is found)
A === exception
B === exception
C === exception
StandardError === Exception
with === being Module#=== because A, B, C, and StandardError are classes.
That === call can be used to track down a potential rescue, e.g. by overriding Module#=== in Exception: (where it becomes a class method)
# foo.rb
def foo
yield
rescue
end
def black_box(&block)
foo(&block)
end
class Exception
def self.===(other)
cl = caller_locations[0]
puts "#{cl.path}:#{cl.lineno} - #{$!.inspect}"
super
end
end
require_relative 'foo.rb'
black_box do
puts 'about to raise'
raise 'error message'
puts 'never gets here'
end
Output:
about to raise
foo.rb:5 - #<RuntimeError: error message>
TracePoint could also be used to intercept the method call:
TracePoint.trace(:c_call) do |tp|
if tp.defined_class == Module && tp.method_id == :=== && tp.self <= Exception
puts "#{tp.path}:#{tp.lineno} - #{$!.inspect}"
end
end

Rescue nested method call

I read that Ruby doesn't have nested rescues. Is that why this won't work?
begin
dosomething
rescue => e
#this is never executed, dosomething raises the error directly
end
def dosomething
SomeModel.find(-1) #this raises the exception instead of above
end
Good day.
You must read ruby doc about exceptions
In short:
Exception - base class for All exceptions, therefore base code:
begin
# some code
rescue Exception => e
end
You should always specify which exception you handle
If you need proc all - use class Exception
P.S.
For you self education - http://rubylearning.com/satishtalim/ruby_exceptions.html

How to include Custom exception in Rails?

I don't understand well how Rails include (or not?) some file from the app directory.
For example, I've created a new directory app/exceptions for create my own exceptions. Now, from a helpers file, I want to raise one of my exception.
Am I suppose to include something in this helper?
The Helper: helpers/communications_helper.rb
//should I include something or it's suppose to be autoloaded?
module CommunicationsHelper
begin.
.
.
.
raise ParamsException, "My exception is lauch!"
rescue StandardError => e
...
end
end
The exception: exceptions/params_exception.rb
class ParamsException < StandardError
def initialize(object, operation)
puts "Dans paramsException"
end
end
Nothing specific from my raise in the output...
Thanks!
EDIT:
Thanks to all, your two answers was helpful in different way.
I didn't raise well the exception like you said, but I've also forggot to update my config.rb.
so now I 've:
rescue StandardError => e
raise ParamsError.new("truc", "truc")
Other question, do you know where can I catch the raise?
Cause I'm already in a catch block, so I'm little lost...
If you don't see output from your raise, make sure you're not rescuing the error by accident, since your error is a subclass of StandardError:
begin
raise ParamsException, "My exception is lauch!"
rescue StandardError => e # This also rescues ParamsException
end
As a side note, in Ruby it's common practice to have your custom errors end with Error rather than Exception. Unlike some other programming languages, classes ending with Exception are meant for system level errors.
First, I think that you're raising your exception incorrectly.
In your custom exception class, your initialize method takes in arguments. Therefore you should raise it with:
raise CustomError.new(arg1, arg2, etc.)
Read this.
Lastly, don't rescue from StandardError if CustomError is a child of StandardError; otherwise your manual 'raise' will be rescued.

Raise custom Exception with arguments

I'm defining a custom Exception on a model in rails as kind of a wrapper Exception: (begin[code]rescue[raise custom exception]end)
When I raise the Exception, I'd like to pass it some info about a) the instance of the model whose internal functions raise the error, and b) the error that was caught.
This is going on an automated import method of a model that gets populated by POST request to from foreign datasource.
tldr; How can one pass arguments to an Exception, given that you define the Exception yourself? I have an initialize method on that Exception but the raise syntax seems to only accept an Exception class and message, no optional parameters that get passed into the instantiation process.
create an instance of your exception with new:
class CustomException < StandardError
def initialize(data)
#data = data
end
end
# => nil
raise CustomException.new(bla: "blupp")
# CustomException: CustomException
Solution:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
#foo = foo
end
end
This is the best way if you follow the Rubocop Style Guide and always pass your message as the second argument to raise:
raise FooError.new(foo), 'argh'
You can get foo like this:
rescue FooError => error
error.foo # => 1234
error.message # => 'argh'
If you want to customize the error message then write:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
#foo = foo
end
def message
"The foo is: #{foo}"
end
end
This works great if foo is required. If you want foo to be an optional argument, then keep reading.
If you don't follow the Rubocop Style Guide
And want this to work:
raise FooError.new('argh', foo)
You need to pass the message to super as the only argument:
class FooError < StandardError
attr_reader :foo
def initialize(message, foo)
super(message)
#foo = foo
end
end
Explanation:
Pass your message as the second argument to raise
As the Rubocop Style Guide says, the message and the exception should be passed separately. If you write:
raise FooError.new('argh')
And want to pass a backtrace, there is no way to do it without passing the message twice:
raise FooError.new('argh'), 'argh', other_error.backtrace
You need to pass a backtrace if you want to re-raise an exception as a new instance with the same backtrace and a different message or data. Sometimes this is very useful.
Why is this so complicated?
The crux of the problem is a design flaw in Ruby: exception messages get set in two different ways.
raise StandardError, 'argh' # case 1
raise StandardError.new('argh') # case 2
In case 1, raise just calls StandardError.new('argh'), so these are the same. But what if you pass an exception instance and a message to raise?
raise FooError.new(foo), 'argh', backtrace
raise will set 'argh' as the message on the FooError instance, so it behaves as if you called super('argh') in FooError#initialize.
We want to be able to use this syntax, because otherwise, we'll have to pass the message twice anytime we want to pass a backtrace:
raise FooError.new(foo, 'argh'), 'argh', backtrace
raise FooError.new('argh', foo), 'argh', backtrace
But what if foo is optional? Then FooError#initialize is overloaded.
raise FooError, 'argh' # case A
raise FooError.new(foo), 'argh' # case B
In case A, raise will call FooError.new('argh'), but your code expects an optional foo, not a message. This is bad. What are your options?
accept that the value passed to FooError#initialize may be either foo or a message.
Don't use case A style. If you're not passing foo, write raise FooError.new(), 'argh'
Make foo a keyword argument
IMO, don't do 2. The code's not self-documenting, so you have to remember all of this. Too complicated.
If you don't want to use a keyword argument, my implementation of FooError way at the top of this answer actually works great with 1. This is why FooError#initialize has to call super and not super(). Because when you write raise FooError, 'argh', foo will be 'argh', and you have to pass it to the parent class to set the message. The code doesn't break if you call super with something that isn't a string; nothing happens.
3 is the simplest option, if you're ok with a keyword argument - h/t Lemon Cat. Here's the code for that:
class FooError < StandardError
attr_reader :foo
def initialize(message, foo: nil)
super(message)
#foo = foo
end
end
raise FooError, 'message', backtrace
raise FooError(foo: foo), 'message', backtrace
Here is a sample code adding a code to an error:
class MyCustomError < StandardError
attr_reader :code
def initialize(code)
#code = code
end
def to_s
"[#{code}] #{super}"
end
end
And to raise it:
raise MyCustomError.new(code), message
TL;DR 7 years after this question, I believe the correct answer is:
class CustomException < StandardError
attr_reader :extra
def initialize(message=nil, extra: nil)
super(message)
#extra = extra
end
end
# => nil
raise CustomException.new('some message', extra: "blupp")
WARNING: you will get identical results with:
raise CustomException.new(extra: 'blupp'), 'some message'
but that is because Exception#exception(string) does a #rb_obj_clone on self, and then calls exc_initialize (which does NOT call CustomException#initialize. From error.c:
static VALUE
exc_exception(int argc, VALUE *argv, VALUE self)
{
VALUE exc;
if (argc == 0) return self;
if (argc == 1 && self == argv[0]) return self;
exc = rb_obj_clone(self);
exc_initialize(argc, argv, exc);
return exc;
}
In the latter example of #raise up above, a CustomException will be raised with message set to "a message" and extra set to "blupp" (because it is a clone) but TWO CustomException objects are actually created: the first by CustomException.new, and the second by #raise calling #exception on the first instance of CustomException which creates a second cloned CustomException.
My extended dance remix version of why is at: https://stackoverflow.com/a/56371923/5299483
Simple pattern for custom errors with additional information
If the extra information you're looking to pass is simply a type with a message, this works well:
# define custom error class
class MyCustomError < StandardError; end
# raise error with extra information
raise MyCustomError, 'Extra Information'
The result (in IRB):
Traceback (most recent call last):
2: from (irb):22
1: from (irb):22:in `rescue in irb_binding'
MyCustomError (Extra Information)
Example in a class
The pattern below has become exceptionally useful for me (pun intended). It's clean, can be easily modularized, and the errors are expressive. Within my class I define new errors that inherit from StandardError, and I raise them with messages (for example, the object associated with the error).
Here's a simple example, similar to OP's original question, that raises a custom error within a class and captures the method name in the error message:
class MyUser
# class errors
class MyUserInitializationError < StandardError; end
# instance methods
def simulate_failure
raise MyUserInitializationError, "method failed: #{__method__}"
end
end
# example usage:
MyUser.new.simulate_failure
# => MyUser::MyUserInitializationError (method failed: simulate_failure)
It's counterintuitive for programmers coming from e.g. Java, but the most effective way to do this is not to write a custom initializer, but rather to write your own replacement for the Exception::exception class method.
Per the Kernel#raise docs:
the first parameter should be an Exception class (or another object that returns an Exception object when sent an exception message). [Emphasis added.]
class MyException < StandardError
class << self
def exception(arg)
# per `Exception::exception` docs
return self if arg.nil? || self.equal?(arg)
return MyException.new(arg.to_s) unless arg.is_a?(MyModel)
# $! is a magic global variable holding the last raised
# exception; Kernel#raise will also inject it as the
# cause attribute of the exception we construct here
error_caught = $!
msg = custom_message_for(arg, error_caught)
ex = MyException.new(msg)
# … any additional initialization goes here
ex
end
private
def custom_message_for(my_model_instance, error_caught)
# …
end
end
end
This way, you can raise your custom exception normally, with a model instance instead of a string message, without having to remember to call new explicitly and upset RuboCop, as well as confusing Ruby programmers that come to your code later expecting the standard syntax.
begin
my_model.frob
rescue => e
raise MyException, my_model # works
end
raise MyException, 'some other reason' # also works
The message & initialization logic from MyException#exception could also go in a custom initializer, letting you just write MyException.new(arg, $!), but in that case make sure the initializer is smart enough to also handle a plain string message, and make sure it at some point calls super with a string message.
You can create an new instance of your Exception subclass, then raise that. For instance:
begin
# do something
rescue => e
error = MyException.new(e, 'some info')
raise error
end

Resources