inventory_refresh 0.3.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +8 -9
  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 +48 -4
  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 +43 -93
  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 +34 -37
  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