state.rb 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. require_dependency 'mixin/rails_logger'
  2. require_dependency 'mixin/start_finish_logger'
  3. class Sequencer
  4. class State
  5. include ::Mixin::RailsLogger
  6. include ::Mixin::StartFinishLogger
  7. def initialize(sequence, parameters: {}, expecting: nil)
  8. @index = -1
  9. @units = sequence.units
  10. @result_index = @units.count
  11. @values = {}
  12. initialize_attributes(sequence.units)
  13. initialize_parameters(parameters)
  14. initialize_expectations(expecting || sequence.expecting)
  15. end
  16. # Stores a value for the given attribute. Value can be a regular object
  17. # or the result of a given code block.
  18. # The attribute gets validated against the .provides list of attributes.
  19. # In the case than an attribute gets provided that is not declared to
  20. # be provided an exception will be raised.
  21. #
  22. # @param [Symbol] attribute the attribute for which the value gets provided.
  23. # @param [Object] value the value that should get stored for the given attribute.
  24. # @yield [] executes the given block and takes the result as the value.
  25. # @yieldreturn [Object] the value for the given attribute.
  26. #
  27. # @example
  28. # state.provide(:sum, 3)
  29. #
  30. # @example
  31. # state.provide(:sum) do
  32. # some_value = rand(100)
  33. # some_value * 3
  34. # end
  35. #
  36. # @raise [RuntimeError] if the attribute is not provideable from the calling Unit
  37. #
  38. # @return [nil]
  39. def provide(attribute, value = nil)
  40. if provideable?(attribute)
  41. value = yield if block_given?
  42. set(attribute, value)
  43. else
  44. value = "UNEXECUTED BLOCK: #{caller(1..1).first}" if block_given?
  45. unprovideable_setter(attribute, value)
  46. end
  47. end
  48. # Returns the value of the given attribute.
  49. # The attribute gets validated against the .uses and .optionals
  50. # lists of attributes. In the case that an attribute gets used
  51. # that is not declared to be used or optional, an exception
  52. # gets raised.
  53. #
  54. # @param [Symbol] attribute the attribute for which the value is requested.
  55. #
  56. # @example
  57. # state.use(:answer)
  58. # #=> 42
  59. #
  60. # @raise [RuntimeError] if the attribute is not useable from the calling Unit
  61. #
  62. # @return [nil]
  63. def use(attribute)
  64. if useable?(attribute)
  65. get(attribute)
  66. else
  67. unaccessable_getter(attribute)
  68. end
  69. end
  70. # Returns the value of the given attribute.
  71. # The attribute DOES NOT get validated against the .uses list of attributes.
  72. # Use this method only in edge cases and prefer .optional macro and state.use otherwise.
  73. #
  74. # @param [Symbol] attribute the attribute for which the value is requested.
  75. #
  76. # @example
  77. # state.optional(:answer)
  78. # #=> 42
  79. #
  80. # @example
  81. # state.optional(:unknown)
  82. # #=> nil
  83. #
  84. # @return [Object, nil]
  85. def optional(attribute)
  86. return get(attribute) if @attributes.known?(attribute)
  87. logger.public_send(log_level[:optional]) { "Access to unknown optional attribute '#{attribute}'." }
  88. nil
  89. end
  90. # Checks if a value for the given attribute is provided.
  91. # The attribute DOES NOT get validated against the .uses list of attributes.
  92. # Use this method only in edge cases and prefer .optional macro and state.use otherwise.
  93. #
  94. # @param [Symbol] attribute the attribute which should get checked.
  95. #
  96. # @example
  97. # state.provided?(:answer)
  98. # #=> true
  99. #
  100. # @example
  101. # state.provided?(:unknown)
  102. # #=> false
  103. #
  104. # @return [Boolean]
  105. def provided?(attribute)
  106. optional(attribute) != nil
  107. end
  108. # Unsets the value for the given attribute.
  109. # The attribute gets validated against the .uses list of attributes.
  110. # In the case than an attribute gets unset that is not declared
  111. # to be used an exception will be raised.
  112. #
  113. # @param [Symbol] attribute the attribute for which the value gets unset.
  114. #
  115. # @example
  116. # state.unset(:answer)
  117. #
  118. # @raise [RuntimeError] if the attribute is not useable from the calling Unit
  119. #
  120. # @return [nil]
  121. def unset(attribute)
  122. value = nil
  123. if useable?(attribute)
  124. set(attribute, value)
  125. else
  126. unprovideable_setter(attribute, value)
  127. end
  128. end
  129. # Handles state processing of the next Unit in the Sequence while executing
  130. # the given block. After the Unit is processed the state will get cleaned up
  131. # and no longer needed attribute values will get discarded.
  132. #
  133. # @yield [] executes the given block and handles the state changes before and afterwards.
  134. #
  135. # @example
  136. # state.process do
  137. # unit.process
  138. # end
  139. #
  140. # @return [nil]
  141. def process
  142. @index += 1
  143. yield
  144. cleanup
  145. end
  146. # Handles state processing of the next Unit in the Sequence while executing
  147. # the given block. After the Unit is processed the state will get cleaned up
  148. # and no longer needed attribute values will get discarded.
  149. #
  150. # @example
  151. # state.to_h
  152. # #=> {"ssl_verify"=>true, "host_url"=>"ldaps://192...", ...}
  153. #
  154. # @return [Hash{Symbol => Object}]
  155. def to_h
  156. available.map { |identifier| [identifier, @values[identifier]] }.to_h
  157. end
  158. private
  159. def available
  160. @attributes.select do |_identifier, attribute|
  161. @index.between?(attribute.from, attribute.till)
  162. end.keys
  163. end
  164. def unit(index = nil)
  165. @units[index || @index]
  166. end
  167. def provideable?(attribute)
  168. unit.provides.include?(attribute)
  169. end
  170. def useable?(attribute)
  171. return true if unit.uses.include?(attribute)
  172. unit.optional.include?(attribute)
  173. end
  174. def set(attribute, value)
  175. logger.public_send(log_level[:set]) { "Setting '#{attribute}' value (#{value.class.name}): #{value.inspect}" }
  176. @values[attribute] = value
  177. end
  178. def get(attribute)
  179. value = @values[attribute]
  180. logger.public_send(log_level[:get]) { "Getting '#{attribute}' value (#{value.class.name}): #{value.inspect}" }
  181. value
  182. end
  183. def unprovideable_setter(attribute, value)
  184. message = "Unprovideable attribute '#{attribute}' set with value (#{value.class.name}): #{value.inspect}"
  185. logger.error(message)
  186. raise message
  187. end
  188. def unaccessable_getter(attribute)
  189. message = "Unaccessable getter used for attribute '#{attribute}'"
  190. logger.error(message)
  191. raise message
  192. end
  193. def initialize_attributes(units)
  194. log_start_finish(log_level[:attribute_initialization][:start_finish], 'Attributes lifespan initialization') do
  195. @attributes = Sequencer::Units::Attributes.new(units.declarations)
  196. logger.public_send(log_level[:attribute_initialization][:attributes]) { "Attributes lifespan: #{@attributes.inspect}" }
  197. end
  198. end
  199. def initialize_parameters(parameters)
  200. logger.public_send(log_level[:parameter_initialization][:parameters]) { "Initializing Sequencer::State with initial parameters: #{parameters.inspect}" }
  201. log_start_finish(log_level[:parameter_initialization][:start_finish], 'Attribute value provisioning check and initialization') do
  202. @attributes.each do |identifier, attribute|
  203. if !attribute.will_be_used?
  204. logger.public_send(log_level[:parameter_initialization][:unused]) { "Attribute '#{identifier}' is provided by Unit(s) but never used." }
  205. next
  206. end
  207. init_param = parameters.key?(identifier)
  208. provided_attr = attribute.will_be_provided?
  209. if !init_param && !provided_attr
  210. next if attribute.optional?
  211. message = "Attribute '#{identifier}' is used in Unit '#{unit(attribute.to).name}' (index: #{attribute.to}) but is not provided or given via initial parameters."
  212. logger.error(message)
  213. raise message
  214. end
  215. # skip if attribute is provided by an Unit but not
  216. # an initial parameter
  217. next if !init_param
  218. # update 'from' lifespan information for attribute
  219. # since it's provided via the initial parameter
  220. attribute.from = @index
  221. # set initial value
  222. set(identifier, parameters[identifier])
  223. end
  224. end
  225. end
  226. def initialize_expectations(expected_attributes)
  227. expected_attributes.each do |identifier|
  228. logger.public_send(log_level[:expectations_initialization]) { "Adding attribute '#{identifier}' to the list of expected result attributes." }
  229. @attributes[identifier].to = @result_index
  230. end
  231. end
  232. def cleanup
  233. log_start_finish(log_level[:cleanup][:start_finish], "State cleanup of Unit #{unit.name} (index: #{@index})") do
  234. @attributes.delete_if do |identifier, attribute|
  235. remove = !attribute.will_be_used?
  236. remove ||= attribute.till <= @index
  237. if remove && attribute.will_be_used?
  238. logger.public_send(log_level[:cleanup][:remove]) { "Removing unneeded attribute '#{identifier}': #{@values[identifier].inspect}" }
  239. end
  240. remove
  241. end
  242. end
  243. end
  244. def log_level
  245. @log_level ||= Sequencer.log_level_for(:state)
  246. end
  247. end
  248. end