inventory_refresh 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +0 -1
  3. data/.travis.yml +6 -8
  4. data/inventory_refresh.gemspec +3 -5
  5. data/lib/inventory_refresh/application_record_iterator.rb +9 -26
  6. data/lib/inventory_refresh/exception.rb +8 -0
  7. data/lib/inventory_refresh/inventory_collection/builder.rb +6 -6
  8. data/lib/inventory_refresh/inventory_collection/data_storage.rb +0 -9
  9. data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +35 -144
  10. data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +1 -44
  11. data/lib/inventory_refresh/inventory_collection/index/proxy.rb +6 -34
  12. data/lib/inventory_refresh/inventory_collection/index/type/base.rb +0 -8
  13. data/lib/inventory_refresh/inventory_collection/references_storage.rb +0 -17
  14. data/lib/inventory_refresh/inventory_collection/scanner.rb +1 -87
  15. data/lib/inventory_refresh/inventory_collection/serialization.rb +10 -16
  16. data/lib/inventory_refresh/inventory_collection.rb +36 -110
  17. data/lib/inventory_refresh/inventory_object.rb +34 -68
  18. data/lib/inventory_refresh/inventory_object_lazy.rb +10 -17
  19. data/lib/inventory_refresh/persister.rb +63 -29
  20. data/lib/inventory_refresh/save_collection/base.rb +2 -4
  21. data/lib/inventory_refresh/save_collection/saver/base.rb +8 -108
  22. data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +48 -126
  23. data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +19 -1
  24. data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +3 -68
  25. data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +0 -125
  26. data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +5 -9
  27. data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +9 -17
  28. data/lib/inventory_refresh/save_collection/sweeper.rb +91 -18
  29. data/lib/inventory_refresh/save_collection/topological_sort.rb +5 -5
  30. data/lib/inventory_refresh/save_inventory.rb +12 -5
  31. data/lib/inventory_refresh/version.rb +1 -1
  32. data/lib/inventory_refresh.rb +0 -2
  33. metadata +15 -57
  34. data/lib/inventory_refresh/save_collection/saver/batch.rb +0 -17
  35. data/lib/inventory_refresh/save_collection/saver/default.rb +0 -57
  36. data/lib/inventory_refresh/target.rb +0 -73
  37. data/lib/inventory_refresh/target_collection.rb +0 -92
@@ -41,7 +41,6 @@ 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?,
45
44
  :to => :inventory_collection
46
45
 
47
46
  # Methods the scanner uses from the :inventory_collection
@@ -52,18 +51,13 @@ module InventoryRefresh
52
51
  :to => :inventory_collection
53
52
 
54
53
  # The data scanner modifies inside of the :inventory_collection
55
- delegate :all_manager_uuids_scope,
56
- :association,
54
+ delegate :association,
57
55
  :arel,
58
56
  :attribute_references,
59
57
  :custom_save_block,
60
58
  :data_collection_finalized=,
61
59
  :dependency_attributes,
62
- :targeted?,
63
- :targeted_scope,
64
60
  :parent,
65
- :parent_inventory_collections,
66
- :parent_inventory_collections=,
67
61
  :references,
68
62
  :transitive_dependency_attributes,
69
63
  :to => :inventory_collection
@@ -78,11 +72,6 @@ module InventoryRefresh
78
72
  # Scan InventoryCollection InventoryObjects and store the results inside of the InventoryCollection
79
73
  data.each do |inventory_object|
80
74
  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
86
75
  end
87
76
 
88
77
  # Scan InventoryCollection skeletal data
@@ -90,80 +79,12 @@ module InventoryRefresh
90
79
  scan_inventory_object!(inventory_object)
91
80
  end
92
81
 
93
- build_parent_inventory_collections!
94
- scan_all_manager_uuids_scope!
95
-
96
82
  # Mark InventoryCollection as finalized aka. scanned
97
83
  self.data_collection_finalized = true
98
84
  end
99
85
 
100
86
  private
101
87
 
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?
162
- raise "Can't find InventoryCollection :#{name} referenced from #{inventory_collection}" if targeted?
163
- end
164
- ic
165
- end
166
-
167
88
  def scan_inventory_object!(inventory_object)
168
89
  inventory_object.data.each do |key, value|
169
90
  if value.kind_of?(Array)
@@ -182,13 +103,6 @@ module InventoryRefresh
182
103
  value_inventory_collection.add_reference(value.reference, :key => value.key)
183
104
  end
184
105
 
185
- def scan_all_manager_uuids_scope_attribute!(value)
186
- return unless loadable?(value)
187
-
188
- add_reference(value.inventory_collection, value)
189
- (dependency_attributes[:__all_manager_uuids_scope] ||= Set.new) << value.inventory_collection
190
- end
191
-
192
106
  def scan_inventory_object_attribute!(key, value)
193
107
  return unless loadable?(value)
194
108
  value_inventory_collection = value.inventory_collection
@@ -3,10 +3,7 @@ require "active_support/core_ext/module/delegation"
3
3
  module InventoryRefresh
4
4
  class InventoryCollection
5
5
  class Serialization
6
- delegate :all_manager_uuids,
7
- :all_manager_uuids=,
8
- :build,
9
- :targeted_scope,
6
+ delegate :build,
10
7
  :data,
11
8
  :inventory_object_lazy?,
12
9
  :inventory_object?,
@@ -28,8 +25,6 @@ module InventoryRefresh
28
25
  # @param available_inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of available
29
26
  # InventoryCollection objects
30
27
  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
-
33
28
  (inventory_objects_data['data'] || []).each do |inventory_object_data|
34
29
  build(hash_to_data(inventory_object_data, available_inventory_collections).symbolize_keys!)
35
30
  end
@@ -37,8 +32,12 @@ module InventoryRefresh
37
32
  (inventory_objects_data['partial_data'] || []).each do |inventory_object_data|
38
33
  skeletal_primary_index.build(hash_to_data(inventory_object_data, available_inventory_collections).symbolize_keys!)
39
34
  end
35
+ end
40
36
 
41
- self.all_manager_uuids = inventory_objects_data['all_manager_uuids']
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
42
41
  end
43
42
 
44
43
  # Serializes InventoryCollection's data storage into Array of Hashes
@@ -47,14 +46,15 @@ module InventoryRefresh
47
46
  def to_hash
48
47
  {
49
48
  :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,
53
49
  :data => data.map { |x| data_to_hash(x.data) },
54
50
  :partial_data => skeletal_primary_index.index_data.map { |x| data_to_hash(x.data) },
55
51
  }
56
52
  end
57
53
 
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,10 +107,6 @@ 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) }
114
110
  else
115
111
  value
116
112
  end
@@ -128,8 +124,6 @@ module InventoryRefresh
128
124
  data.transform_values do |value|
129
125
  if inventory_object_lazy?(value) || inventory_object?(value)
130
126
  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) }
133
127
  else
134
128
  value
135
129
  end
@@ -71,32 +71,14 @@ 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
-
88
74
  # @return [Set] A set of InventoryCollection objects that depends on this InventoryCollection object.
89
75
  attr_accessor :dependees
90
76
 
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
77
  attr_reader :model_class, :strategy, :attributes_blacklist, :attributes_whitelist, :custom_save_block, :parent,
96
- :internal_attributes, :delete_method, :dependency_attributes, :manager_ref, :create_only,
78
+ :internal_attributes, :dependency_attributes, :manager_ref, :secondary_refs, :create_only,
97
79
  :association, :complete, :update_only, :transitive_dependency_attributes, :check_changed, :arel,
98
- :inventory_object_attributes, :name, :saver_strategy, :targeted_scope, :default_values,
99
- :targeted_arel, :targeted, :manager_ref_allowed_nil, :use_ar_object,
80
+ :inventory_object_attributes, :name, :saver_strategy, :default_values,
81
+ :manager_ref_allowed_nil, :use_ar_object,
100
82
  :created_records, :updated_records, :deleted_records, :retention_strategy,
101
83
  :custom_reconnect_block, :batch_extra_attributes, :references_storage, :unconnected_edges,
102
84
  :assert_graph_integrity
@@ -130,7 +112,6 @@ module InventoryRefresh
130
112
  :lazy_find_by,
131
113
  :named_ref,
132
114
  :primary_index,
133
- :reindex_secondary_indexes!,
134
115
  :skeletal_primary_index,
135
116
  :to => :index_proxy
136
117
 
@@ -153,28 +134,18 @@ module InventoryRefresh
153
134
  properties[:check_changed],
154
135
  properties[:update_only],
155
136
  properties[:use_ar_object],
156
- properties[:targeted],
157
137
  properties[:assert_graph_integrity])
158
138
 
159
139
  init_strategies(properties[:strategy],
160
- properties[:saver_strategy],
161
- properties[:retention_strategy],
162
- properties[:delete_method])
140
+ properties[:retention_strategy])
163
141
 
164
142
  init_references(properties[:manager_ref],
165
143
  properties[:manager_ref_allowed_nil],
166
- properties[:secondary_refs],
167
- properties[:manager_uuids])
144
+ properties[:secondary_refs])
168
145
 
169
- init_all_manager_uuids(properties[:all_manager_uuids],
170
- properties[:all_manager_uuids_scope],
171
- properties[:all_manager_uuids_timestamp])
146
+ init_ic_relations(properties[:dependency_attributes])
172
147
 
173
- init_ic_relations(properties[:dependency_attributes],
174
- properties[:parent_inventory_collections])
175
-
176
- init_arels(properties[:arel],
177
- properties[:targeted_arel])
148
+ init_arels(properties[:arel])
178
149
 
179
150
  init_custom_procs(properties[:custom_save_block],
180
151
  properties[:custom_reconnect_block])
@@ -311,6 +282,11 @@ module InventoryRefresh
311
282
  @base_columns ||= (unique_index_columns + internal_columns + not_null_columns).uniq
312
283
  end
313
284
 
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
+
314
290
  # @param value [Object] Object we want to test
315
291
  # @return [Boolean] true is value is kind of InventoryRefresh::InventoryObject
316
292
  def inventory_object?(value)
@@ -335,7 +311,7 @@ module InventoryRefresh
335
311
 
336
312
  # Convert manager_ref list of attributes to list of DB columns
337
313
  #
338
- # @return [Array<String>] true is processing of this InventoryCollection will be in targeted mode
314
+ # @return [Array<String>] Converted manager_ref list of attributes to list of DB columns
339
315
  def manager_ref_to_cols
340
316
  # TODO(lsmola) this should contain the polymorphic _type, otherwise the IC with polymorphic unique key will get
341
317
  # conflicts
@@ -434,7 +410,6 @@ module InventoryRefresh
434
410
  :parent => parent,
435
411
  :arel => arel,
436
412
  :strategy => strategy,
437
- :saver_strategy => saver_strategy,
438
413
  :custom_save_block => custom_save_block,
439
414
  # We want cloned IC to be update only, since this is used for cycle resolution
440
415
  :update_only => true,
@@ -472,78 +447,31 @@ module InventoryRefresh
472
447
 
473
448
  # @return [Integer] default batch size for talking to the DB
474
449
  def batch_size
475
- # TODO(lsmola) mode to the settings
476
450
  1000
477
451
  end
478
452
 
479
453
  # @return [Integer] default batch size for talking to the DB if not using ApplicationRecord objects
480
454
  def batch_size_pure_sql
481
- # TODO(lsmola) mode to the settings
482
455
  10_000
483
456
  end
484
457
 
485
- # Returns a list of stringified uuids of all scoped InventoryObjects, which is used for scoping in targeted mode
486
- #
487
- # @return [Array<String>] list of stringified uuids of all scoped InventoryObjects
488
- def manager_uuids
489
- # TODO(lsmola) LEGACY: this is still being used by :targetel_arel definitions and it expects array of strings
490
- raise "This works only for :manager_ref size 1" if manager_ref.size > 1
491
- key = manager_ref.first
492
- transform_references_to_hashes(targeted_scope.primary_references).map { |x| x[key] }
493
- end
494
-
495
458
  # Builds a multiselection conditions like (table1.a = a1 AND table2.b = b1) OR (table1.a = a2 AND table2.b = b2)
496
459
  #
497
460
  # @param hashes [Array<Hash>] data we want to use for the query
498
461
  # @param keys [Array<Symbol>] keys of attributes involved
499
462
  # @return [String] A condition usable in .where of an ActiveRecord relation
500
- def build_multi_selection_condition(hashes, keys = manager_ref)
463
+ def build_multi_selection_condition(hashes, keys = unique_index_keys)
501
464
  arel_table = model_class.arel_table
502
465
  # We do pure SQL OR, since Arel is nesting every .or into another parentheses, otherwise this would be just
503
466
  # inject(:or) instead of to_sql with .join(" OR ")
504
467
  hashes.map { |hash| "(#{keys.map { |key| arel_table[key].eq(hash[key]) }.inject(:and).to_sql})" }.join(" OR ")
505
468
  end
506
469
 
507
- # @return [ActiveRecord::Relation] A relation that can fetch all data of this InventoryCollection from the DB
508
- def db_collection_for_comparison
509
- if targeted?
510
- if targeted_arel.respond_to?(:call)
511
- targeted_arel.call(self)
512
- elsif parent_inventory_collections.present?
513
- targeted_arel_default
514
- else
515
- targeted_iterator_for(targeted_scope.primary_references)
516
- end
517
- else
518
- full_collection_for_comparison
519
- end
520
- end
521
-
522
- # Builds targeted query limiting the results by the :references defined in parent_inventory_collections
523
- #
524
- # @return [InventoryRefresh::ApplicationRecordIterator] an iterator for default targeted arel
525
- def targeted_arel_default
526
- if parent_inventory_collections.collect { |x| x.model_class.base_class }.uniq.count > 1
527
- raise "Multiple :parent_inventory_collections with different base class are not supported by default. Write "\
528
- ":targeted_arel manually, or separate [#{self}] into 2 InventoryCollection objects."
529
- end
530
- parent_collection = parent_inventory_collections.first
531
- references = parent_inventory_collections.map { |x| x.targeted_scope.primary_references }.reduce({}, :merge!)
532
-
533
- parent_collection.targeted_iterator_for(references, full_collection_for_comparison)
534
- end
535
-
536
- # Gets targeted references and transforms them into list of hashes
470
+ # Returns iterator for the passed references and a query
537
471
  #
538
- # @param references [Array, InventoryRefresh::InventoryCollection::TargetedScope] passed references
539
- # @return [Array<Hash>] References transformed into the array of hashes
540
- def transform_references_to_hashes(references)
541
- if references.kind_of?(Array)
542
- # Sliced InventoryRefresh::InventoryCollection::TargetedScope
543
- references.map { |x| x.second.full_reference }
544
- else
545
- references.values.map(&:full_reference)
546
- end
472
+ # @return [InventoryRefresh::ApplicationRecordIterator] Iterator for the references and query
473
+ def db_collection_for_comparison
474
+ InventoryRefresh::ApplicationRecordIterator.new(:inventory_collection => self)
547
475
  end
548
476
 
549
477
  # Builds a multiselection conditions like (table1.a = a1 AND table2.b = b1) OR (table1.a = a2 AND table2.b = b2)
@@ -552,20 +480,20 @@ module InventoryRefresh
552
480
  # @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] passed references
553
481
  # @return [String] A condition usable in .where of an ActiveRecord relation
554
482
  def targeted_selection_for(references)
555
- build_multi_selection_condition(transform_references_to_hashes(references))
483
+ build_multi_selection_condition(references.map(&:second))
556
484
  end
557
485
 
558
- # Returns iterator for the passed references and a query
559
- #
560
- # @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] Passed references
561
- # @param query [ActiveRecord::Relation] relation that can fetch all data of this InventoryCollection from the DB
562
- # @return [InventoryRefresh::ApplicationRecordIterator] Iterator for the references and query
563
- def targeted_iterator_for(references, query = nil)
564
- InventoryRefresh::ApplicationRecordIterator.new(
565
- :inventory_collection => self,
566
- :manager_uuids_set => references,
567
- :query => query
568
- )
486
+ def select_keys
487
+ @select_keys ||= [@model_class.primary_key] + manager_ref_to_cols.map(&:to_s) + internal_columns.map(&:to_s)
488
+ end
489
+
490
+ # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter] ActiveRecord connection
491
+ def get_connection
492
+ ActiveRecord::Base.connection
493
+ end
494
+
495
+ def pure_sql_record_fetching?
496
+ !use_ar_object?
569
497
  end
570
498
 
571
499
  # Builds an ActiveRecord::Relation that can fetch all the references from the DB
@@ -573,14 +501,18 @@ module InventoryRefresh
573
501
  # @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] passed references
574
502
  # @return [ActiveRecord::Relation] relation that can fetch all the references from the DB
575
503
  def db_collection_for_comparison_for(references)
576
- full_collection_for_comparison.where(targeted_selection_for(references))
504
+ query = full_collection_for_comparison.where(targeted_selection_for(references))
505
+ if pure_sql_record_fetching?
506
+ return get_connection.query(query.select(*select_keys).to_sql)
507
+ end
508
+
509
+ query
577
510
  end
578
511
 
579
512
  # @return [ActiveRecord::Relation] relation that can fetch all the references from the DB
580
513
  def full_collection_for_comparison
581
514
  return arel unless arel.nil?
582
515
  rel = parent.send(association)
583
- rel = rel.active if rel && supports_column?(:archived_at) && retention_strategy == :archive
584
516
  rel
585
517
  end
586
518
 
@@ -634,11 +566,5 @@ module InventoryRefresh
634
566
  :id => identity
635
567
  }
636
568
  end
637
-
638
- # TODO: Not used!
639
- # @return [Array<Symbol>] all association attributes and foreign keys of the model class
640
- def association_attributes
641
- model_class.reflect_on_all_associations.map { |x| [x.name, x.foreign_key] }.flatten.compact.map(&:to_sym)
642
- end
643
569
  end
644
570
  end
@@ -42,64 +42,12 @@ module InventoryRefresh
42
42
  # Transforms InventoryObject object data into hash format with keys that are column names and resolves correct
43
43
  # values of the foreign keys (even the polymorphic ones)
44
44
  #
45
- # @param inventory_collection_scope [InventoryRefresh::InventoryCollection] parent InventoryCollection object
46
- # @return [Hash] Data in DB format
47
- def attributes(inventory_collection_scope = nil)
48
- # We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with
49
- # different blacklist and whitelist. The generic code always passes a scope.
50
- inventory_collection_scope ||= inventory_collection
51
-
52
- attributes_for_saving = {}
53
- # First transform the values
54
- data.each do |key, value|
55
- if !allowed?(inventory_collection_scope, key)
56
- next
57
- elsif value.kind_of?(Array) && value.any? { |x| loadable?(x) }
58
- # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
59
- # result
60
- data[key] = value.compact.map(&:load).compact
61
- # We can use built in _ids methods to assign array of ids into has_many relations. So e.g. the :key_pairs=
62
- # relation setter will become :key_pair_ids=
63
- attributes_for_saving[(key.to_s.singularize + "_ids").to_sym] = data[key].map(&:id).compact.uniq
64
- elsif loadable?(value) || inventory_collection_scope.association_to_foreign_key_mapping[key]
65
- # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
66
- # result
67
- data[key] = value.load if value.respond_to?(:load)
68
- if (foreign_key = inventory_collection_scope.association_to_foreign_key_mapping[key])
69
- # We have an association to fill, lets fill also the :key, cause some other InventoryObject can refer to it
70
- record_id = data[key].try(:id)
71
- attributes_for_saving[foreign_key.to_sym] = record_id
72
-
73
- if (foreign_type = inventory_collection_scope.association_to_foreign_type_mapping[key])
74
- # If we have a polymorphic association, we need to also fill a base class name, but we want to nullify it
75
- # if record_id is missing
76
- base_class = data[key].try(:base_class_name) || data[key].class.try(:base_class).try(:name)
77
- attributes_for_saving[foreign_type.to_sym] = record_id ? base_class : nil
78
- end
79
- elsif data[key].kind_of?(::InventoryRefresh::InventoryObject)
80
- # We have an association to fill but not an Activerecord association, so e.g. Ancestry, lets just load
81
- # it here. This way of storing ancestry is ineffective in DB call count, but RAM friendly
82
- attributes_for_saving[key.to_sym] = data[key].base_class_name.constantize.find_by(:id => data[key].id)
83
- else
84
- # We have a normal attribute to fill
85
- attributes_for_saving[key.to_sym] = data[key]
86
- end
87
- else
88
- attributes_for_saving[key.to_sym] = value
89
- end
90
- end
91
-
92
- attributes_for_saving
93
- end
94
-
95
- # Transforms InventoryObject object data into hash format with keys that are column names and resolves correct
96
- # values of the foreign keys (even the polymorphic ones)
97
- #
45
+ # @param data [Array<Object>] Array of objects that we want to map to DB table columns
98
46
  # @param inventory_collection_scope [InventoryRefresh::InventoryCollection] parent InventoryCollection object
99
47
  # @param all_attribute_keys [Array<Symbol>] Attribute keys we will modify based on object's data
100
48
  # @param inventory_object [InventoryRefresh::InventoryObject] InventoryObject object owning these attributes
101
49
  # @return [Hash] Data in DB format
102
- def attributes_with_keys(inventory_collection_scope = nil, all_attribute_keys = [], inventory_object = nil)
50
+ def self.attributes_with_keys(data, inventory_collection_scope = nil, all_attribute_keys = [], inventory_object = nil)
103
51
  # We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with
104
52
  # different blacklist and whitelist. The generic code always passes a scope.
105
53
  inventory_collection_scope ||= inventory_collection
@@ -206,6 +154,36 @@ module InventoryRefresh
206
154
  end
207
155
  end
208
156
 
157
+ # Return true if the attribute is allowed to be saved into the DB
158
+ #
159
+ # @param inventory_collection_scope [InventoryRefresh::InventoryCollection] InventoryCollection object owning the
160
+ # attribute
161
+ # @param key [Symbol] attribute name
162
+ # @return true if the attribute is allowed to be saved into the DB
163
+ def self.allowed?(inventory_collection_scope, key)
164
+ foreign_to_association = (inventory_collection_scope.foreign_key_to_association_mapping[key] ||
165
+ inventory_collection_scope.foreign_type_to_association_mapping[key])
166
+
167
+ return false if inventory_collection_scope.attributes_blacklist.present? &&
168
+ (inventory_collection_scope.attributes_blacklist.include?(key) ||
169
+ (foreign_to_association && inventory_collection_scope.attributes_blacklist.include?(foreign_to_association)))
170
+
171
+ return false if inventory_collection_scope.attributes_whitelist.present? &&
172
+ (!inventory_collection_scope.attributes_whitelist.include?(key) &&
173
+ (!foreign_to_association || (foreign_to_association && inventory_collection_scope.attributes_whitelist.include?(foreign_to_association))))
174
+
175
+ true
176
+ end
177
+
178
+ # Return true if the object is loadable, which we determine by a list of loadable classes.
179
+ #
180
+ # @param value [Object] object we test
181
+ # @return true if the object is loadable
182
+ def self.loadable?(value)
183
+ value.kind_of?(::InventoryRefresh::InventoryObjectLazy) || value.kind_of?(::InventoryRefresh::InventoryObject) ||
184
+ value.kind_of?(::InventoryRefresh::ApplicationRecordReference)
185
+ end
186
+
209
187
  private
210
188
 
211
189
  # Assigns value based on the version attributes. If versions are specified, it asigns attribute only if it's
@@ -283,18 +261,7 @@ module InventoryRefresh
283
261
  # @param key [Symbol] attribute name
284
262
  # @return true if the attribute is allowed to be saved into the DB
285
263
  def allowed?(inventory_collection_scope, key)
286
- foreign_to_association = inventory_collection_scope.foreign_key_to_association_mapping[key] ||
287
- inventory_collection_scope.foreign_type_to_association_mapping[key]
288
-
289
- return false if inventory_collection_scope.attributes_blacklist.present? &&
290
- (inventory_collection_scope.attributes_blacklist.include?(key) ||
291
- (foreign_to_association && inventory_collection_scope.attributes_blacklist.include?(foreign_to_association)))
292
-
293
- return false if inventory_collection_scope.attributes_whitelist.present? &&
294
- (!inventory_collection_scope.attributes_whitelist.include?(key) &&
295
- (!foreign_to_association || (foreign_to_association && inventory_collection_scope.attributes_whitelist.include?(foreign_to_association))))
296
-
297
- true
264
+ self.class.allowed?(inventory_collection_scope, key)
298
265
  end
299
266
 
300
267
  # Return true if the object is loadable, which we determine by a list of loadable classes.
@@ -302,8 +269,7 @@ module InventoryRefresh
302
269
  # @param value [Object] object we test
303
270
  # @return true if the object is loadable
304
271
  def loadable?(value)
305
- value.kind_of?(::InventoryRefresh::InventoryObjectLazy) || value.kind_of?(::InventoryRefresh::InventoryObject) ||
306
- value.kind_of?(::InventoryRefresh::ApplicationRecordReference)
272
+ self.class.loadable?(value)
307
273
  end
308
274
  end
309
275
  end
@@ -30,7 +30,6 @@ module InventoryRefresh
30
30
 
31
31
  # @return [String] stringified reference
32
32
  def to_s
33
- # TODO(lsmola) do we need this method?
34
33
  stringified_reference
35
34
  end
36
35
 
@@ -71,10 +70,6 @@ module InventoryRefresh
71
70
 
72
71
  # @return [Boolean] true if the key is an association on inventory_collection_scope model class
73
72
  def association?(key)
74
- # TODO(lsmola) remove this if there will be better dependency scan, probably with transitive dependencies filled
75
- # in a second pass, then we can get rid of this hardcoded symbols. Right now we are not able to introspect these.
76
- return true if [:parent, :genealogy_parent].include?(key)
77
-
78
73
  inventory_collection.dependency_attributes.key?(key) ||
79
74
  !inventory_collection.association_to_foreign_key_mapping[key].nil?
80
75
  end
@@ -100,7 +95,7 @@ module InventoryRefresh
100
95
 
101
96
  private
102
97
 
103
- delegate :parallel_safe?, :saved?, :saver_strategy, :skeletal_primary_index, :targeted?, :to => :inventory_collection
98
+ delegate :saved?, :saver_strategy, :skeletal_primary_index, :to => :inventory_collection
104
99
  delegate :nested_secondary_index?, :primary?, :full_reference, :keys, :primary?, :to => :reference
105
100
 
106
101
  attr_writer :reference
@@ -112,20 +107,18 @@ module InventoryRefresh
112
107
  #
113
108
  # @return [InventoryRefresh::InventoryObject, NilClass] Returns pre-created InventoryObject or nil
114
109
  def skeletal_precreate!
115
- # We can do skeletal pre-create only for strategies using unique indexes. Since this can build records out of
116
- # the given :arel scope, we will always attempt to create the recod, so we need unique index to avoid duplication
117
- # of records.
118
- return unless parallel_safe?
119
110
  # Pre-create only for strategies that will be persisting data, i.e. are not saved already
120
111
  return if saved?
121
112
  # We can only do skeletal pre-create for primary index reference, since that is needed to create DB unique index
122
- return unless primary?
123
- # Full reference must be present
124
- return if full_reference.blank?
125
-
126
- # To avoid pre-creating invalid records all fields of a primary key must have value
127
- # TODO(lsmola) for composite keys, it's still valid to have one of the keys nil, figure out how to allow this
128
- return if keys.any? { |x| full_reference[x].blank? }
113
+ # and full reference must be present
114
+ return if !primary? || full_reference.blank?
115
+
116
+ # To avoid pre-creating invalid records all fields of a primary key must have non null value
117
+ # TODO(lsmola) for composite keys, it's still valid to have one of the keys nil, figure out how to allow this. We
118
+ # will need to scan the DB for NOT NULL constraint and allow it based on that. So we would move this check to
119
+ # saving code, but this will require bigger change, since having the column nil means we will have to batch it
120
+ # smartly, since having nil means we will need to use different unique index for the upsert/update query.
121
+ return if keys.any? { |x| full_reference[x].nil? }
129
122
 
130
123
  skeletal_primary_index.build(full_reference)
131
124
  end