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

View file

@ -54,6 +54,53 @@ class Sequencer
end end
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 # Creates the class macro `provides` that allows a Unit to
# declare the attributes it will provided via parameter or block. # declare the attributes it will provided via parameter or block.
# On the other hand it returns the declared attributes if # On the other hand it returns the declared attributes if

View file

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

View file

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

View file

@ -21,10 +21,12 @@ class Sequencer
private private
def mapped def mapped
resource_with_indifferent_access = resource.with_indifferent_access @mapped ||= begin
mapping.symbolize_keys.collect do |source, local| resource_with_indifferent_access = resource.with_indifferent_access
[local, resource_with_indifferent_access[source]] mapping.symbolize_keys.collect do |source, local|
end.to_h.with_indifferent_access [local, resource_with_indifferent_access[source]]
end.to_h.with_indifferent_access
end
end end
def mapping def mapping

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@ class Sequencer
class Units < SimpleDelegator class Units < SimpleDelegator
class Attribute class Attribute
attr_accessor :from, :to attr_accessor :from, :to, :optional
# Checks if the attribute will be provided by one or more Units. # Checks if the attribute will be provided by one or more Units.
# #
@ -23,7 +23,25 @@ class Sequencer
# #
# @return [Boolean] # @return [Boolean]
def will_be_used? 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 end
end end

View file

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