Enhancement: Add support for optional Sequencer Unit attributes.

This commit is contained in:
Thorsten Eckel 2020-03-18 10:56:37 +01:00
parent e865826810
commit bf874d77c6
10 changed files with 100 additions and 19 deletions

View file

@ -51,9 +51,10 @@ class Sequencer
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.
# The attribute gets validated against the .uses and .optionals
# lists of attributes. In the case that an attribute gets used
# that is not declared to be used or optional, an exception
# gets raised.
#
# @param [Symbol] attribute the attribute for which the value is requested.
#
@ -74,6 +75,7 @@ class Sequencer
# Returns the value of the given attribute.
# The attribute DOES NOT get validated against the .uses list of attributes.
# Use this method only in edge cases and prefer .optional macro and state.use otherwise.
#
# @param [Symbol] attribute the attribute for which the value is requested.
#
@ -95,6 +97,7 @@ class Sequencer
# Checks if a value for the given attribute is provided.
# The attribute DOES NOT get validated against the .uses list of attributes.
# Use this method only in edge cases and prefer .optional macro and state.use otherwise.
#
# @param [Symbol] attribute the attribute which should get checked.
#
@ -168,7 +171,7 @@ class Sequencer
def available
@attributes.select do |_identifier, attribute|
@index.between?(attribute.from, attribute.to)
@index.between?(attribute.from, attribute.till)
end.keys
end
@ -181,7 +184,9 @@ class Sequencer
end
def useable?(attribute)
unit.uses.include?(attribute)
return true if unit.uses.include?(attribute)
unit.optional.include?(attribute)
end
def set(attribute, value)
@ -230,6 +235,8 @@ class Sequencer
provided_attr = attribute.will_be_provided?
if !init_param && !provided_attr
next if attribute.optional?
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
@ -261,7 +268,7 @@ class Sequencer
@attributes.delete_if do |identifier, attribute|
remove = !attribute.will_be_used?
remove ||= attribute.to <= @index
remove ||= attribute.till <= @index
if remove && attribute.will_be_used?
logger.public_send(log_level[:cleanup][:remove]) { "Removing unneeded attribute '#{identifier}': #{@values[identifier].inspect}" }
end

View file

@ -54,6 +54,53 @@ class Sequencer
end
end
# Creates the class macro `optional` that allows a Unit to
# declare the attributes it will use via parameter or block.
# On the other hand it returns the declared attributes if
# called without parameters.
#
# This method can be called multiple times and will add the
# given attributes to the list. It takes care of handling
# duplicates so no uniq check is required. It's safe to use
# for inheritance structures and modules.
#
# It additionally creates a getter instance method for each declared
# attribute like e.g. attr_reader does. This allows direct access
# to an attribute via `attribute_name`. See examples.
#
# @param [Array<Symbol>] attributes an optional list of attributes that the Unit optional
#
# @yield [] A block returning a list of attributes
#
# @example Via regular Array<Symbol> parameter
# optional :instance, :action, :connection
#
# @example Via block
# optional do
# additional = method(parameter)
# [:some, additional]
# end
#
# @example Listing declared attributes
# Unit::Name.optional
# # => [:instance, :action, :connection, :some, :suprise]
#
# @example Using declared attribute in the Unit via state object
# state.use(:instance).id
#
# @example Using declared attribute in the Unit via getter
# instance.id
#
# @return [Array<Symbol>] the list of all declared optionals of a Unit.
def self.optional(*attributes, &block)
declaration_accessor(
key: __method__,
attributes: attributes(*attributes, &block)
) do |attribute|
use_getter(attribute)
end
end
# Creates the class macro `provides` that allows a Unit to
# declare the attributes it will provided via parameter or block.
# On the other hand it returns the declared attributes if

View file

@ -5,12 +5,13 @@ class Sequencer
class IdPathMap < Sequencer::Unit::Base
include ::Sequencer::Unit::Exchange::Folders::Mixin::Folder
optional :ews_folder_ids
provides :ews_folder_id_path_map
def process
state.provide(:ews_folder_id_path_map) do
ids = state.optional(:ews_folder_ids)
ids = ews_folder_ids
ids ||= []
ews_folder.id_folder_map.collect do |id, folder|

View file

@ -7,6 +7,7 @@ class Sequencer
class Update < Sequencer::Unit::Base
uses :statistics_diff
optional :import_job
provides :statistics
def process
@ -24,7 +25,6 @@ class Sequencer
private
def statistics
import_job = state.optional(:import_job)
return {} if import_job.nil?
import_job.result

View file

@ -21,11 +21,13 @@ class Sequencer
private
def mapped
@mapped ||= begin
resource_with_indifferent_access = resource.with_indifferent_access
mapping.symbolize_keys.collect do |source, local|
[local, resource_with_indifferent_access[source]]
end.to_h.with_indifferent_access
end
end
def mapping
raise "Missing implementation of '#{__method__}' method for '#{self.class.name}'"

View file

@ -7,13 +7,14 @@ class Sequencer
module ProvideMapped
def self.included(base)
base.optional :mapped
base.provides :mapped
end
private
def existing_mapped
@existing_mapped ||= state.optional(:mapped) || ActiveSupport::HashWithIndifferentAccess.new
@existing_mapped ||= mapped || ActiveSupport::HashWithIndifferentAccess.new
end
def provide_mapped

View file

@ -35,11 +35,11 @@ class Sequencer
end
def self.prepended(base)
base.optional :action
base.extend(ClassMethods)
end
def process
action = state.optional(:action)
if self.class.skip_action?(action)
logger.debug { "Skipping due to provided action #{action.inspect}." }
else

View file

@ -30,11 +30,11 @@ class Sequencer
end
end
# Provides an Array of :uses and :provides declarations for each Unit.
# Provides an Array of :uses, :provides and :optional declarations for each Unit.
#
# @example
# units.declarations
# #=> [{uses: [:question], provides: [:answer], ...}]
# #=> [{uses: [:question], provides: [:answer], optional: [:facts], ...}]
#
# @return [Array<Hash{Symbol => Array<Symbol>}>] the declarations of the Units
def declarations
@ -42,6 +42,7 @@ class Sequencer
{
uses: unit.uses,
provides: unit.provides,
optional: unit.optional,
}
end
end

View file

@ -2,7 +2,7 @@ class Sequencer
class Units < SimpleDelegator
class Attribute
attr_accessor :from, :to
attr_accessor :from, :to, :optional
# Checks if the attribute will be provided by one or more Units.
#
@ -23,7 +23,25 @@ class Sequencer
#
# @return [Boolean]
def will_be_used?
!to.nil?
till.present?
end
def optional?
to.nil? && !optional.nil?
end
def cleanup?(index)
return true if !will_be_used?
till <= index
end
def available?(index)
index.between?(from, till)
end
def till
[to, optional].compact.max
end
end
end

View file

@ -71,6 +71,10 @@ class Sequencer
result[attribute].from = index
end
unit[:optional].try(:each) do |attribute|
result[attribute].optional = index
end
end
end
end