lib/active_interactor/interactor/perform.rb
# frozen_string_literal: true
module ActiveInteractor
module Interactor
# Interactor perform methods. Because {Perform} is a module classes should include {Perform} rather than inherit
# from it.
#
# @author Aaron Allen <hello@aaronmallen.me>
# @since 1.0.0
module Perform
# Interactor perform class methods. Because {ClassMethods} is a module classes should extend {ClassMethods}
# rather than inherit from it.
#
# @author Aaron Allen <hello@aaronmallen.me>
# @since 1.0.0
module ClassMethods
# Initialize a new {Base interactor} instance and call its {Interactor::Perform#perform #perform} method. This
# is the default api to interact with all {Base interactors}.
#
# @since 0.1.0
#
# @example
# class MyInteractor < ActiveInteractor::Base
# def perform
# puts "I performed"
# end
# end
#
# MyInteractor.perform
# "I performed"
# #=> <#MyInteractor::Context>
#
# @param context [Hash, Class] attributes to assign to the {Base interactor} instance's
# {ActiveInteractor::Context::Base context} instance
# @param options [Hash, Perform::Options] options to use for the {.perform}. See {Perform::Options}
# @return [Class] an instance of the {Base interactor} class' {ActiveInteractor::Context::Base context}
# instance
def perform(context = {}, options = {})
new(context).with_options(options).execute_perform
end
# Initialize a new {Base interactor} instance and call its {Interactor::Perform#perform #perform} method without
# rescuing {ActiveInteractor::Error::ContextFailure}.
#
# @since 0.1.0
#
# @example Calling {Perform::ClassMethods#perform! .perform!} without failure
# class MyInteractor < ActiveInteractor::Base
# def perform
# puts "I performed"
# end
# end
#
# MyInteractor.perform!
# "I performed"
# #=> <#MyInteractor::Context>
#
# @example Calling {Perform::ClassMethods#perform! .perform!} with failure
# class MyInteractor < ActiveInteractor::Base
# def perform
# context.fail!
# end
# end
#
# MyInteractor.perform!
# ActiveInteractor::Error::ContextFailure "<#MyInteractor::Context>"
#
# @param context [Hash, Class] attributes to assign to the {Base interactor} instance's
# {ActiveInteractor::Context::Base context} instance
# @param options [Hash, Perform::Options] options to use for the {.perform}. See {Perform::Options}
# @raise [Error::ContextFailure] if the {ActiveInteractor::Context::Base context} instance
# fails.
# @return [Class] an instance of the {Base interactor} class' {ActiveInteractor::Context::Base context}
# instance
def perform!(context = {}, options = {})
new(context).with_options(options).execute_perform!
end
end
# Interactor {Interactor::Perform#perform #perform} options
#
# @author Aaron Allen <hello@aaronmallen.me>
# @since 1.0.0
#
# @!attribute [rw] skip_each_perform_callbacks
# if `true` an {Organizer::Base organizer} will be instructed to skip
# {Organizer::Callbacks::ClassMethods each_perform} callbacks.
#
# @since 1.0.0
#
# @return [Boolean] whether or not to skip {Organizer::Callbacks::ClassMethods each_perform} callbacks
#
# @!attribute [rw] skip_perform_callbacks
# if `true` an {Base interactor} will be instructed to skip {Interactor::Callbacks::ClassMethods perform}
# callbacks.
#
# @since 1.0.0
#
# @return [Boolean] whether or not to skip {Interactor::Callbacks::ClassMethods perform} callbacks.
#
# @!attribute [rw] skip_rollback
# if `true` an {Base interactor} will be instructed to skip {Interactor::Perform#rollback #rollback} on
# {Context::Base context} {ActiveInteractor::Context::Status#fail! failure}.
#
# @since 1.0.0
#
# @return [Boolean] whether or not to skip {Interactor::Perform#rollback #rollback}
#
# @!attribute [rw] skip_rollback_callbacks
# if `true` an {Base interactor} will be instructed to skip {Interactor::Callbacks::ClassMethods rollback}
# callbacks on {Context::Base context} {ActiveInteractor::Context::Status#fail! failure}.
#
# @since 1.0.0
#
# @return [Boolean] whether or not to skip {Interactor::Callbacks::ClassMethods rollback} callbacks.
#
# @!attribute [rw] validate
# if `false` an {Base interactor} will not run validations.
#
# @since 1.0.0
#
# @return [Boolean] whether or to run validations.
#
# @!attribute [rw] validate_on_calling
# if `false` an {Base interactor} will not run validations with the validation context `:calling`.
#
# @since 1.0.0
#
# @return [Boolean] whether or to run validations with the validation context `:calling`
#
# @!attribute [rw] validate_on_called
# if `false` an {Base interactor} will not run validations with the validation context `:called`.
#
# @since 1.0.0
#
# @return [Boolean] whether or to run validation with the validation context `:called`.
#
# @!method initialize(options = {})
# Initialize a new instance of {Options}
#
# @since 1.0.0
#
# @param options [Hash{Symbol=>*}] the attributes to assign to {Options}
# @option options [Boolean] :skip_each_perform_callbacks (false) the {Options#skip_each_perform_callbacks}
# attribute
# @option options [Boolean] :skip_perform_callbacks (false) the {Options#skip_perform_callbacks} attribute
# @option options [Boolean] :skip_rollback (false) the {Options#skip_rollback} attribute
# @option options [Boolean] :skip_rollback_callbacks (false) the {Options#skip_rollback_callbacks} attribute
# @option options [Boolean] :validate (true) the {Options#validate} attribute
# @option options [Boolean] :validate_on_calling (true) the {Options#validate_on_calling} attribute
# @option options [Boolean] :validate_on_called (true) the {Options#validate_on_called} attribute
# @return [Options] a new instance of {Options}
class Options
include ActiveInteractor::Configurable
defaults skip_each_perform_callbacks: false, skip_perform_callbacks: false, skip_rollback: false,
skip_rollback_callbacks: false, validate: true, validate_on_calling: true, validate_on_called: true
end
# @!method execute_perform
# Run the {Base interactor} instance's {#perform} with callbacks and validation.
#
# @api private
# @since 0.1.0
#
# @!method execute_perform!
# Run the {Base interactor} instance's {#perform} with callbacks and validation without rescuing
# {Error::ContextFailure}.
#
# @api private
# @since 0.1.0
delegate :execute_perform, :execute_perform!, to: :worker
# Duplicate an {Base interactor} instance as well as it's {#options} and {ActiveInteractor::Context::Base context}
# instances.
#
# @return [Base] a duplicated {Base interactor} instance
def deep_dup
dupped = dup
%w[@context @options].each do |variable|
dupped.instance_variable_set(variable, instance_variable_get(variable)&.dup)
end
dupped
end
# Options for the {Base interactor} {#perform}
#
# @return [Options] an instance of {Options}
def options
@options ||= ActiveInteractor::Interactor::Perform::Options.new
end
# The steps to run when an {Base interactor} is called. An {Base interactor's} {#perform} method should never be
# called directly on an {Base interactor} instance and should instead be invoked by the {Base interactor's} class
# method {Perform::ClassMethods#perform .perform}.
#
# @since 0.1.0
#
# @abstract {Base interactors} should override {#perform} with the appropriate steps and
# {ActiveInteractor::Context::Base context} mutations required for the {Base interactor} to do its work.
#
# @example
# class MyInteractor < ActiveInteractor::Base
# def perform
# context.first_name = 'Aaron'
# end
# end
#
# MyInteractor.perform
# #=> <#MyInteractor::Context first_name='Aaron'>
def perform; end
# The steps to run when an {Base interactor} {ActiveInteractor::Context::Status#fail! fails}. An
# {Base interactor's} {#rollback} method should never be called directly and is instead called by the
# {Base interactor's} {ActiveInteractor::Context::Base context} when
# {ActiveInteractor::Context::Status#fail! #fail} is called.
#
# @since 0.1.0
#
# @abstract {Base interactors} should override {#rollback} with the appropriate steps and
# {ActiveInteractor::Context::Base context} mutations required for the {Base interactor} to roll back its work.
#
# @example
# class MyInteractor < ActiveInteractor::Base
# def perform
# context.first_name = 'Aaron'
# context.fail!
# end
#
# def rollback
# context.first_name = 'Bob'
# end
# end
#
# MyInteractor.perform
# #=> <#MyInteractor::Context first_name='Bob'>
def rollback; end
# Set {Options options} for an {Base interactor's} {#perform}
#
# @api private
#
# @param options [Hash, Perform::Options] options to use for the perform call. See {Perform::Options}
# @return [self] the {Base interactor} instance
def with_options(options)
@options = if options.is_a?(ActiveInteractor::Interactor::Perform::Options)
options
else
ActiveInteractor::Interactor::Perform::Options.new(options)
end
self
end
private
def worker
ActiveInteractor::Interactor::Worker.new(self)
end
end
end
end