inventory_refresh 0.3.3 → 1.0.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +25 -30
  3. data/.github/workflows/ci.yaml +47 -0
  4. data/.rubocop.yml +3 -3
  5. data/.rubocop_cc.yml +3 -4
  6. data/.rubocop_local.yml +5 -2
  7. data/.whitesource +3 -0
  8. data/CHANGELOG.md +19 -0
  9. data/Gemfile +10 -4
  10. data/README.md +1 -2
  11. data/Rakefile +2 -2
  12. data/inventory_refresh.gemspec +9 -10
  13. data/lib/inventory_refresh/application_record_iterator.rb +25 -12
  14. data/lib/inventory_refresh/graph/topological_sort.rb +24 -26
  15. data/lib/inventory_refresh/graph.rb +2 -2
  16. data/lib/inventory_refresh/inventory_collection/builder.rb +37 -15
  17. data/lib/inventory_refresh/inventory_collection/data_storage.rb +9 -0
  18. data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +147 -38
  19. data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +49 -5
  20. data/lib/inventory_refresh/inventory_collection/index/proxy.rb +35 -3
  21. data/lib/inventory_refresh/inventory_collection/index/type/base.rb +8 -0
  22. data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +2 -0
  23. data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +1 -0
  24. data/lib/inventory_refresh/inventory_collection/reference.rb +1 -0
  25. data/lib/inventory_refresh/inventory_collection/references_storage.rb +17 -0
  26. data/lib/inventory_refresh/inventory_collection/scanner.rb +91 -3
  27. data/lib/inventory_refresh/inventory_collection/serialization.rb +16 -10
  28. data/lib/inventory_refresh/inventory_collection.rb +122 -64
  29. data/lib/inventory_refresh/inventory_object.rb +74 -40
  30. data/lib/inventory_refresh/inventory_object_lazy.rb +17 -10
  31. data/lib/inventory_refresh/null_logger.rb +2 -2
  32. data/lib/inventory_refresh/persister.rb +31 -65
  33. data/lib/inventory_refresh/save_collection/base.rb +4 -2
  34. data/lib/inventory_refresh/save_collection/saver/base.rb +114 -15
  35. data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
  36. data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +129 -51
  37. data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
  38. data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +2 -19
  39. data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +68 -3
  40. data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +125 -0
  41. data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +10 -6
  42. data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +28 -16
  43. data/lib/inventory_refresh/save_collection/sweeper.rb +17 -93
  44. data/lib/inventory_refresh/save_collection/topological_sort.rb +5 -5
  45. data/lib/inventory_refresh/save_inventory.rb +5 -12
  46. data/lib/inventory_refresh/target.rb +73 -0
  47. data/lib/inventory_refresh/target_collection.rb +92 -0
  48. data/lib/inventory_refresh/version.rb +1 -1
  49. data/lib/inventory_refresh.rb +2 -0
  50. metadata +42 -39
  51. data/.travis.yml +0 -23
  52. 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 :association,
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 if value.transitive_dependency?
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 :build,
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
- def sweep_scope_from_hash(sweep_scope, available_inventory_collections)
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
- attr_reader :model_class, :strategy, :attributes_blacklist, :attributes_whitelist, :custom_save_block, :parent,
78
- :internal_attributes, :dependency_attributes, :manager_ref, :secondary_refs, :create_only,
79
- :association, :complete, :update_only, :transitive_dependency_attributes, :check_changed, :arel,
80
- :inventory_object_attributes, :name, :saver_strategy, :default_values,
81
- :manager_ref_allowed_nil, :use_ar_object,
82
- :created_records, :updated_records, :deleted_records, :retention_strategy,
83
- :custom_reconnect_block, :batch_extra_attributes, :references_storage, :unconnected_edges,
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[:retention_strategy])
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
- init_ic_relations(properties[:dependency_attributes])
164
+ init_all_manager_uuids(properties[:all_manager_uuids],
165
+ properties[:all_manager_uuids_scope],
166
+ properties[:all_manager_uuids_timestamp])
147
167
 
148
- init_arels(properties[:arel])
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
- @unique_indexes_cache if @unique_indexes_cache
230
+ return @unique_indexes if @unique_indexes
207
231
 
208
- @unique_indexes_cache = model_class.connection.indexes(model_class.table_name).select(&:unique)
232
+ @unique_indexes = model_class.connection.indexes(model_class.table_name).select(&:unique)
209
233
 
210
- if @unique_indexes_cache.blank?
234
+ if @unique_indexes.blank?
211
235
  raise "#{self} and its table #{model_class.table_name} must have a unique index defined, to"\
212
- " be able to use saver_strategy :concurrent_safe_batch."
236
+ " be able to use saver_strategy :concurrent_safe_batch."
213
237
  end
214
238
 
215
- @unique_indexes_cache
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
- "covering columns #{keys} to be able to use saver_strategy :concurrent_safe_batch."
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(created_at created_on updated_at updated_on).collect do |timestamp_col|
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>] Converted manager_ref list of attributes to list of DB columns
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 += not_null_attributes.uniq
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 = unique_index_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
- # Returns iterator for the passed references and a query
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
- InventoryRefresh::ApplicationRecordIterator.new(:inventory_collection => self)
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.map(&:second))
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
- def pure_sql_record_fetching?
508
- !use_ar_object?
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
- query = full_collection_for_comparison.where(targeted_selection_for(references))
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