inventory_refresh 0.3.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +25 -30
- data/.github/workflows/ci.yaml +58 -0
- data/.rubocop.yml +3 -3
- data/.rubocop_cc.yml +3 -4
- data/.rubocop_local.yml +5 -2
- data/.whitesource +3 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +10 -4
- data/README.md +1 -2
- data/Rakefile +2 -2
- data/inventory_refresh.gemspec +9 -10
- data/lib/inventory_refresh/application_record_iterator.rb +25 -12
- data/lib/inventory_refresh/graph/topological_sort.rb +24 -26
- data/lib/inventory_refresh/graph.rb +2 -2
- data/lib/inventory_refresh/inventory_collection/builder.rb +37 -15
- data/lib/inventory_refresh/inventory_collection/data_storage.rb +9 -0
- data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +147 -38
- data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +48 -4
- data/lib/inventory_refresh/inventory_collection/index/proxy.rb +35 -3
- data/lib/inventory_refresh/inventory_collection/index/type/base.rb +8 -0
- data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +2 -0
- data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +1 -0
- data/lib/inventory_refresh/inventory_collection/reference.rb +1 -0
- data/lib/inventory_refresh/inventory_collection/references_storage.rb +17 -0
- data/lib/inventory_refresh/inventory_collection/scanner.rb +91 -3
- data/lib/inventory_refresh/inventory_collection/serialization.rb +16 -10
- data/lib/inventory_refresh/inventory_collection.rb +122 -64
- data/lib/inventory_refresh/inventory_object.rb +74 -40
- data/lib/inventory_refresh/inventory_object_lazy.rb +17 -10
- data/lib/inventory_refresh/null_logger.rb +2 -2
- data/lib/inventory_refresh/persister.rb +43 -93
- data/lib/inventory_refresh/save_collection/base.rb +4 -2
- data/lib/inventory_refresh/save_collection/saver/base.rb +114 -15
- data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +129 -51
- data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
- data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +2 -19
- data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +68 -3
- data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +125 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +10 -6
- data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +28 -16
- data/lib/inventory_refresh/save_collection/sweeper.rb +17 -93
- data/lib/inventory_refresh/save_collection/topological_sort.rb +5 -5
- data/lib/inventory_refresh/save_inventory.rb +5 -12
- data/lib/inventory_refresh/target.rb +73 -0
- data/lib/inventory_refresh/target_collection.rb +92 -0
- data/lib/inventory_refresh/version.rb +1 -1
- data/lib/inventory_refresh.rb +2 -0
- metadata +42 -39
- data/.travis.yml +0 -23
- data/lib/inventory_refresh/exception.rb +0 -8
| @@ -41,6 +41,7 @@ module InventoryRefresh | |
| 41 41 | 
             
                  # Boolean helpers the scanner uses from the :inventory_collection
         | 
| 42 42 | 
             
                  delegate :inventory_object_lazy?,
         | 
| 43 43 | 
             
                           :inventory_object?,
         | 
| 44 | 
            +
                           :targeted?,
         | 
| 44 45 | 
             
                           :to => :inventory_collection
         | 
| 45 46 |  | 
| 46 47 | 
             
                  # Methods the scanner uses from the :inventory_collection
         | 
| @@ -51,13 +52,18 @@ module InventoryRefresh | |
| 51 52 | 
             
                           :to => :inventory_collection
         | 
| 52 53 |  | 
| 53 54 | 
             
                  # The data scanner modifies inside of the :inventory_collection
         | 
| 54 | 
            -
                  delegate : | 
| 55 | 
            +
                  delegate :all_manager_uuids_scope,
         | 
| 56 | 
            +
                           :association,
         | 
| 55 57 | 
             
                           :arel,
         | 
| 56 58 | 
             
                           :attribute_references,
         | 
| 57 59 | 
             
                           :custom_save_block,
         | 
| 58 60 | 
             
                           :data_collection_finalized=,
         | 
| 59 61 | 
             
                           :dependency_attributes,
         | 
| 62 | 
            +
                           :targeted?,
         | 
| 63 | 
            +
                           :targeted_scope,
         | 
| 60 64 | 
             
                           :parent,
         | 
| 65 | 
            +
                           :parent_inventory_collections,
         | 
| 66 | 
            +
                           :parent_inventory_collections=,
         | 
| 61 67 | 
             
                           :references,
         | 
| 62 68 | 
             
                           :transitive_dependency_attributes,
         | 
| 63 69 | 
             
                           :to => :inventory_collection
         | 
| @@ -72,6 +78,11 @@ module InventoryRefresh | |
| 72 78 | 
             
                    # Scan InventoryCollection InventoryObjects and store the results inside of the InventoryCollection
         | 
| 73 79 | 
             
                    data.each do |inventory_object|
         | 
| 74 80 | 
             
                      scan_inventory_object!(inventory_object)
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                      if targeted? && parent_inventory_collections.blank?
         | 
| 83 | 
            +
                        # We want to track what manager_uuids we should query from a db, for the targeted refresh
         | 
| 84 | 
            +
                        targeted_scope << inventory_object.reference
         | 
| 85 | 
            +
                      end
         | 
| 75 86 | 
             
                    end
         | 
| 76 87 |  | 
| 77 88 | 
             
                    # Scan InventoryCollection skeletal data
         | 
| @@ -79,12 +90,81 @@ module InventoryRefresh | |
| 79 90 | 
             
                      scan_inventory_object!(inventory_object)
         | 
| 80 91 | 
             
                    end
         | 
| 81 92 |  | 
| 93 | 
            +
                    build_parent_inventory_collections!
         | 
| 94 | 
            +
                    scan_all_manager_uuids_scope!
         | 
| 95 | 
            +
             | 
| 82 96 | 
             
                    # Mark InventoryCollection as finalized aka. scanned
         | 
| 83 97 | 
             
                    self.data_collection_finalized = true
         | 
| 84 98 | 
             
                  end
         | 
| 85 99 |  | 
| 86 100 | 
             
                  private
         | 
| 87 101 |  | 
| 102 | 
            +
                  def scan_all_manager_uuids_scope!
         | 
| 103 | 
            +
                    return if all_manager_uuids_scope.nil?
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    all_manager_uuids_scope.each do |scope|
         | 
| 106 | 
            +
                      scope.each_value do |value|
         | 
| 107 | 
            +
                        scan_all_manager_uuids_scope_attribute!(value)
         | 
| 108 | 
            +
                      end
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  def build_parent_inventory_collections!
         | 
| 113 | 
            +
                    if parent_inventory_collections.nil?
         | 
| 114 | 
            +
                      build_parent_inventory_collection!
         | 
| 115 | 
            +
                    else
         | 
| 116 | 
            +
                      # We can't figure out what immediate parent is, we'll add parent_inventory_collections as dependencies
         | 
| 117 | 
            +
                      parent_inventory_collections.each { |ic_name| add_parent_inventory_collection_dependency!(ic_name) }
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    return if parent_inventory_collections.blank?
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                    # Transform InventoryCollection object names to actual objects
         | 
| 123 | 
            +
                    self.parent_inventory_collections = parent_inventory_collections.map { |x| load_inventory_collection_by_name(x) }
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  def build_parent_inventory_collection!
         | 
| 127 | 
            +
                    return unless supports_building_inventory_collection?
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                    if association.present? && parent.present? && associations_hash[association].present?
         | 
| 130 | 
            +
                      # Add immediate parent IC as dependency
         | 
| 131 | 
            +
                      add_parent_inventory_collection_dependency!(associations_hash[association])
         | 
| 132 | 
            +
                      # Add root IC in parent_inventory_collections
         | 
| 133 | 
            +
                      self.parent_inventory_collections = [find_parent_inventory_collection(associations_hash, inventory_collection.association)]
         | 
| 134 | 
            +
                    end
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  def supports_building_inventory_collection?
         | 
| 138 | 
            +
                    # Don't try to introspect ICs with custom query or saving code
         | 
| 139 | 
            +
                    return if arel.present? || custom_save_block.present?
         | 
| 140 | 
            +
                    # We support :parent_inventory_collections only for targeted mode, where all ICs are present
         | 
| 141 | 
            +
                    return unless targeted?
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                    true
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  def add_parent_inventory_collection_dependency!(ic_name)
         | 
| 147 | 
            +
                    ic = load_inventory_collection_by_name(ic_name)
         | 
| 148 | 
            +
                    (dependency_attributes[:__parent_inventory_collections] ||= Set.new) << ic if ic
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  def find_parent_inventory_collection(hash, name)
         | 
| 152 | 
            +
                    if hash[name]
         | 
| 153 | 
            +
                      find_parent_inventory_collection(hash, hash[name])
         | 
| 154 | 
            +
                    else
         | 
| 155 | 
            +
                      name
         | 
| 156 | 
            +
                    end
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  def load_inventory_collection_by_name(name)
         | 
| 160 | 
            +
                    ic = indexed_inventory_collections[name]
         | 
| 161 | 
            +
                    if ic.nil? && targeted?
         | 
| 162 | 
            +
                      raise "Can't find InventoryCollection :#{name} referenced from #{inventory_collection}"
         | 
| 163 | 
            +
                    end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                    ic
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
             | 
| 88 168 | 
             
                  def scan_inventory_object!(inventory_object)
         | 
| 89 169 | 
             
                    inventory_object.data.each do |key, value|
         | 
| 90 170 | 
             
                      if value.kind_of?(Array)
         | 
| @@ -103,8 +183,16 @@ module InventoryRefresh | |
| 103 183 | 
             
                    value_inventory_collection.add_reference(value.reference, :key => value.key)
         | 
| 104 184 | 
             
                  end
         | 
| 105 185 |  | 
| 186 | 
            +
                  def scan_all_manager_uuids_scope_attribute!(value)
         | 
| 187 | 
            +
                    return unless loadable?(value)
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    add_reference(value.inventory_collection, value)
         | 
| 190 | 
            +
                    (dependency_attributes[:__all_manager_uuids_scope] ||= Set.new) << value.inventory_collection
         | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
             | 
| 106 193 | 
             
                  def scan_inventory_object_attribute!(key, value)
         | 
| 107 194 | 
             
                    return unless loadable?(value)
         | 
| 195 | 
            +
             | 
| 108 196 | 
             
                    value_inventory_collection = value.inventory_collection
         | 
| 109 197 |  | 
| 110 198 | 
             
                    # Storing attributes and their dependencies
         | 
| @@ -114,9 +202,9 @@ module InventoryRefresh | |
| 114 202 | 
             
                    # e.g. load all the referenced uuids from a DB
         | 
| 115 203 | 
             
                    add_reference(value_inventory_collection, value)
         | 
| 116 204 |  | 
| 117 | 
            -
                    if inventory_object_lazy?(value)
         | 
| 205 | 
            +
                    if inventory_object_lazy?(value) && value.transitive_dependency?
         | 
| 118 206 | 
             
                      # Storing if attribute is a transitive dependency, so a lazy_find :key results in dependency
         | 
| 119 | 
            -
                      transitive_dependency_attributes << key | 
| 207 | 
            +
                      transitive_dependency_attributes << key
         | 
| 120 208 | 
             
                    end
         | 
| 121 209 | 
             
                  end
         | 
| 122 210 | 
             
                end
         | 
| @@ -3,7 +3,10 @@ require "active_support/core_ext/module/delegation" | |
| 3 3 | 
             
            module InventoryRefresh
         | 
| 4 4 | 
             
              class InventoryCollection
         | 
| 5 5 | 
             
                class Serialization
         | 
| 6 | 
            -
                  delegate : | 
| 6 | 
            +
                  delegate :all_manager_uuids,
         | 
| 7 | 
            +
                           :all_manager_uuids=,
         | 
| 8 | 
            +
                           :build,
         | 
| 9 | 
            +
                           :targeted_scope,
         | 
| 7 10 | 
             
                           :data,
         | 
| 8 11 | 
             
                           :inventory_object_lazy?,
         | 
| 9 12 | 
             
                           :inventory_object?,
         | 
| @@ -25,6 +28,8 @@ module InventoryRefresh | |
| 25 28 | 
             
                  # @param available_inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of available
         | 
| 26 29 | 
             
                  #        InventoryCollection objects
         | 
| 27 30 | 
             
                  def from_hash(inventory_objects_data, available_inventory_collections)
         | 
| 31 | 
            +
                    targeted_scope.merge!(inventory_objects_data["manager_uuids"].map(&:symbolize_keys!)) if inventory_objects_data["manager_uuids"]
         | 
| 32 | 
            +
             | 
| 28 33 | 
             
                    (inventory_objects_data['data'] || []).each do |inventory_object_data|
         | 
| 29 34 | 
             
                      build(hash_to_data(inventory_object_data, available_inventory_collections).symbolize_keys!)
         | 
| 30 35 | 
             
                    end
         | 
| @@ -32,12 +37,8 @@ module InventoryRefresh | |
| 32 37 | 
             
                    (inventory_objects_data['partial_data'] || []).each do |inventory_object_data|
         | 
| 33 38 | 
             
                      skeletal_primary_index.build(hash_to_data(inventory_object_data, available_inventory_collections).symbolize_keys!)
         | 
| 34 39 | 
             
                    end
         | 
| 35 | 
            -
                  end
         | 
| 36 40 |  | 
| 37 | 
            -
             | 
| 38 | 
            -
                    sweep_scope.map do |s|
         | 
| 39 | 
            -
                      hash_to_data(s, available_inventory_collections).symbolize_keys!
         | 
| 40 | 
            -
                    end
         | 
| 41 | 
            +
                    self.all_manager_uuids = inventory_objects_data['all_manager_uuids']
         | 
| 41 42 | 
             
                  end
         | 
| 42 43 |  | 
| 43 44 | 
             
                  # Serializes InventoryCollection's data storage into Array of Hashes
         | 
| @@ -46,15 +47,14 @@ module InventoryRefresh | |
| 46 47 | 
             
                  def to_hash
         | 
| 47 48 | 
             
                    {
         | 
| 48 49 | 
             
                      :name              => name,
         | 
| 50 | 
            +
                      # TODO(lsmola) we do not support nested references here, should we?
         | 
| 51 | 
            +
                      :manager_uuids     => targeted_scope.primary_references.values.map(&:full_reference),
         | 
| 52 | 
            +
                      :all_manager_uuids => all_manager_uuids,
         | 
| 49 53 | 
             
                      :data              => data.map { |x| data_to_hash(x.data) },
         | 
| 50 54 | 
             
                      :partial_data      => skeletal_primary_index.index_data.map { |x| data_to_hash(x.data) },
         | 
| 51 55 | 
             
                    }
         | 
| 52 56 | 
             
                  end
         | 
| 53 57 |  | 
| 54 | 
            -
                  def sweep_scope_to_hash(sweep_scope)
         | 
| 55 | 
            -
                    sweep_scope.map { |x| data_to_hash(x) }
         | 
| 56 | 
            -
                  end
         | 
| 57 | 
            -
             | 
| 58 58 | 
             
                  private
         | 
| 59 59 |  | 
| 60 60 | 
             
                  # Converts InventoryRefresh::InventoryObject or InventoryRefresh::InventoryObjectLazy into Hash
         | 
| @@ -107,6 +107,10 @@ module InventoryRefresh | |
| 107 107 | 
             
                    hash.transform_values do |value|
         | 
| 108 108 | 
             
                      if value.kind_of?(Hash) && value['inventory_collection_name']
         | 
| 109 109 | 
             
                        hash_to_lazy_relation(value, available_inventory_collections, depth)
         | 
| 110 | 
            +
                      elsif value.kind_of?(Array) && value.first.kind_of?(Hash) && value.first['inventory_collection_name']
         | 
| 111 | 
            +
                        # TODO(lsmola) do we need to compact it sooner? What if first element is nil? On the other hand, we want to
         | 
| 112 | 
            +
                        # deprecate this Vm HABTM assignment because it's not effective
         | 
| 113 | 
            +
                        value.compact.map { |x| hash_to_lazy_relation(x, available_inventory_collections, depth) }
         | 
| 110 114 | 
             
                      else
         | 
| 111 115 | 
             
                        value
         | 
| 112 116 | 
             
                      end
         | 
| @@ -124,6 +128,8 @@ module InventoryRefresh | |
| 124 128 | 
             
                    data.transform_values do |value|
         | 
| 125 129 | 
             
                      if inventory_object_lazy?(value) || inventory_object?(value)
         | 
| 126 130 | 
             
                        lazy_relation_to_hash(value, depth)
         | 
| 131 | 
            +
                      elsif value.kind_of?(Array) && (inventory_object_lazy?(value.compact.first) || inventory_object?(value.compact.first))
         | 
| 132 | 
            +
                        value.compact.map { |x| lazy_relation_to_hash(x, depth) }
         | 
| 127 133 | 
             
                      else
         | 
| 128 134 | 
             
                        value
         | 
| 129 135 | 
             
                      end
         | 
| @@ -71,17 +71,30 @@ module InventoryRefresh | |
| 71 71 | 
             
                #   InventoryCollection, since it is already persisted into the DB.
         | 
| 72 72 | 
             
                attr_accessor :saved
         | 
| 73 73 |  | 
| 74 | 
            +
                # If present, InventoryCollection switches into delete_complement mode, where it will
         | 
| 75 | 
            +
                # delete every record from the DB, that is not present in this list. This is used for the batch processing,
         | 
| 76 | 
            +
                # where we don't know which InventoryObject should be deleted, but we know all manager_uuids of all
         | 
| 77 | 
            +
                # InventoryObject objects that exists in the provider.
         | 
| 78 | 
            +
                #
         | 
| 79 | 
            +
                # @return [Array, nil] nil or a list of all :manager_uuids that are present in the Provider's InventoryCollection.
         | 
| 80 | 
            +
                attr_accessor :all_manager_uuids
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                # @return [Array, nil] Scope for applying :all_manager_uuids
         | 
| 83 | 
            +
                attr_accessor :all_manager_uuids_scope
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                # @return [String] Timestamp in UTC before fetching :all_manager_uuids
         | 
| 86 | 
            +
                attr_accessor :all_manager_uuids_timestamp
         | 
| 87 | 
            +
             | 
| 74 88 | 
             
                # @return [Set] A set of InventoryCollection objects that depends on this InventoryCollection object.
         | 
| 75 89 | 
             
                attr_accessor :dependees
         | 
| 76 90 |  | 
| 77 | 
            -
                 | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
                            :assert_graph_integrity
         | 
| 91 | 
            +
                # @return [Array<Symbol>] @see #parent_inventory_collections documentation of InventoryCollection.new's initialize_ic_relations()
         | 
| 92 | 
            +
                #   parameters
         | 
| 93 | 
            +
                attr_accessor :parent_inventory_collections
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                attr_accessor :attributes_blacklist, :attributes_whitelist
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                attr_reader :model_class, :strategy, :custom_save_block, :parent, :internal_attributes, :delete_method, :dependency_attributes, :manager_ref, :create_only, :association, :complete, :update_only, :transitive_dependency_attributes, :check_changed, :arel, :inventory_object_attributes, :name, :saver_strategy, :targeted_scope, :default_values, :targeted_arel, :targeted, :manager_ref_allowed_nil, :use_ar_object, :created_records, :updated_records, :deleted_records, :retention_strategy, :custom_reconnect_block, :batch_extra_attributes, :references_storage, :unconnected_edges, :assert_graph_integrity
         | 
| 85 98 |  | 
| 86 99 | 
             
                delegate :<<,
         | 
| 87 100 | 
             
                         :build,
         | 
| @@ -112,6 +125,7 @@ module InventoryRefresh | |
| 112 125 | 
             
                         :lazy_find_by,
         | 
| 113 126 | 
             
                         :named_ref,
         | 
| 114 127 | 
             
                         :primary_index,
         | 
| 128 | 
            +
                         :reindex_secondary_indexes!,
         | 
| 115 129 | 
             
                         :skeletal_primary_index,
         | 
| 116 130 | 
             
                         :to => :index_proxy
         | 
| 117 131 |  | 
| @@ -134,18 +148,28 @@ module InventoryRefresh | |
| 134 148 | 
             
                             properties[:check_changed],
         | 
| 135 149 | 
             
                             properties[:update_only],
         | 
| 136 150 | 
             
                             properties[:use_ar_object],
         | 
| 151 | 
            +
                             properties[:targeted],
         | 
| 137 152 | 
             
                             properties[:assert_graph_integrity])
         | 
| 138 153 |  | 
| 139 154 | 
             
                  init_strategies(properties[:strategy],
         | 
| 140 | 
            -
                                  properties[: | 
| 155 | 
            +
                                  properties[:saver_strategy],
         | 
| 156 | 
            +
                                  properties[:retention_strategy],
         | 
| 157 | 
            +
                                  properties[:delete_method])
         | 
| 141 158 |  | 
| 142 159 | 
             
                  init_references(properties[:manager_ref],
         | 
| 143 160 | 
             
                                  properties[:manager_ref_allowed_nil],
         | 
| 144 | 
            -
                                  properties[:secondary_refs] | 
| 161 | 
            +
                                  properties[:secondary_refs],
         | 
| 162 | 
            +
                                  properties[:manager_uuids])
         | 
| 145 163 |  | 
| 146 | 
            -
                   | 
| 164 | 
            +
                  init_all_manager_uuids(properties[:all_manager_uuids],
         | 
| 165 | 
            +
                                         properties[:all_manager_uuids_scope],
         | 
| 166 | 
            +
                                         properties[:all_manager_uuids_timestamp])
         | 
| 147 167 |  | 
| 148 | 
            -
                   | 
| 168 | 
            +
                  init_ic_relations(properties[:dependency_attributes],
         | 
| 169 | 
            +
                                    properties[:parent_inventory_collections])
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  init_arels(properties[:arel],
         | 
| 172 | 
            +
                             properties[:targeted_arel])
         | 
| 149 173 |  | 
| 150 174 | 
             
                  init_custom_procs(properties[:custom_save_block],
         | 
| 151 175 | 
             
                                    properties[:custom_reconnect_block])
         | 
| @@ -203,16 +227,16 @@ module InventoryRefresh | |
| 203 227 |  | 
| 204 228 | 
             
                # @return [Array<ActiveRecord::ConnectionAdapters::IndexDefinition>] array of all unique indexes known to model
         | 
| 205 229 | 
             
                def unique_indexes
         | 
| 206 | 
            -
                  @ | 
| 230 | 
            +
                  return @unique_indexes if @unique_indexes
         | 
| 207 231 |  | 
| 208 | 
            -
                  @ | 
| 232 | 
            +
                  @unique_indexes = model_class.connection.indexes(model_class.table_name).select(&:unique)
         | 
| 209 233 |  | 
| 210 | 
            -
                  if @ | 
| 234 | 
            +
                  if @unique_indexes.blank?
         | 
| 211 235 | 
             
                    raise "#{self} and its table #{model_class.table_name} must have a unique index defined, to"\
         | 
| 212 | 
            -
             | 
| 236 | 
            +
                          " be able to use saver_strategy :concurrent_safe_batch."
         | 
| 213 237 | 
             
                  end
         | 
| 214 238 |  | 
| 215 | 
            -
                  @ | 
| 239 | 
            +
                  @unique_indexes
         | 
| 216 240 | 
             
                end
         | 
| 217 241 |  | 
| 218 242 | 
             
                # Finds an index that fits the list of columns (keys) the best
         | 
| @@ -222,7 +246,7 @@ module InventoryRefresh | |
| 222 246 | 
             
                # @return [ActiveRecord::ConnectionAdapters::IndexDefinition] unique index fitting the keys
         | 
| 223 247 | 
             
                def unique_index_for(keys)
         | 
| 224 248 | 
             
                  @unique_index_for_keys_cache ||= {}
         | 
| 225 | 
            -
                  @unique_index_for_keys_cache[keys] if @unique_index_for_keys_cache[keys]
         | 
| 249 | 
            +
                  return @unique_index_for_keys_cache[keys] if @unique_index_for_keys_cache[keys]
         | 
| 226 250 |  | 
| 227 251 | 
             
                  # Take the uniq key having the least number of columns
         | 
| 228 252 | 
             
                  @unique_index_for_keys_cache[keys] = uniq_keys_candidates(keys).min_by { |x| x.columns.count }
         | 
| @@ -239,7 +263,7 @@ module InventoryRefresh | |
| 239 263 |  | 
| 240 264 | 
             
                  if unique_indexes.blank? || uniq_key_candidates.blank?
         | 
| 241 265 | 
             
                    raise "#{self} and its table #{model_class.table_name} must have a unique index defined "\
         | 
| 242 | 
            -
             | 
| 266 | 
            +
                          "covering columns #{keys} to be able to use saver_strategy :concurrent_safe_batch."
         | 
| 243 267 | 
             
                  end
         | 
| 244 268 |  | 
| 245 269 | 
             
                  uniq_key_candidates
         | 
| @@ -268,7 +292,7 @@ module InventoryRefresh | |
| 268 292 | 
             
                def internal_timestamp_columns
         | 
| 269 293 | 
             
                  return @internal_timestamp_columns if @internal_timestamp_columns
         | 
| 270 294 |  | 
| 271 | 
            -
                  @internal_timestamp_columns = %i | 
| 295 | 
            +
                  @internal_timestamp_columns = %i[created_at created_on updated_at updated_on].collect do |timestamp_col|
         | 
| 272 296 | 
             
                    timestamp_col if supports_column?(timestamp_col)
         | 
| 273 297 | 
             
                  end.compact
         | 
| 274 298 | 
             
                end
         | 
| @@ -282,11 +306,6 @@ module InventoryRefresh | |
| 282 306 | 
             
                  @base_columns ||= (unique_index_columns + internal_columns + not_null_columns).uniq
         | 
| 283 307 | 
             
                end
         | 
| 284 308 |  | 
| 285 | 
            -
                # @return [Array<Symbol>] Array of all column names on the InventoryCollection
         | 
| 286 | 
            -
                def all_column_names
         | 
| 287 | 
            -
                  @all_column_names ||= model_class.columns.map { |x| x.name.to_sym }
         | 
| 288 | 
            -
                end
         | 
| 289 | 
            -
             | 
| 290 309 | 
             
                # @param value [Object] Object we want to test
         | 
| 291 310 | 
             
                # @return [Boolean] true is value is kind of InventoryRefresh::InventoryObject
         | 
| 292 311 | 
             
                def inventory_object?(value)
         | 
| @@ -311,7 +330,7 @@ module InventoryRefresh | |
| 311 330 |  | 
| 312 331 | 
             
                # Convert manager_ref list of attributes to list of DB columns
         | 
| 313 332 | 
             
                #
         | 
| 314 | 
            -
                # @return [Array<String>]  | 
| 333 | 
            +
                # @return [Array<String>] true is processing of this InventoryCollection will be in targeted mode
         | 
| 315 334 | 
             
                def manager_ref_to_cols
         | 
| 316 335 | 
             
                  # TODO(lsmola) this should contain the polymorphic _type, otherwise the IC with polymorphic unique key will get
         | 
| 317 336 | 
             
                  # conflicts
         | 
| @@ -342,25 +361,13 @@ module InventoryRefresh | |
| 342 361 | 
             
                #
         | 
| 343 362 | 
             
                # @return [Array<Symbol>] attributes that are needed for saving of the record
         | 
| 344 363 | 
             
                def fixed_attributes
         | 
| 345 | 
            -
                  not_null_attributes = []
         | 
| 346 | 
            -
             | 
| 347 364 | 
             
                  if model_class
         | 
| 348 | 
            -
                    # Attrs having presence validator
         | 
| 349 365 | 
             
                    presence_validators = model_class.validators.detect { |x| x.kind_of?(ActiveRecord::Validations::PresenceValidator) }
         | 
| 350 | 
            -
                    not_null_attributes += presence_validators.attributes if presence_validators.present?
         | 
| 351 | 
            -
             | 
| 352 | 
            -
                    # Column names having NOT NULL constraint
         | 
| 353 | 
            -
                    non_null_constraints = model_class.columns_hash.values.reject(&:null).map(&:name) - [model_class.primary_key]
         | 
| 354 | 
            -
                    not_null_attributes += non_null_constraints.map(&:to_sym)
         | 
| 355 | 
            -
             | 
| 356 | 
            -
                    # Column names having NOT NULL constraint transformed to relation names
         | 
| 357 | 
            -
                    not_null_attributes += non_null_constraints.map {|x| foreign_key_to_association_mapping[x]}.compact
         | 
| 358 366 | 
             
                  end
         | 
| 359 367 | 
             
                  # Attributes that has to be always on the entity, so attributes making unique index of the record + attributes
         | 
| 360 368 | 
             
                  # that have presence validation
         | 
| 361 | 
            -
             | 
| 362 369 | 
             
                  fixed_attributes = manager_ref
         | 
| 363 | 
            -
                  fixed_attributes +=  | 
| 370 | 
            +
                  fixed_attributes += presence_validators.attributes if presence_validators.present?
         | 
| 364 371 | 
             
                  fixed_attributes
         | 
| 365 372 | 
             
                end
         | 
| 366 373 |  | 
| @@ -422,6 +429,7 @@ module InventoryRefresh | |
| 422 429 | 
             
                                          :parent                => parent,
         | 
| 423 430 | 
             
                                          :arel                  => arel,
         | 
| 424 431 | 
             
                                          :strategy              => strategy,
         | 
| 432 | 
            +
                                          :saver_strategy        => saver_strategy,
         | 
| 425 433 | 
             
                                          :custom_save_block     => custom_save_block,
         | 
| 426 434 | 
             
                                          # We want cloned IC to be update only, since this is used for cycle resolution
         | 
| 427 435 | 
             
                                          :update_only           => true,
         | 
| @@ -459,31 +467,79 @@ module InventoryRefresh | |
| 459 467 |  | 
| 460 468 | 
             
                # @return [Integer] default batch size for talking to the DB
         | 
| 461 469 | 
             
                def batch_size
         | 
| 470 | 
            +
                  # TODO(lsmola) mode to the settings
         | 
| 462 471 | 
             
                  1000
         | 
| 463 472 | 
             
                end
         | 
| 464 473 |  | 
| 465 474 | 
             
                # @return [Integer] default batch size for talking to the DB if not using ApplicationRecord objects
         | 
| 466 475 | 
             
                def batch_size_pure_sql
         | 
| 476 | 
            +
                  # TODO(lsmola) mode to the settings
         | 
| 467 477 | 
             
                  10_000
         | 
| 468 478 | 
             
                end
         | 
| 469 479 |  | 
| 480 | 
            +
                # Returns a list of stringified uuids of all scoped InventoryObjects, which is used for scoping in targeted mode
         | 
| 481 | 
            +
                #
         | 
| 482 | 
            +
                # @return [Array<String>] list of stringified uuids of all scoped InventoryObjects
         | 
| 483 | 
            +
                def manager_uuids
         | 
| 484 | 
            +
                  # TODO(lsmola) LEGACY: this is still being used by :targetel_arel definitions and it expects array of strings
         | 
| 485 | 
            +
                  raise "This works only for :manager_ref size 1" if manager_ref.size > 1
         | 
| 486 | 
            +
             | 
| 487 | 
            +
                  key = manager_ref.first
         | 
| 488 | 
            +
                  transform_references_to_hashes(targeted_scope.primary_references).map { |x| x[key] }
         | 
| 489 | 
            +
                end
         | 
| 490 | 
            +
             | 
| 470 491 | 
             
                # Builds a multiselection conditions like (table1.a = a1 AND table2.b = b1) OR (table1.a = a2 AND table2.b = b2)
         | 
| 471 492 | 
             
                #
         | 
| 472 493 | 
             
                # @param hashes [Array<Hash>] data we want to use for the query
         | 
| 473 494 | 
             
                # @param keys [Array<Symbol>] keys of attributes involved
         | 
| 474 495 | 
             
                # @return [String] A condition usable in .where of an ActiveRecord relation
         | 
| 475 | 
            -
                def build_multi_selection_condition(hashes, keys =  | 
| 496 | 
            +
                def build_multi_selection_condition(hashes, keys = manager_ref)
         | 
| 476 497 | 
             
                  arel_table = model_class.arel_table
         | 
| 477 498 | 
             
                  # We do pure SQL OR, since Arel is nesting every .or into another parentheses, otherwise this would be just
         | 
| 478 499 | 
             
                  # inject(:or) instead of to_sql with .join(" OR ")
         | 
| 479 500 | 
             
                  hashes.map { |hash| "(#{keys.map { |key| arel_table[key].eq(hash[key]) }.inject(:and).to_sql})" }.join(" OR ")
         | 
| 480 501 | 
             
                end
         | 
| 481 502 |  | 
| 482 | 
            -
                #  | 
| 483 | 
            -
                #
         | 
| 484 | 
            -
                # @return [InventoryRefresh::ApplicationRecordIterator] Iterator for the references and query
         | 
| 503 | 
            +
                # @return [ActiveRecord::Relation] A relation that can fetch all data of this InventoryCollection from the DB
         | 
| 485 504 | 
             
                def db_collection_for_comparison
         | 
| 486 | 
            -
                   | 
| 505 | 
            +
                  if targeted?
         | 
| 506 | 
            +
                    if targeted_arel.respond_to?(:call)
         | 
| 507 | 
            +
                      targeted_arel.call(self)
         | 
| 508 | 
            +
                    elsif parent_inventory_collections.present?
         | 
| 509 | 
            +
                      targeted_arel_default
         | 
| 510 | 
            +
                    else
         | 
| 511 | 
            +
                      targeted_iterator_for(targeted_scope.primary_references)
         | 
| 512 | 
            +
                    end
         | 
| 513 | 
            +
                  else
         | 
| 514 | 
            +
                    full_collection_for_comparison
         | 
| 515 | 
            +
                  end
         | 
| 516 | 
            +
                end
         | 
| 517 | 
            +
             | 
| 518 | 
            +
                # Builds targeted query limiting the results by the :references defined in parent_inventory_collections
         | 
| 519 | 
            +
                #
         | 
| 520 | 
            +
                # @return [InventoryRefresh::ApplicationRecordIterator] an iterator for default targeted arel
         | 
| 521 | 
            +
                def targeted_arel_default
         | 
| 522 | 
            +
                  if parent_inventory_collections.collect { |x| x.model_class.base_class }.uniq.count > 1
         | 
| 523 | 
            +
                    raise "Multiple :parent_inventory_collections with different base class are not supported by default. Write "\
         | 
| 524 | 
            +
                          ":targeted_arel manually, or separate [#{self}] into 2 InventoryCollection objects."
         | 
| 525 | 
            +
                  end
         | 
| 526 | 
            +
                  parent_collection = parent_inventory_collections.first
         | 
| 527 | 
            +
                  references        = parent_inventory_collections.map { |x| x.targeted_scope.primary_references }.reduce({}, :merge!)
         | 
| 528 | 
            +
             | 
| 529 | 
            +
                  parent_collection.targeted_iterator_for(references, full_collection_for_comparison)
         | 
| 530 | 
            +
                end
         | 
| 531 | 
            +
             | 
| 532 | 
            +
                # Gets targeted references and transforms them into list of hashes
         | 
| 533 | 
            +
                #
         | 
| 534 | 
            +
                # @param references [Array, InventoryRefresh::InventoryCollection::TargetedScope] passed references
         | 
| 535 | 
            +
                # @return [Array<Hash>] References transformed into the array of hashes
         | 
| 536 | 
            +
                def transform_references_to_hashes(references)
         | 
| 537 | 
            +
                  if references.kind_of?(Array)
         | 
| 538 | 
            +
                    # Sliced InventoryRefresh::InventoryCollection::TargetedScope
         | 
| 539 | 
            +
                    references.map { |x| x.second.full_reference }
         | 
| 540 | 
            +
                  else
         | 
| 541 | 
            +
                    references.values.map(&:full_reference)
         | 
| 542 | 
            +
                  end
         | 
| 487 543 | 
             
                end
         | 
| 488 544 |  | 
| 489 545 | 
             
                # Builds a multiselection conditions like (table1.a = a1 AND table2.b = b1) OR (table1.a = a2 AND table2.b = b2)
         | 
| @@ -492,20 +548,20 @@ module InventoryRefresh | |
| 492 548 | 
             
                # @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] passed references
         | 
| 493 549 | 
             
                # @return [String] A condition usable in .where of an ActiveRecord relation
         | 
| 494 550 | 
             
                def targeted_selection_for(references)
         | 
| 495 | 
            -
                  build_multi_selection_condition(references | 
| 496 | 
            -
                end
         | 
| 497 | 
            -
             | 
| 498 | 
            -
                def select_keys
         | 
| 499 | 
            -
                  @select_keys ||= [@model_class.primary_key] + manager_ref_to_cols.map(&:to_s) + internal_columns.map(&:to_s)
         | 
| 500 | 
            -
                end
         | 
| 501 | 
            -
             | 
| 502 | 
            -
                # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter] ActiveRecord connection
         | 
| 503 | 
            -
                def get_connection
         | 
| 504 | 
            -
                  ActiveRecord::Base.connection
         | 
| 551 | 
            +
                  build_multi_selection_condition(transform_references_to_hashes(references))
         | 
| 505 552 | 
             
                end
         | 
| 506 553 |  | 
| 507 | 
            -
                 | 
| 508 | 
            -
             | 
| 554 | 
            +
                # Returns iterator for the passed references and a query
         | 
| 555 | 
            +
                #
         | 
| 556 | 
            +
                # @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] Passed references
         | 
| 557 | 
            +
                # @param query [ActiveRecord::Relation] relation that can fetch all data of this InventoryCollection from the DB
         | 
| 558 | 
            +
                # @return [InventoryRefresh::ApplicationRecordIterator] Iterator for the references and query
         | 
| 559 | 
            +
                def targeted_iterator_for(references, query = nil)
         | 
| 560 | 
            +
                  InventoryRefresh::ApplicationRecordIterator.new(
         | 
| 561 | 
            +
                    :inventory_collection => self,
         | 
| 562 | 
            +
                    :manager_uuids_set    => references,
         | 
| 563 | 
            +
                    :query                => query
         | 
| 564 | 
            +
                  )
         | 
| 509 565 | 
             
                end
         | 
| 510 566 |  | 
| 511 567 | 
             
                # Builds an ActiveRecord::Relation that can fetch all the references from the DB
         | 
| @@ -513,18 +569,15 @@ module InventoryRefresh | |
| 513 569 | 
             
                # @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] passed references
         | 
| 514 570 | 
             
                # @return [ActiveRecord::Relation] relation that can fetch all the references from the DB
         | 
| 515 571 | 
             
                def db_collection_for_comparison_for(references)
         | 
| 516 | 
            -
                   | 
| 517 | 
            -
                  if pure_sql_record_fetching?
         | 
| 518 | 
            -
                    return get_connection.query(query.select(*select_keys).to_sql)
         | 
| 519 | 
            -
                  end
         | 
| 520 | 
            -
             | 
| 521 | 
            -
                  query
         | 
| 572 | 
            +
                  full_collection_for_comparison.where(targeted_selection_for(references))
         | 
| 522 573 | 
             
                end
         | 
| 523 574 |  | 
| 524 575 | 
             
                # @return [ActiveRecord::Relation] relation that can fetch all the references from the DB
         | 
| 525 576 | 
             
                def full_collection_for_comparison
         | 
| 526 577 | 
             
                  return arel unless arel.nil?
         | 
| 578 | 
            +
             | 
| 527 579 | 
             
                  rel = parent.send(association)
         | 
| 580 | 
            +
                  rel = rel.active if rel && supports_column?(:archived_at) && retention_strategy == :archive
         | 
| 528 581 | 
             
                  rel
         | 
| 529 582 | 
             
                end
         | 
| 530 583 |  | 
| @@ -541,8 +594,6 @@ module InventoryRefresh | |
| 541 594 | 
             
                  inventory_object_class.new(self, hash)
         | 
| 542 595 | 
             
                end
         | 
| 543 596 |  | 
| 544 | 
            -
                attr_writer :attributes_blacklist, :attributes_whitelist
         | 
| 545 | 
            -
             | 
| 546 597 | 
             
                private
         | 
| 547 598 |  | 
| 548 599 | 
             
                # Creates dynamically a subclass of InventoryRefresh::InventoryObject, that will be used per InventoryCollection
         | 
| @@ -574,9 +625,16 @@ module InventoryRefresh | |
| 574 625 | 
             
                def record_identity(record)
         | 
| 575 626 | 
             
                  identity = record.try(:[], :id) || record.try(:[], "id") || record.try(:id)
         | 
| 576 627 | 
             
                  raise "Cannot obtain identity of the #{record}" if identity.blank?
         | 
| 628 | 
            +
             | 
| 577 629 | 
             
                  {
         | 
| 578 630 | 
             
                    :id => identity
         | 
| 579 631 | 
             
                  }
         | 
| 580 632 | 
             
                end
         | 
| 633 | 
            +
             | 
| 634 | 
            +
                # TODO: Not used!
         | 
| 635 | 
            +
                # @return [Array<Symbol>] all association attributes and foreign keys of the model class
         | 
| 636 | 
            +
                def association_attributes
         | 
| 637 | 
            +
                  model_class.reflect_on_all_associations.map { |x| [x.name, x.foreign_key] }.flatten.compact.map(&:to_sym)
         | 
| 638 | 
            +
                end
         | 
| 581 639 | 
             
              end
         | 
| 582 640 | 
             
            end
         |