require "interactor/context" require "interactor/declaration" require "interactor/error" require "interactor/hooks" require "interactor/organizer" require "active_support/concern" # Public: Interactor methods. Because Interactor is a module, custom Interactor # classes should include Interactor rather than inherit from it. # # Examples # # class MyInteractor # include Interactor # # def call # puts context.foo # end # end module Interactor extend ActiveSupport::Concern include Hooks include Declaration included do # Public: Gets the Interactor::Context of the Interactor instance. attr_reader :context end # Internal: Interactor class methods. class_methods do # Public: Invoke an Interactor. This is the primary public API method to an # interactor. # # context - A Hash whose key/value pairs are used in initializing a new # Interactor::Context object. An existing Interactor::Context may # also be given. (default: {}) # # Examples # # MyInteractor.call(foo: "bar") # # => # # # MyInteractor.call # # => # # # Returns the resulting Interactor::Context after manipulation by the # interactor. def call(context = {}) new(context).tap(&:run).context end # Public: Invoke an Interactor. The "call!" method behaves identically to # the "call" method with one notable exception. If the context is failed # during invocation of the interactor, the Interactor::Failure is raised. # # context - A Hash whose key/value pairs are used in initializing a new # Interactor::Context object. An existing Interactor::Context may # also be given. (default: {}) # # Examples # # MyInteractor.call!(foo: "bar") # # => # # # MyInteractor.call! # # => # # # MyInteractor.call!(foo: "baz") # # => Interactor::Failure: # # # Returns the resulting Interactor::Context after manipulation by the # interactor. # Raises Interactor::Failure if the context is failed. def call!(context = {}) new(context).tap(&:run!).context end end # Internal: Initialize an Interactor. # # context - A Hash whose key/value pairs are used in initializing the # interactor's context. An existing Interactor::Context may also be # given. (default: {}) # # Examples # # MyInteractor.new(foo: "bar") # # => #> # # MyInteractor.new # # => #> def initialize(context = {}) @context = self.context_class.build(**context.to_h) end # Internal: Invoke an interactor instance along with all defined hooks. The # "run" method is used internally by the "call" class method. The following # are equivalent: # # MyInteractor.call(foo: "bar") # # => # # # interactor = MyInteractor.new(foo: "bar") # interactor.run # interactor.context # # => # # # After successful invocation of the interactor, the instance is tracked # within the context. If the context is failed or any error is raised, the # context is rolled back. # # Returns nothing. def run run! rescue Failure end # Internal: Invoke an Interactor instance along with all defined hooks. The # "run!" method is used internally by the "call!" class method. The following # are equivalent: # # MyInteractor.call!(foo: "bar") # # => # # # interactor = MyInteractor.new(foo: "bar") # interactor.run! # interactor.context # # => # # # After successful invocation of the interactor, the instance is tracked # within the context. If the context is failed or any error is raised, the # context is rolled back. # # The "run!" method behaves identically to the "run" method with one notable # exception. If the context is failed during invocation of the interactor, # the Interactor::Failure is raised. # # Returns nothing. # Raises Interactor::Failure if the context is failed. def run! with_hooks do call end rescue Failure => e # Make sure we fail the current context when a call! to another interactor fails context.fail!(error: e.context&.error, error_cause: e.cause) end # Public: Invoke an Interactor instance without any hooks, tracking, or # rollback. It is expected that the "call" instance method is overwritten for # each interactor class. # # Returns nothing. def call end end