From 93013d613c7a925ffe779f284a9092e3aa121c97 Mon Sep 17 00:00:00 2001 From: Matas Zanevicius Date: Mon, 16 Sep 2019 10:48:31 +0300 Subject: [PATCH 1/3] fail context on specified exception and handle such exception in lambda --- lib/interactor.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/interactor.rb b/lib/interactor.rb index 2423630..d80d9de 100644 --- a/lib/interactor.rb +++ b/lib/interactor.rb @@ -22,6 +22,10 @@ def self.included(base) extend ClassMethods include Hooks + class << self + attr_accessor :exception_classes + attr_accessor :exception_handler + end # Public: Gets the Interactor::Context of the Interactor instance. attr_reader :context end @@ -29,6 +33,11 @@ def self.included(base) # Internal: Interactor class methods. module ClassMethods + def fail_on_exeption(*exception_classes, exception_handler: nil) + @exception_classes = exception_classes + @exception_handler = exception_handler + end + # Public: Invoke an Interactor. This is the primary public API method to an # interactor. # @@ -140,8 +149,13 @@ def run # Raises Interactor::Failure if the context is failed. def run! with_hooks do - call - context.called!(self) + begin + call + context.called!(self) + rescue *self.class.exception_classes => e + self.class.exception_handler&.call(e) + context.fail!(error: e) + end end rescue context.rollback! From fabd69034765d57dca527aff45e69806cff4fde2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matas=20Zanevi=C4=8Dius?= Date: Tue, 17 Sep 2019 20:20:41 +0300 Subject: [PATCH 2/3] Allow multiple sends to .fail_on_exception; Docs --- lib/interactor.rb | 62 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/lib/interactor.rb b/lib/interactor.rb index d80d9de..4998f77 100644 --- a/lib/interactor.rb +++ b/lib/interactor.rb @@ -22,9 +22,11 @@ def self.included(base) extend ClassMethods include Hooks + # Internal: Expose instance variables of Interactor instance metaclass + # for reading. class << self - attr_accessor :exception_classes - attr_accessor :exception_handler + attr_reader :exception_classes + attr_reader :exception_handlers end # Public: Gets the Interactor::Context of the Interactor instance. attr_reader :context @@ -33,9 +35,57 @@ class << self # Internal: Interactor class methods. module ClassMethods - def fail_on_exeption(*exception_classes, exception_handler: nil) - @exception_classes = exception_classes - @exception_handler = exception_handler + # Public: Specify exception classes that should result in failing context + # and provide a custom logic on rescued exception before failing. Failing + # the context is raising Interactor::Failure, this exception is silently + # swallowed by the interactor. Note that any code after failing the context + # will not be evaluated. + # + # Examples + # class MyInteractor + # include Interactor + # + # fail_on_exception StandardErro + # fail_on_exception NameError, NoMethodError + # + # exception_handler = ->(e) { ErrorLogger.log(e) } + # + # fail_on_exception MyBespokeError, exception_handler: exception_handler + # + # def call + # exception_raising_logic + # end + # end + # + # MyInteractor.call + # # => # Did you mean? method_missing>> + # + # MyInteractor.call.success? + # # => false + # + # Returned context holds the rescued exception object + # + # MyInteractor.call.error.class.name + # # => "NameError" + # + # Method accepts object representing exception classes of any type that + # will respond to #to_s and return string, as an argument to + # Kernel.const_get will result in previously initialized constant. + # e.g. constant, symbol, string... + + def fail_on_exception(*exceptions_to_fail_on, exception_handler: ->(e) {}) + exceptions_to_fail_on = exceptions_to_fail_on.each do |it| + Kernel.const_get(it.to_s) + end + @exception_classes = Array(exception_classes) | exceptions_to_fail_on + return unless exception_handler + exceptions_to_fail_on.each do |exception_class| + @exception_handlers = Hash(exception_handlers).update( + exception_class.name.to_sym => exception_handler + ) + end end # Public: Invoke an Interactor. This is the primary public API method to an @@ -153,7 +203,7 @@ def run! call context.called!(self) rescue *self.class.exception_classes => e - self.class.exception_handler&.call(e) + self.class.exception_handlers[e.class.name.to_sym]&.call(e) context.fail!(error: e) end end From e4171edf087bf097b9f3a047538b4de8044afca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matas=20Zanevi=C4=8Dius?= Date: Tue, 17 Sep 2019 20:26:43 +0300 Subject: [PATCH 3/3] Use Ruby Standard Style --- interactor.gemspec | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/interactor.gemspec b/interactor.gemspec index ca108d4..0cb25dc 100644 --- a/interactor.gemspec +++ b/interactor.gemspec @@ -1,17 +1,17 @@ require "English" Gem::Specification.new do |spec| - spec.name = "interactor" + spec.name = "interactor" spec.version = "3.1.1" - spec.author = "Collective Idea" - spec.email = "info@collectiveidea.com" + spec.author = "Collective Idea" + spec.email = "info@collectiveidea.com" spec.description = "Interactor provides a common interface for performing complex user interactions." - spec.summary = "Simple interactor implementation" - spec.homepage = "https://github.com/collectiveidea/interactor" - spec.license = "MIT" + spec.summary = "Simple interactor implementation" + spec.homepage = "https://github.com/collectiveidea/interactor" + spec.license = "MIT" - spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) + spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) spec.test_files = spec.files.grep(/^spec/) spec.add_development_dependency "bundler"