2017-08-14 11:56:23 +00:00
require 'mixin/rails_logger'
require 'mixin/start_finish_logger'
class Sequencer
class State
include :: Mixin :: RailsLogger
include :: Mixin :: StartFinishLogger
def initialize ( sequence , parameters : { } , expecting : nil )
@index = - 1
@units = sequence . units
@result_index = @units . count
@values = { }
initialize_attributes ( sequence . units )
initialize_parameters ( parameters . with_indifferent_access )
initialize_expectations ( expecting || sequence . expecting )
end
# Stores a value for the given attribute. Value can be a regular object
# or the result of a given code block.
# The attribute gets validated against the .provides list of attributes.
# In the case than an attribute gets provided that is not declared to
# be provided an exception will be raised.
#
# @param [Symbol] attribute the attribute for which the value gets provided.
# @param [Object] value the value that should get stored for the given attribute.
# @yield [] executes the given block and takes the result as the value.
# @yieldreturn [Object] the value for the given attribute.
#
# @example
# state.provide(:sum, 3)
#
# @example
# state.provide(:sum) do
# some_value = rand(100)
# some_value * 3
# end
#
# @raise [RuntimeError] if the attribute is not provideable from the calling Unit
#
# @return [nil]
def provide ( attribute , value = nil )
if provideable? ( attribute )
value = yield if block_given?
set ( attribute , value )
else
value = " UNEXECUTED BLOCK: #{ caller ( 1 .. 1 ) . first } " if block_given?
unprovideable_setter ( attribute , value )
end
end
# Returns the value of the given attribute.
# The attribute gets validated against the .uses list of attributes. In the
# case than an attribute gets used that is not declared to be used
# an exception will be raised.
#
# @param [Symbol] attribute the attribute for which the value is requested.
#
# @example
# state.use(:answer)
# #=> 42
#
# @raise [RuntimeError] if the attribute is not useable from the calling Unit
#
# @return [nil]
def use ( attribute )
if useable? ( attribute )
get ( attribute )
else
unaccessable_getter ( attribute )
end
end
# Returns the value of the given attribute.
# The attribute DOES NOT get validated against the .uses list of attributes.
#
# @param [Symbol] attribute the attribute for which the value is requested.
#
# @example
# state.optional(:answer)
# #=> 42
#
# @example
# state.optional(:unknown)
# #=> nil
#
# @return [Object, nil]
def optional ( attribute )
return get ( attribute ) if @attributes . known? ( attribute )
logger . debug ( " Access to unknown optional attribute ' #{ attribute } '. " )
nil
end
# Checks if a value for the given attribute is provided.
# The attribute DOES NOT get validated against the .uses list of attributes.
#
# @param [Symbol] attribute the attribute which should get checked.
#
# @example
# state.provided?(:answer)
# #=> true
#
# @example
# state.provided?(:unknown)
# #=> false
#
# @return [Boolean]
def provided? ( attribute )
optional ( attribute ) != nil
end
# Unsets the value for the given attribute.
# The attribute gets validated against the .uses list of attributes.
# In the case than an attribute gets unset that is not declared
# to be used an exception will be raised.
#
# @param [Symbol] attribute the attribute for which the value gets unset.
#
# @example
# state.unset(:answer)
#
# @raise [RuntimeError] if the attribute is not useable from the calling Unit
#
# @return [nil]
def unset ( attribute )
value = nil
if useable? ( attribute )
set ( attribute , value )
else
unprovideable_setter ( attribute , value )
end
end
# Handles state processing of the next Unit in the Sequence while executing
# the given block. After the Unit is processed the state will get cleaned up
# and no longer needed attribute values will get discarded.
#
# @yield [] executes the given block and handles the state changes before and afterwards.
#
# @example
# state.process do
# unit.process
# end
#
# @return [nil]
def process
@index += 1
yield
cleanup
end
# Handles state processing of the next Unit in the Sequence while executing
# the given block. After the Unit is processed the state will get cleaned up
# and no longer needed attribute values will get discarded.
#
# @example
# state.to_h
# #=> {"ssl_verify"=>true, "host_url"=>"ldaps://192...", ...}
#
# @return [Hash{Symbol => Object}]
def to_h
available . map { | identifier | [ identifier , @values [ identifier ] ] } . to_h
end
private
def available
@attributes . select do | _identifier , attribute |
@index . between? ( attribute . from , attribute . to )
end . keys
end
def unit ( index = nil )
@units [ index || @index ]
end
def provideable? ( attribute )
unit . provides . include? ( attribute )
end
def useable? ( attribute )
unit . uses . include? ( attribute )
end
def set ( attribute , value )
logger . debug ( " Setting ' #{ attribute } ' value ( #{ value . class . name } ): #{ value . inspect } " )
@values [ attribute ] = value
end
def get ( attribute )
value = @values [ attribute ]
logger . debug ( " Getting ' #{ attribute } ' value ( #{ value . class . name } ): #{ value . inspect } " )
value
end
def unprovideable_setter ( attribute , value )
message = " Unprovideable attribute ' #{ attribute } ' set with value ( #{ value . class . name } ): ' #{ value } ' "
logger . error ( message )
raise message
end
def unaccessable_getter ( attribute )
message = " Unaccessable getter used for attribute ' #{ attribute } ' "
logger . error ( message )
raise message
end
def initialize_attributes ( units )
log_start_finish ( :debug , 'Attributes lifespan initialization' ) do
@attributes = Sequencer :: Units :: Attributes . new ( units . declarations )
logger . debug ( " Attributes lifespan: #{ @attributes . inspect } " )
end
end
def initialize_parameters ( parameters )
logger . debug ( " Initializing Sequencer::State with initial parameters: #{ parameters . inspect } " )
log_start_finish ( :debug , 'Attribute value provisioning check and initialization' ) do
@attributes . each do | identifier , attribute |
if ! attribute . will_be_used?
logger . debug ( " Attribute ' #{ identifier } ' is provided by Unit(s) but never used. " )
next
end
init_param = parameters . key? ( identifier )
provided_attr = attribute . will_be_provided?
if ! init_param && ! provided_attr
message = " Attribute ' #{ identifier } ' is used in Unit ' #{ unit ( attribute . to ) . name } ' (index: #{ attribute . to } ) but is not provided or given via initial parameters. "
logger . error ( message )
raise message
end
# skip if attribute is provided by an Unit but not
# an initial parameter
next if ! init_param
# update 'from' lifespan information for attribute
# since it's provided via the initial parameter
attribute . from = @index
# set initial value
set ( identifier , parameters [ identifier ] )
end
end
end
def initialize_expectations ( expected_attributes )
expected_attributes . each do | identifier |
logger . debug ( " Adding attribute ' #{ identifier } ' to the list of expected result attributes. " )
@attributes [ identifier ] . to = @result_index
end
end
def cleanup
log_start_finish ( :info , " State cleanup of Unit #{ unit . name } (index: #{ @index } ) " ) do
@attributes . delete_if do | identifier , attribute |
remove = ! attribute . will_be_used?
remove || = attribute . to < = @index
if remove && attribute . will_be_used?
2017-11-30 09:11:43 +00:00
logger . debug ( " Removing unneeded attribute ' #{ identifier } ': #{ @values [ identifier ] . inspect } " )
2017-08-14 11:56:23 +00:00
end
remove
end
end
end
end
end