Passing Multiple Error Classes to Ruby's Rescue Clause in a Dry Fashion

Passing multiple error classes to ruby's rescue clause in a DRY fashion

You can use an array with the splat operator *.

EXCEPTIONS = [FooException, BarException]

begin
a = rand
if a > 0.5
raise FooException
else
raise BarException
end
rescue *EXCEPTIONS
puts "rescued!"
end

If you are going to use a constant for the array as above (with EXCEPTIONS), note that you cannot define it within a definition, and also if you define it in some other class, you have to refer to it with its namespace. Actually, it does not have to be a constant.


Splat Operator

The splat operator * "unpacks" an array in its position so that

rescue *EXCEPTIONS

means the same as

rescue FooException, BarException

You can also use it within an array literal as

[BazException, *EXCEPTIONS, BangExcepion]

which is the same as

[BazException, FooException, BarException, BangExcepion]

or in an argument position

method(BazException, *EXCEPTIONS, BangExcepion)

which means

method(BazException, FooException, BarException, BangExcepion)

[] expands to vacuity:

[a, *[], b] # => [a, b]

One difference between ruby 1.8 and ruby 1.9 is with nil.

[a, *nil, b] # => [a, b]       (ruby 1.9)
[a, *nil, b] # => [a, nil, b] (ruby 1.8)

Be careful with objects on which to_a is defined, as to_a will be applied in such cases:

[a, *{k: :v}, b] # => [a, [:k, :v], b]

With other types of objects, it returns itself.

[1, *2, 3] # => [1, 2, 3]

multiple assignment in rescue clause?

I found the code above at http://phrogz.net/programmingruby/tut_exceptions.html. Was that your source?

In any event, the local variable in that code is assigned whichever error is raised; it's just specified after the last one.

And yes, it's throwing TypeError because the errors do not implicitly coerce to a string in today's Ruby. Perhaps they used to when the book was initially published. You need to add .message to the local variable reference to get the error message (e.g. + boom.message).

TypeError (class or module required for rescue clause)

The line number in the error is a little misleading, the error is actually coming from this:

rescue e

I think you meant

rescue => e

What is wrong with this rescue example?

I just want to add something to the table: OP code suggests that the two exceptions are the same but they are not - furthermore i want to illustrate what OP meant with:

So it seems like that after doing raise x, x == y and x === y no longer hold. It seems to because x and y no longer have the same backtrace.

 x = StandardError.new(:hello)
y = StandardError.new(:hello)
class Object
def all_equals(o)
ops = [:==, :===, :eql?, :equal?]
Hash[ops.map(&:to_s).zip(ops.map {|s| send(s, o) })]
end
end

puts x.all_equals y # => {"=="=>true, "==="=>true, "eql?"=>false, "equal?"=>false}

begin
raise x
rescue
puts "ok" # gets printed
end

puts x.all_equals y # => {"=="=>false, "==="=>false, "eql?"=>false, "equal?"=>false}

DRY-ing up begin-rescue-end

DRYing this up is a great idea and a good lesson in rails design (and Test Driven Development/TDD) if you can do so.

Ideally, you'd do something like this:

def create
...
grid = Tag.find_by_id(story[:tag_id]) or raise GridNotFoundError
...
event = Event.find_by_id(story[:event_id]) or raise EventNotFoundError
...
rescue GridNotFoundError
flash.now[:error] = "Please select a category"
process_grid_not_found(item, story, race, etc...)
rescue EventNotFoundError
flash.now[:error] = "Please select an event or create a new one if you don't find your event"
process_event_not_found(item, story, race, etc...)
rescue CreateEventError
flash.now[:error] = "There has been a problem creating your event."
process_event_create_error(item, story, race, etc...)
rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound
flash.now[:error] = item.errors.full_messages.join(" ,")
process_other_error(item, story, race, etc...)
end
  render 'home/race_updates'

Then you should create the relavent new methods (process_event_not_found, etc) as separate (probably private) methods in the model.

This both makes the code much more readable, but has the great advantage be being much easier to write test code for.

So then you should write test code (using Test::Unit or rspec or whatever) that tests the isolated functionality required by each of the individual exception methods. What you'll find is that this both yields better code, as well as it also will likely break-down the exception methods into smaller, more modular methods themselves.

When you hear Ruby and Rails developers talk about the benefits of Test Driven Development, one of the main benefits of that approach is that it is much less likely to result in long, complex methods like the one you've got here. It's much more likely that you'll have code that is much DRYer, with smaller, simpler methods.

I'd also recommend that once you get through this you take another look and try to simplify it further. There will be more room for simplification, but I'd recommend refactoring it iteratively and starting with breaking it down as I've described and getting tests in place to start.

Multiple Rescue Statements in Rails Create Not Working

Found the problem. There is a patch to the open-uri.rb file in the ruby library.

Replace:

(/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:http|ftp)\z/i =~ uri2.scheme)

With:

(/\A(?:https?|ftp)\z/i =~ uri1.scheme && /\A(?:https?|ftp)\z/i =~ uri2.scheme)

in the open-uri.rb file. The problem is with http -> https redirects. This file was found for me in:

/Users/MyName/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/open-uri.rb

Rescue from an error that may not be defined

The exception list of a rescue-clause doesn't have to be a literal/static list:

excs = [EOFError]
defined?(AWS) && excs << AWS::SES::Response
# ...
rescue *excs => e

The splat operator * is used here to convert an array into a list.



Related Topics



Leave a reply



Submit