lib/determinator.rb in determinator-0.12.1 vs lib/determinator.rb in determinator-1.0.0

- old
+ new

@@ -2,42 +2,93 @@ require 'determinator/control' require 'determinator/feature' require 'determinator/target_group' require 'determinator/retrieve/routemaster' require 'determinator/retrieve/null_retriever' +require 'determinator/cache/fetch_wrapper' module Determinator - # @param :retrieval [Determinator::Retrieve::Routemaster] A retrieval instance for Features - # @param :errors [#call, nil] a proc, accepting an error, which will be called with any errors which occur while determinating - # @param :missing_feature [#call, nil] a proc, accepting a feature name, which will be called any time a feature is requested but isn't available - def self.configure(retrieval:, errors: nil, missing_feature: nil) - @error_logger = errors if errors.respond_to?(:call) - @missing_feature_logger = missing_feature if missing_feature.respond_to?(:call) - @instance = Control.new(retrieval: retrieval) - end + class << self + # @param :retrieval [Determinator::Retrieve::Routemaster] A retrieval instance for Features + # @param :errors [#call, nil] a proc, accepting an error, which will be called with any errors which occur while determinating + # @param :missing_feature [#call, nil] a proc, accepting a feature name, which will be called any time a feature is requested but isn't available + # @param :feature_cache [#call, nil] a caching proc, accepting a feature name, which will return the named feature or yield (and store) if not available + def configure(retrieval:, errors: nil, missing_feature: nil, feature_cache: nil) + self.on_error_logger(&errors) if errors + self.on_missing_feature(&missing_feature) if missing_feature + @feature_cache = feature_cache if feature_cache.respond_to?(:call) + @instance = Control.new(retrieval: retrieval) + end - # @return [Determinator::Control] The currently active instance of determinator. - # @raises [RuntimeError] If no Determinator instance is set up (with `.configure`) - def self.instance - raise "No singleton Determinator instance defined" unless @instance - @instance - end + # Returns the currently configured Determinator::Control instance + # + # @return [Determinator::Control] The currently active instance of determinator. + # @raises [RuntimeError] If no Determinator instance is set up (with `.configure`) + def instance + raise "No singleton Determinator instance defined" unless @instance + @instance + end - # Returns the feature with the given name as Determinator uses it. This is useful for - # debugging issues with the retrieval mechanism which delivers features to Determinator. - # @returns [Determinator::Feature,nil] The feature details Determinator would use for a determination right now. - def self.feature_details(name) - instance.retrieval.retrieve(name) - end + # Defines how errors that shouldn't break your application should be logged + def on_error(&block) + @error_logger = block + end - def self.notice_error(error) - return unless @error_logger + # Defines how to record the moment when a feature which doesn't exist is requested. + # If this happens a lot it indicates poor set up, so can be useful for tracking. + def on_missing_feature(&block) + @missing_feature_logger = block + end - @error_logger.call(error) - end + # Defines code that should execute when a determination is completed. This is particularly + # helpful for preparing or sending events to record that an actor has seen a particular experiment variant. + # + # Please note that this block will be executed _synchronously_ before delivering the determination to the callsite. + # + # @yield [id, guid, feature, determination] Will be called when a determination was requested for the + # specified `feature`, for the actor with `id` and `guid`, and received the determination `determination`. + # @yieldparam id [String, nil] The ID that was used to request the determination + # @yieldparam guid [String, nil] The GUID that was used to request the determination + # @yieldparam feature [Determinator::Feature] The feature that was requested + # @yieldparam determination [String,Boolean] The result of the determination + def on_determination(&block) + @determination_callback = block + end - def self.missing_feature(name) - return unless @missing_feature_logger + # Returns the feature with the given name as Determinator uses it. This is useful for + # debugging issues with the retrieval mechanism which delivers features to Determinator. + # @returns [Determinator::Feature,nil] The feature details Determinator would use for a determination right now. + def feature_details(name) + with_retrieval_cache(name) { instance.retrieval.retrieve(name) } + end - @missing_feature_logger.call(name) + # Allows Determinator to track that an error has happened with determination + # @api private + def notice_error(error) + return unless @error_logger + + error = RuntimeError.new(error) unless error.is_a?(StandardError) + @error_logger.call(error) + end + + # Allows Determinator to track that a feature was requested but was missing + # @api private + def notice_missing_feature(name) + return unless @missing_feature_logger + + @missing_feature_logger.call(name) + end + + def notice_determination(id, guid, feature, determination) + return unless @determination_callback + @determination_callback.call(id, guid, feature, determination) + end + + # Allows access to the chosen caching mechanism for any retrieval plugin. + # @api private + def with_retrieval_cache(name) + return yield unless @feature_cache.respond_to?(:call) + + @feature_cache.call(name) { yield } + end end end