lib/outbacker.rb
require "outbacker/version"
require "configurations"
module Outbacker
include Configurations
DEFAULT_BLACKLIST = [ActiveRecord::Base, ActionController::Base]
DEFAULT_WHITELIST = []
configuration_defaults do |c|
c.blacklist = DEFAULT_BLACKLIST
c.whitelist = DEFAULT_WHITELIST
end
#
# DSL-ish factory method to create an instance of OutcomeHandlerSet
# given a block of outcome handlers.
#
# To be used within your business-logic methods in combination with
# the OutcomeHandlerSet#handle method.
#
def with(outcome_handlers)
outcome_handler_set = OutcomeHandlerSet.new(outcome_handlers)
yield outcome_handler_set
# Just return the triggered outcome and the args intended
# for the outcome callback if no callbacks are provided:
unless outcome_handlers
return outcome_handler_set.triggered_outcome, *outcome_handler_set.args
end
#
# The rest is a check to make sure at least one outcome handler
# was invoked/triggered by the outbacked object, and handled by
# the caller...even if there was an early return from the "with"
# statement, and while preserving any errors raised.
#
rescue
@__exception_raised_outback = true
raise
ensure
if outcome_handlers && !@__exception_raised_outback
raise "No outcome selected" unless outcome_handler_set.outcome_handled?
end
end
#
# Class to encapsulate the processing of a block of outcome handlers.
#
OutcomeHandlerSet = Struct.new(:outcome_handlers,
:triggered_outcome,
:args,
:outcome_handled_by_caller) do
#
# Process the outcome specified by the given outcome_key,
# using the outcome handlers set on this OutcomeHandlerSet
# instance. Any additiona arbitrary arguments can be passed
# through to the corresponding outcome handler callback.
#
def handle(outcome_key, *args)
self.triggered_outcome = outcome_key
self.args = args
if outcome_handlers
outcome_handlers.call(self)
raise "No outcome handler for outcome #{outcome_key}" unless outcome_handled?
end
true
end
#
# Internal method to indicate that the outcome has been
# handled by some han dler.
#
def outcome_handled?
!!self.outcome_handled_by_caller
end
def triggered_outcome_matches?(outcome_key)
outcome_key == self.triggered_outcome
end
#
# Specify an outcome handler callback block for the specified
# outcome key.
#
def of(outcome_key, &outcome_block)
execute_outcome_block(outcome_key, &outcome_block)
end
#
# Provides an alternate way to specify a callback block using
# method names.
#
def method_missing(method_name, *args, &outcome_block)
super unless /^outcome_of_(?<suffix>.*)/ =~ method_name.to_s
outcome_key = suffix.to_sym
execute_outcome_block(outcome_key, &outcome_block)
end
private
#
# Internal helper method to execute the given outcome block
# if it matches the triggered outcome.
#
def execute_outcome_block(outcome_block_key, &outcome_block)
if !outcome_block
raise "No block provided for outcome #{outcome_block_key}"
end
if triggered_outcome_matches?(outcome_block_key)
raise "Outcome #{outcome_block_key} already handled" if outcome_handled?
self.outcome_handled_by_caller = outcome_block_key
outcome_block.call(*self.args)
end
end
end
#
# Restrict where Outbacker can be included.
#
def self.included(target_module)
apply_whitelist(target_module) if Outbacker.configuration.whitelist.any?
apply_blacklist(target_module) if Outbacker.configuration.blacklist.any?
end
def self.apply_whitelist(target_module)
Outbacker.configuration.whitelist.each do |whitelisted_classs|
return if target_module.ancestors.include?(whitelisted_classs)
end
fail "Can only include #{self.name} within a subclass of: #{Outbacker.configuration.whitelist.join(', ')}"
end
def self.apply_blacklist(target_module)
Outbacker.configuration.blacklist.each do |blacklisted_class|
if target_module.ancestors.include?(blacklisted_class)
fail "Cannot include #{self.name} within an #{blacklisted_class} class, a plain-old Ruby object is preferred."
end
end
end
end