inventory_refresh 0.2.3 → 0.3.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 (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