273 lines
8.2 KiB
Ruby
273 lines
8.2 KiB
Ruby
|
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?
|
||
|
logger.debug("Removing unneeded attribute '#{identifier}': #{@values[identifier]}")
|
||
|
end
|
||
|
remove
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|