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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +0 -1
- data/.travis.yml +6 -8
- data/inventory_refresh.gemspec +3 -5
- data/lib/inventory_refresh/application_record_iterator.rb +9 -26
- data/lib/inventory_refresh/exception.rb +8 -0
- data/lib/inventory_refresh/inventory_collection/builder.rb +6 -6
- data/lib/inventory_refresh/inventory_collection/data_storage.rb +0 -9
- data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +35 -144
- data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +1 -44
- data/lib/inventory_refresh/inventory_collection/index/proxy.rb +6 -34
- data/lib/inventory_refresh/inventory_collection/index/type/base.rb +0 -8
- data/lib/inventory_refresh/inventory_collection/references_storage.rb +0 -17
- data/lib/inventory_refresh/inventory_collection/scanner.rb +1 -87
- data/lib/inventory_refresh/inventory_collection/serialization.rb +10 -16
- data/lib/inventory_refresh/inventory_collection.rb +36 -110
- data/lib/inventory_refresh/inventory_object.rb +34 -68
- data/lib/inventory_refresh/inventory_object_lazy.rb +10 -17
- data/lib/inventory_refresh/persister.rb +63 -29
- data/lib/inventory_refresh/save_collection/base.rb +2 -4
- data/lib/inventory_refresh/save_collection/saver/base.rb +8 -108
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +48 -126
- data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +19 -1
- data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +3 -68
- data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +0 -125
- data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +5 -9
- data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +9 -17
- data/lib/inventory_refresh/save_collection/sweeper.rb +91 -18
- data/lib/inventory_refresh/save_collection/topological_sort.rb +5 -5
- data/lib/inventory_refresh/save_inventory.rb +12 -5
- data/lib/inventory_refresh/version.rb +1 -1
- data/lib/inventory_refresh.rb +0 -2
- metadata +15 -57
- data/lib/inventory_refresh/save_collection/saver/batch.rb +0 -17
- data/lib/inventory_refresh/save_collection/saver/default.rb +0 -57
- data/lib/inventory_refresh/target.rb +0 -73
- 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 :
|
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 :
|
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
|
-
|
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, :
|
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, :
|
99
|
-
:
|
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[:
|
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
|
-
|
170
|
-
properties[:all_manager_uuids_scope],
|
171
|
-
properties[:all_manager_uuids_timestamp])
|
146
|
+
init_ic_relations(properties[:dependency_attributes])
|
172
147
|
|
173
|
-
|
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>]
|
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 =
|
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
|
-
#
|
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
|
-
# @
|
539
|
-
|
540
|
-
|
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(
|
483
|
+
build_multi_selection_condition(references.map(&:second))
|
556
484
|
end
|
557
485
|
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
# @return [
|
563
|
-
def
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
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
|
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
|
-
|
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
|
-
|
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 :
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
#
|
127
|
-
#
|
128
|
-
|
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
|