# frozen_string_literal: true require "active_support/callbacks" require "active_support/core_ext/array/wrap" require_relative "not_executable" require_relative "result" module ActionMan class Base include ActiveSupport::Callbacks RESULT_TYPES = %i[success failure].freeze class_attribute :transaction, default: true class_attribute :exception_if_not_executable, default: false attr_reader :model, :args, :params, :result define_callbacks :execute class << self def before(...) set_callback(:execute, :before, ...) end def after(*filters, &) set_options_for_callbacks!(filters) set_callback(:execute, :after, *filters, &) end def after_success(*filters, &) set_options_for_callbacks!(filters, on: :success) set_callback(:execute, :after, *filters, &) end def after_failure(*filters, &) set_options_for_callbacks!(filters, on: :failure) set_callback(:execute, :after, *filters, &) end def param(name) define_method(name) do params[name] end end def params(*names) names.each { |name| param(name) } end def model(name) define_method(name) do model end end private def set_options_for_callbacks!(args, enforced_options={}) options = args.extract_options!.merge!(enforced_options) args << options return unless options[:on] fire_on = Array.wrap(options[:on]) assert_valid_execute_on(fire_on) options[:if] = [ -> { fire_on.any? { |on| on.to_sym == result.status.to_sym } }, *options[:if] ] end def assert_valid_execute_on(actions) return unless (actions - RESULT_TYPES).any? raise ArgumentError, ":on conditions for after callbacks have to be one of #{RESULT_TYPES}" end end def initialize(model) @model = model @params = {} end def executable? true end def run(params={}) @params = params if executable? run_execute elsif exception_if_not_executable raise NotExecutable else Result.failure end end private def execute raise NotImplementedError end def success!(params={}) throw :finish, Result.success(params:) end def failure!(errors={}) throw :finish, Result.failure(errors:) end def finish!(value) value ? success! : failure! end def run_execute with_transaction do run_callbacks(:execute) do run_with_catch end end result end def with_transaction(&block) if transaction && defined?(ActiveRecord::Base) ActiveRecord::Base.transaction do block.call raise ActiveRecord::Rollback if result.failure? end else block.call end end def run_with_catch response = catch :finish do execute end @result = response.is_a?(Result) ? response : Result.success(output: response) end end end