Question

I have written a program that utilizes an external ruby gem. As I am doing a lot of different actions with this, I want to be able to rescue and handle exceptions across the board, instead of implementing it each time I call a method.

What is the best way to do this?
Should I write my own method that simply calls the external gem and also rescues exceptions? Or is there another way to do something like "Whenever an exception of this type comes up anywhere in the program, handle it this way"?

I know that if I wrote the external gem code I could add error handling like that, but that is not feasible.

Was it helpful?

Solution

The basic answer to this is probably to wrap the class you're working with; Ruby allows for a lot of flexibility for doing this since it has method_missing and a pretty dynamic class environment. Here's an example (which may or may not be fatally flawed, but demonstrates the principle:

# A Foo class that throws a nasty exception somewhere.
class Foo
  class SpecialException < Exception; end

  def bar
    raise SpecialException.new("Barf!")
  end
end

# This will rescue all exceptions and optionally call a callback instead
# of raising.
class RescueAllTheThings
  def initialize(instance, callback=nil)
    @instance = instance
    @callback = callback
  end

  def method_missing(method, *args, &block)
    if @instance.respond_to? method
      begin
        @instance.send(method, *args, &block)
      rescue Exception => e
        @callback.call(e) if @callback
      end
    else
      super
    end
  end
end

# A normal non-wrapped Foo. Exceptions will propagate.
raw_foo = Foo.new

# We'll wrap it here with a rescue so that we don't exit when it raises.
begin
  raw_foo.bar
rescue Foo::SpecialException
  puts "Uncaught exception here! I would've exited without this local rescue!"
end

# Wrap the raw_foo instance with RescueAllTheThings, which will pass through
# all method calls, but will rescue all exceptions and optionally call the
# callback instead. Using lambda{} is a fancy way to create a temporary class
# with a #call method that runs the block of code passed. This code is executed
# in the context *here*, so local variables etc. are usable from wherever the
# lambda is placed.
safe_foo = RescueAllTheThings.new(raw_foo, lambda { |e| puts "Caught an exception: #{e.class}: #{e.message}" })

# No need to rescue anything, it's all handled!
safe_foo.bar

puts "Look ma, I didn't exit!"

Whether it makes sense to use a very generic version of a wrapper class, such as the RescueAllTheThings class above, or something more specific to the thing you're trying to wrap will depend a lot on the context and the specific issues you're looking to solve.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top