From bf874d77c62ac1d2ad62eae8d86f3916380907a1 Mon Sep 17 00:00:00 2001 From: Thorsten Eckel Date: Wed, 18 Mar 2020 10:56:37 +0100 Subject: [PATCH] Enhancement: Add support for optional Sequencer Unit attributes. --- lib/sequencer/state.rb | 19 +++++--- lib/sequencer/unit/base.rb | 47 +++++++++++++++++++ .../unit/exchange/folders/id_path_map.rb | 3 +- .../common/import_job/statistics/update.rb | 4 +- .../unit/import/common/mapping/flat_keys.rb | 10 ++-- .../common/mapping/mixin/provide_mapped.rb | 3 +- .../import/common/model/mixin/skip/action.rb | 2 +- lib/sequencer/units.rb | 5 +- lib/sequencer/units/attribute.rb | 22 ++++++++- lib/sequencer/units/attributes.rb | 4 ++ 10 files changed, 100 insertions(+), 19 deletions(-) diff --git a/lib/sequencer/state.rb b/lib/sequencer/state.rb index 431ddd792..24e4933fc 100644 --- a/lib/sequencer/state.rb +++ b/lib/sequencer/state.rb @@ -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 diff --git a/lib/sequencer/unit/base.rb b/lib/sequencer/unit/base.rb index b8cd57953..7ac49dafa 100644 --- a/lib/sequencer/unit/base.rb +++ b/lib/sequencer/unit/base.rb @@ -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] attributes an optional list of attributes that the Unit optional + # + # @yield [] A block returning a list of attributes + # + # @example Via regular Array 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] 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 diff --git a/lib/sequencer/unit/exchange/folders/id_path_map.rb b/lib/sequencer/unit/exchange/folders/id_path_map.rb index 79bc343de..f739019c8 100644 --- a/lib/sequencer/unit/exchange/folders/id_path_map.rb +++ b/lib/sequencer/unit/exchange/folders/id_path_map.rb @@ -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| diff --git a/lib/sequencer/unit/import/common/import_job/statistics/update.rb b/lib/sequencer/unit/import/common/import_job/statistics/update.rb index 8a4beddfc..47b1130a5 100644 --- a/lib/sequencer/unit/import/common/import_job/statistics/update.rb +++ b/lib/sequencer/unit/import/common/import_job/statistics/update.rb @@ -6,7 +6,8 @@ class Sequencer module Statistics class Update < Sequencer::Unit::Base - uses :statistics_diff + 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 diff --git a/lib/sequencer/unit/import/common/mapping/flat_keys.rb b/lib/sequencer/unit/import/common/mapping/flat_keys.rb index 4caa06bda..6817e768a 100644 --- a/lib/sequencer/unit/import/common/mapping/flat_keys.rb +++ b/lib/sequencer/unit/import/common/mapping/flat_keys.rb @@ -21,10 +21,12 @@ class Sequencer private def mapped - 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 + @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 diff --git a/lib/sequencer/unit/import/common/mapping/mixin/provide_mapped.rb b/lib/sequencer/unit/import/common/mapping/mixin/provide_mapped.rb index 544dc9727..432acb059 100644 --- a/lib/sequencer/unit/import/common/mapping/mixin/provide_mapped.rb +++ b/lib/sequencer/unit/import/common/mapping/mixin/provide_mapped.rb @@ -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 diff --git a/lib/sequencer/unit/import/common/model/mixin/skip/action.rb b/lib/sequencer/unit/import/common/model/mixin/skip/action.rb index bccf8911b..6a24fd6ed 100644 --- a/lib/sequencer/unit/import/common/model/mixin/skip/action.rb +++ b/lib/sequencer/unit/import/common/model/mixin/skip/action.rb @@ -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 diff --git a/lib/sequencer/units.rb b/lib/sequencer/units.rb index b63ca129b..b74e5217d 100644 --- a/lib/sequencer/units.rb +++ b/lib/sequencer/units.rb @@ -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 Array}>] the declarations of the Units def declarations @@ -42,6 +42,7 @@ class Sequencer { uses: unit.uses, provides: unit.provides, + optional: unit.optional, } end end diff --git a/lib/sequencer/units/attribute.rb b/lib/sequencer/units/attribute.rb index b56dcf544..03504f2da 100644 --- a/lib/sequencer/units/attribute.rb +++ b/lib/sequencer/units/attribute.rb @@ -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 diff --git a/lib/sequencer/units/attributes.rb b/lib/sequencer/units/attributes.rb index 6660fa4d1..ade862c12 100644 --- a/lib/sequencer/units/attributes.rb +++ b/lib/sequencer/units/attributes.rb @@ -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