# frozen_string_literal: true module PriceHubble module Utils # Provides an easy to use decision DSL which works like a flow control. # (eg. if conditions, or a switch statement) It should DRY out the # processing logic on clients, while make decisions about the status of the # response. The decision helper evaluates the answers to: Should we raise # an error or return a default value? Was the response any good? # # Example: # # decision(bang: true) do |result| # result.fail { nil } # result.bang { Hausgold::AuthenticationError.new(nil, res, res.body) } # result.good { Hausgold::Jwt.new(res.body).clear_changes } # successful?(res) # end module Decision extend ActiveSupport::Concern included do # Allow users to build decision paths and run them according to the # result. This is a kind of flow control like an if condition, or a # switch statement. It contains three decision result paths, the happy # case (good), an error case for regular issues (fail) and a error case # for fatal issues (bang). You can configure the decision which error # behaiviour you prefer by setting the +bang+ argument to true or # false. # # @param bang [Boolean] whenever to bang or not # @yield Runtime to collect the settings and the result # @return [Mixed] the result of the decision (good|fail|bang) block def decision(bang: false, &block) runtime = Runtime.new(on_error: bang ? :bang : :fail) runtime.evaluate(&block) end end # An inline runtime class to abstract the decision making. class Runtime # Generate getters for the runtime settings attr_reader :on_error, :bang_proc, :fail_proc, :good_proc # Create a new decision runtime object which collect all the result # paths, and then evaluates the decision. # # @param on_error [Symbol] the error way def initialize(on_error: :fail) @on_error = on_error @bang_proc = -> { StandardError.new } @fail_proc = @good_proc = -> {} end # Register a new error (bang) way. Requires a block. # # @param block [Proc] the block to run in case of errors def bang(&block) @bang_proc = block end # Register a new error (fail) way. Requires a block. # # @param block [Proc] the block to run in case of errors def fail(&block) @fail_proc = block end # Register a new success (good) way. Requires a block. # # @param block [Proc] the block to run in case of success def good(&block) @good_proc = block end # Returns the prefered error method block, based on the +on_error+ # setting. # # @return [Proc] the error block we should use def error_proc return fail_proc if on_error == :fail -> { raise bang_proc.call } end # Evaluate the decision. # # @yield Runtime to collect the settings and the result # @return [Mixed] the result of the decision (good|fail|bang) block def evaluate result = yield(self) result ? good_proc.call : error_proc.call end end end end end