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.