Created
August 31, 2017 11:20
-
-
Save tzvetkoff/ab67e976a1cd90a9b071261578768993 to your computer and use it in GitHub Desktop.
rescuable.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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