Skip to content

Instantly share code, notes, and snippets.

@tzvetkoff
Created August 31, 2017 11:20
Show Gist options
  • Save tzvetkoff/ab67e976a1cd90a9b071261578768993 to your computer and use it in GitHub Desktop.
Save tzvetkoff/ab67e976a1cd90a9b071261578768993 to your computer and use it in GitHub Desktop.
rescuable.rb
#
# Rescuable allows you to lazily wrap methods with a rescue logic outside.
# Example:
#
# class Foo
# extend Rescuable
#
# def kaboom
# raise RuntimeError, 'This will be rescued and will return 1337'
# raise 'This, however, will not be rescued'
# end
#
# def bam
# raise RuntimeError, 'This will also be rescued'
# end
#
# rescue_method :kaboom, :bam, from: RuntimeError, with: :handle_kaboom
#
# def handle_kaboom(e)
# 1337
# end
# end
#
# class Bar < Foo
# def bam
# raise RuntimeError, 'This will be rescued from Foo and return 1337'
# end
# end
#
# class Baz < Foo
# rescue_method :bam, from: RuntimeError, return: -1
# end
#
# puts Foo.new.bam # => 1337
# puts Bar.new.bam # => 1337
# puts Baz.new.bam # => -1
#
# It can also store the error for further inspection:
#
# class Foo
# extend Rescuable
# attr_reader :error
#
# def kaboom
# raise RuntimeError, 'This will be rescued and will return 1337'
# end
#
# rescue_method :kaboom, from: RuntimeError, return: 1337, ivar: :error
# end
#
# foo = Foo.new
# foo.kaboom # 1337
# foo.error # #<RuntimeError: This will be rescued and will return 1337>
#
module Rescuable
def rescue_method(*methods)
options = methods.pop
raise ArgumentError, 'Missing required key :from' unless options[:from]
methods.each do |meth|
rescues[meth] ||= {}
Array(options[:from]).each do |klass|
rescues[meth][klass] = {
with: options[:with],
return: options[:return],
ivar: options[:ivar] && "@#{options[:ivar].to_s.gsub(/^@/, '')}".to_sym,
}
end
unless self.instance_methods.include?(:"#{meth}_with_rescue")
define_method(:"#{meth}_with_rescue") do |*args, &block|
begin
self.send(:"#{meth}_without_rescue", *args, &block)
rescue => e
self.class.rescues[meth].each do |klass, rescue_options|
if e.is_a?(klass)
self.instance_variable_set(rescue_options[:ivar], e) if rescue_options[:ivar]
return rescue_options[:with] ? self.send(rescue_options[:with], e) : rescue_options[:result]
end
end
raise
end
end
alias_method :"#{meth}_without_rescue", meth
alias_method meth, :"#{meth}_with_rescue"
end
end
end
def method_added(meth)
if rescues[meth] && self.instance_method(meth) != self.instance_method(:"#{meth}_with_rescue")
alias_method :"#{meth}_without_rescue", meth
alias_method meth, :"#{meth}_with_rescue"
end
end
def rescues
@_rescues ||= self.ancestors[1..-1].reverse.reduce({}) do |result, klass|
if klass.is_a?(Class) && klass.instance_variable_defined?('@_rescues')
result.merge(klass.instance_variable_get('@_rescues').map{ |k, v| [k, v.dup] }.to_h)
else
result
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment