module BusinessFlow # Extends the DSL to support caching of completed processes module Cacheable def self.included(klass) klass.extend(ClassMethods) end def cache_key klass = self.class key = Digest::SHA256.hexdigest(klass.cache_key.call(self, nil).to_s) "#{klass.name.underscore}/#{key}/v2" end # DSL Methods module ClassMethods # Responsible for converting our DSL options into cache store options CacheOptions = Struct.new(:ttl) do def to_store_options # compact is not available in Ruby <2.4 or ActiveSupport < 4, so # we can't use it here. options = {} options[:expires_in] = ttl if ttl options end end def cache_options @cache_options ||= CacheOptions.new end def cache_store(store = nil) if store @cache_store = store else @cache_store ||= if defined?(Rails) Rails.cache else ActiveSupport::Cache::MemoryStore.new end end end def cache_ttl(ttl = nil) if ttl cache_options.ttl = ttl else cache_options.ttl end end def cache_key(key = nil) if key @cache_key = Callable.new(key) else @cache_key ||= Callable.new(:parameter_object) end end # :reek:TooManyStatements This can't be easily simplified any further, # as best as I can tell. def execute(flow) add_cache_key_to_result_class cache_store.fetch(flow.cache_key, cache_options.to_store_options) do result = add_cache_key(super(flow), flow) raise FlowFailedException, result if result.errors? result end rescue FlowFailedException => exc exc.flow end def add_cache_key_to_result_class return if @cache_key_added DSL::PublicField.new(:cache_key).add_to(const_get(:Result)) @cache_key_added = true end # :reek:UtilityFunction Not entirey sure where else to put this. def add_cache_key(result, flow) result.instance_variable_set('@cache_key'.freeze, flow.cache_key) result end end end end