I am developing a RoR application. I need to use a custom exception I implemented. It looks like this:
# app/exceptions/JobError.rb
module Exceptions
class JobError < StandardError
# ...
end
end
However, I got a uninitialized constant JobError when I raise the exception. I tried different namespaces but none of them work:
raise JobError.new()
raise Exceptions::JobError.new()
raise ::Exceptions::JobError.new()
Any idea?
My bad, I wrote the file name in CamelCase, not in snake_case...
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
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
I have this piece of code that I am trying to test:
def error_from_exception(ex)
if ex.is_a?(ActiveRecord::RecordInvalid)
...
To get into the if block, I need to pass in the correct ex param.
How do I create an ActiveRecord::RecordInvalid?
With rspec, I'm trying to do something like this:
context 'exception is ActiveRecord::RecordInvalid' do
it 'returns the validation error' do
begin
raise ActiveRecord::RecordInvalid
rescue Exception => ex
binding.pry
###
# From my pry session:
# $ ex
# $ <ArgumentError: wrong number of arguments (0 for 1)>
# $ ex.class
# $ ArgumentError < StandardError
###
end
end
end
How do I find out what argument type the library is looking for?
RecordInvalid link
None of the above methods worked for me so I finally did the following while doing a spec:
class InvalidRecord
include ActiveModel::Model
end
raise ActiveRecord::RecordInvalid.new(InvalidRecord.new)
Hope it helps!
EDIT: This is now possible in Rails 5.1.1. The record argument is no longer required after this commit: https://github.com/rails/rails/commit/4ff626cac901b41f86646dab1939d2a95b2d26bd
If you are on a Rails version under 5.1.1, see the original answer below:
It doesn't seem like it is possible to raise an ActiveRecord::RecordInvalid by itself. If you look at the source code for ActiveRecord::RecordInvalid, it requires a record when initializing:
class RecordInvalid < ActiveRecordError
attr_reader :record # :nodoc:
def initialize(record) # :nodoc:
#record = record
...
end
end
(source: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/validations.rb)
Something you could do to work around this would be to simply create an actual record that is invalid and attempt to save it using save! (such as calling User.new.save! when User.name is required). However, keep in mind that this might potentially become a problem in the future if the model you use is changed and it becomes valid inside your test (User.name is no longer required).
I needed to do something similar to test my code's handling of ActiveRecord::RecordInvalid. I wanted to do
allow(mock_ar_object).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)
but that gives ArgumentError: wrong number of arguments (0 for 1) when RSpec tries to instantiate RecordInvalid.
Instead I wrote a RecordInvalid subclass and overrode initialize like this:
class MockRecordInvalid < ActiveRecord::RecordInvalid
def initialize
end
end
allow(mock_ar_object).to receive(:save!).and_raise(MockRecordInvalid)
Then a rescue ActiveRecord::RecordInvalid will catch MockRecordInvalid and MockRecordInvalid.new.is_a?(ActiveRecord::RecordInvalid) is true.
ActiveRecord::RecordInvalid needs an object to be created.
If you want to test just the exception itself, try:
null_object = double.as_null_object
ActiveRecord::RecordInvalid.new(null_object)
Understand the double as null object here (https://www.relishapp.com/rspec/rspec-mocks/v/2-6/docs/method-stubs/as-null-object)
I am using update_attribute(attr, 'value') instead of save! and I can mock the update_attribute method as follows:
expect(mock_object).to receive(:update_attribute).with(attr, 'value').and_raise(ActiveRecord::RecordInvalid)
We can stub it to the Model like this,
ModelName.any_instance.stubs(<method_name>).raises(ActiveRecord::RecordInvalid.new(record))
Example:
Post.any_instance.stubs(:save!).raises(ActiveRecord::RecordInvalid.new(post))
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