# frozen_string_literal: true 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}/v3" end # DSL Methods module ClassMethods # Responsible for converting our DSL options into cache store options CacheOptions = Struct.new(:ttl) do def to_store_options(flow) # compact is not available in Ruby <2.4 or ActiveSupport < 4, so # we can't use it here. options = {} options[:expires_in] = ttl.call(flow, nil) 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.is_a?(Numeric) cache_options.ttl = proc { ttl } elsif ttl cache_options.ttl = Callable.new(ttl) else cache_options.ttl end end def cache_key(key = nil, &blk) key ||= blk if key @cache_key = Callable.new(key) else @cache_key ||= default_cache_key end end # :reek:UtilityFunction def default_cache_key Callable.new(:_business_flow_dsl_parameters) end def execute(flow) with_cache(flow) do super(flow)._business_flow_cacheable_finalize(flow.cache_key) end rescue FlowFailedException => exc exc.flow end def with_cache(flow, &blk) add_cache_key_to_result_class catch(:halt_step) do return instrument_cache_fetch(flow, &blk) end raise FlowFailedException, flow end def instrument_cache_fetch(flow) instrument(:cache, flow) do |payload| payload[:cache_hit] = true if payload cache_store.fetch(flow.cache_key, cache_options.to_store_options(flow)) do payload[:cache_hit] = false if payload yield end end end RESULT_FINALIZE = proc do |cache_key| @cache_key = cache_key raise FlowFailedException, self if errors? self end def add_cache_key_to_result_class return if @cache_key_added result_class = const_get(:Result) DSL::PublicField.new(:cache_key).add_to(result_class) result_class.send(:define_method, :_business_flow_cacheable_finalize, RESULT_FINALIZE) @cache_key_added = true end end end end