inventory_refresh 0.3.6 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +25 -30
- data/.github/workflows/ci.yaml +47 -0
- data/.rubocop.yml +3 -3
- data/.rubocop_cc.yml +3 -4
- data/.rubocop_local.yml +5 -2
- data/.whitesource +3 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +10 -4
- data/README.md +1 -2
- data/Rakefile +2 -2
- data/inventory_refresh.gemspec +8 -9
- data/lib/inventory_refresh/application_record_iterator.rb +25 -12
- data/lib/inventory_refresh/graph/topological_sort.rb +24 -26
- data/lib/inventory_refresh/graph.rb +2 -2
- data/lib/inventory_refresh/inventory_collection/builder.rb +37 -15
- data/lib/inventory_refresh/inventory_collection/data_storage.rb +9 -0
- data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +147 -38
- data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +48 -4
- data/lib/inventory_refresh/inventory_collection/index/proxy.rb +35 -3
- data/lib/inventory_refresh/inventory_collection/index/type/base.rb +8 -0
- data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +2 -0
- data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +1 -0
- data/lib/inventory_refresh/inventory_collection/reference.rb +1 -0
- data/lib/inventory_refresh/inventory_collection/references_storage.rb +17 -0
- data/lib/inventory_refresh/inventory_collection/scanner.rb +91 -3
- data/lib/inventory_refresh/inventory_collection/serialization.rb +16 -10
- data/lib/inventory_refresh/inventory_collection.rb +122 -64
- data/lib/inventory_refresh/inventory_object.rb +74 -40
- data/lib/inventory_refresh/inventory_object_lazy.rb +17 -10
- data/lib/inventory_refresh/null_logger.rb +2 -2
- data/lib/inventory_refresh/persister.rb +43 -93
- data/lib/inventory_refresh/save_collection/base.rb +4 -2
- data/lib/inventory_refresh/save_collection/saver/base.rb +114 -15
- data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +129 -51
- data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
- data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +2 -19
- data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +68 -3
- data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +125 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +10 -6
- data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +28 -16
- data/lib/inventory_refresh/save_collection/sweeper.rb +17 -93
- data/lib/inventory_refresh/save_collection/topological_sort.rb +5 -5
- data/lib/inventory_refresh/save_inventory.rb +5 -12
- data/lib/inventory_refresh/target.rb +73 -0
- data/lib/inventory_refresh/target_collection.rb +92 -0
- data/lib/inventory_refresh/version.rb +1 -1
- data/lib/inventory_refresh.rb +2 -0
- metadata +34 -37
- data/.travis.yml +0 -23
- data/lib/inventory_refresh/exception.rb +0 -8
@@ -41,6 +41,7 @@ module InventoryRefresh
|
|
41
41
|
# Boolean helpers the scanner uses from the :inventory_collection
|
42
42
|
delegate :inventory_object_lazy?,
|
43
43
|
:inventory_object?,
|
44
|
+
:targeted?,
|
44
45
|
:to => :inventory_collection
|
45
46
|
|
46
47
|
# Methods the scanner uses from the :inventory_collection
|
@@ -51,13 +52,18 @@ module InventoryRefresh
|
|
51
52
|
:to => :inventory_collection
|
52
53
|
|
53
54
|
# The data scanner modifies inside of the :inventory_collection
|
54
|
-
delegate :
|
55
|
+
delegate :all_manager_uuids_scope,
|
56
|
+
:association,
|
55
57
|
:arel,
|
56
58
|
:attribute_references,
|
57
59
|
:custom_save_block,
|
58
60
|
:data_collection_finalized=,
|
59
61
|
:dependency_attributes,
|
62
|
+
:targeted?,
|
63
|
+
:targeted_scope,
|
60
64
|
:parent,
|
65
|
+
:parent_inventory_collections,
|
66
|
+
:parent_inventory_collections=,
|
61
67
|
:references,
|
62
68
|
:transitive_dependency_attributes,
|
63
69
|
:to => :inventory_collection
|
@@ -72,6 +78,11 @@ module InventoryRefresh
|
|
72
78
|
# Scan InventoryCollection InventoryObjects and store the results inside of the InventoryCollection
|
73
79
|
data.each do |inventory_object|
|
74
80
|
scan_inventory_object!(inventory_object)
|
81
|
+
|
82
|
+
if targeted? && parent_inventory_collections.blank?
|
83
|
+
# We want to track what manager_uuids we should query from a db, for the targeted refresh
|
84
|
+
targeted_scope << inventory_object.reference
|
85
|
+
end
|
75
86
|
end
|
76
87
|
|
77
88
|
# Scan InventoryCollection skeletal data
|
@@ -79,12 +90,81 @@ module InventoryRefresh
|
|
79
90
|
scan_inventory_object!(inventory_object)
|
80
91
|
end
|
81
92
|
|
93
|
+
build_parent_inventory_collections!
|
94
|
+
scan_all_manager_uuids_scope!
|
95
|
+
|
82
96
|
# Mark InventoryCollection as finalized aka. scanned
|
83
97
|
self.data_collection_finalized = true
|
84
98
|
end
|
85
99
|
|
86
100
|
private
|
87
101
|
|
102
|
+
def scan_all_manager_uuids_scope!
|
103
|
+
return if all_manager_uuids_scope.nil?
|
104
|
+
|
105
|
+
all_manager_uuids_scope.each do |scope|
|
106
|
+
scope.each_value do |value|
|
107
|
+
scan_all_manager_uuids_scope_attribute!(value)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_parent_inventory_collections!
|
113
|
+
if parent_inventory_collections.nil?
|
114
|
+
build_parent_inventory_collection!
|
115
|
+
else
|
116
|
+
# We can't figure out what immediate parent is, we'll add parent_inventory_collections as dependencies
|
117
|
+
parent_inventory_collections.each { |ic_name| add_parent_inventory_collection_dependency!(ic_name) }
|
118
|
+
end
|
119
|
+
|
120
|
+
return if parent_inventory_collections.blank?
|
121
|
+
|
122
|
+
# Transform InventoryCollection object names to actual objects
|
123
|
+
self.parent_inventory_collections = parent_inventory_collections.map { |x| load_inventory_collection_by_name(x) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def build_parent_inventory_collection!
|
127
|
+
return unless supports_building_inventory_collection?
|
128
|
+
|
129
|
+
if association.present? && parent.present? && associations_hash[association].present?
|
130
|
+
# Add immediate parent IC as dependency
|
131
|
+
add_parent_inventory_collection_dependency!(associations_hash[association])
|
132
|
+
# Add root IC in parent_inventory_collections
|
133
|
+
self.parent_inventory_collections = [find_parent_inventory_collection(associations_hash, inventory_collection.association)]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def supports_building_inventory_collection?
|
138
|
+
# Don't try to introspect ICs with custom query or saving code
|
139
|
+
return if arel.present? || custom_save_block.present?
|
140
|
+
# We support :parent_inventory_collections only for targeted mode, where all ICs are present
|
141
|
+
return unless targeted?
|
142
|
+
|
143
|
+
true
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_parent_inventory_collection_dependency!(ic_name)
|
147
|
+
ic = load_inventory_collection_by_name(ic_name)
|
148
|
+
(dependency_attributes[:__parent_inventory_collections] ||= Set.new) << ic if ic
|
149
|
+
end
|
150
|
+
|
151
|
+
def find_parent_inventory_collection(hash, name)
|
152
|
+
if hash[name]
|
153
|
+
find_parent_inventory_collection(hash, hash[name])
|
154
|
+
else
|
155
|
+
name
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def load_inventory_collection_by_name(name)
|
160
|
+
ic = indexed_inventory_collections[name]
|
161
|
+
if ic.nil? && targeted?
|
162
|
+
raise "Can't find InventoryCollection :#{name} referenced from #{inventory_collection}"
|
163
|
+
end
|
164
|
+
|
165
|
+
ic
|
166
|
+
end
|
167
|
+
|
88
168
|
def scan_inventory_object!(inventory_object)
|
89
169
|
inventory_object.data.each do |key, value|
|
90
170
|
if value.kind_of?(Array)
|
@@ -103,8 +183,16 @@ module InventoryRefresh
|
|
103
183
|
value_inventory_collection.add_reference(value.reference, :key => value.key)
|
104
184
|
end
|
105
185
|
|
186
|
+
def scan_all_manager_uuids_scope_attribute!(value)
|
187
|
+
return unless loadable?(value)
|
188
|
+
|
189
|
+
add_reference(value.inventory_collection, value)
|
190
|
+
(dependency_attributes[:__all_manager_uuids_scope] ||= Set.new) << value.inventory_collection
|
191
|
+
end
|
192
|
+
|
106
193
|
def scan_inventory_object_attribute!(key, value)
|
107
194
|
return unless loadable?(value)
|
195
|
+
|
108
196
|
value_inventory_collection = value.inventory_collection
|
109
197
|
|
110
198
|
# Storing attributes and their dependencies
|
@@ -114,9 +202,9 @@ module InventoryRefresh
|
|
114
202
|
# e.g. load all the referenced uuids from a DB
|
115
203
|
add_reference(value_inventory_collection, value)
|
116
204
|
|
117
|
-
if inventory_object_lazy?(value)
|
205
|
+
if inventory_object_lazy?(value) && value.transitive_dependency?
|
118
206
|
# Storing if attribute is a transitive dependency, so a lazy_find :key results in dependency
|
119
|
-
transitive_dependency_attributes << key
|
207
|
+
transitive_dependency_attributes << key
|
120
208
|
end
|
121
209
|
end
|
122
210
|
end
|
@@ -3,7 +3,10 @@ require "active_support/core_ext/module/delegation"
|
|
3
3
|
module InventoryRefresh
|
4
4
|
class InventoryCollection
|
5
5
|
class Serialization
|
6
|
-
delegate :
|
6
|
+
delegate :all_manager_uuids,
|
7
|
+
:all_manager_uuids=,
|
8
|
+
:build,
|
9
|
+
:targeted_scope,
|
7
10
|
:data,
|
8
11
|
:inventory_object_lazy?,
|
9
12
|
:inventory_object?,
|
@@ -25,6 +28,8 @@ module InventoryRefresh
|
|
25
28
|
# @param available_inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of available
|
26
29
|
# InventoryCollection objects
|
27
30
|
def from_hash(inventory_objects_data, available_inventory_collections)
|
31
|
+
targeted_scope.merge!(inventory_objects_data["manager_uuids"].map(&:symbolize_keys!)) if inventory_objects_data["manager_uuids"]
|
32
|
+
|
28
33
|
(inventory_objects_data['data'] || []).each do |inventory_object_data|
|
29
34
|
build(hash_to_data(inventory_object_data, available_inventory_collections).symbolize_keys!)
|
30
35
|
end
|
@@ -32,12 +37,8 @@ module InventoryRefresh
|
|
32
37
|
(inventory_objects_data['partial_data'] || []).each do |inventory_object_data|
|
33
38
|
skeletal_primary_index.build(hash_to_data(inventory_object_data, available_inventory_collections).symbolize_keys!)
|
34
39
|
end
|
35
|
-
end
|
36
40
|
|
37
|
-
|
38
|
-
sweep_scope.map do |s|
|
39
|
-
hash_to_data(s, available_inventory_collections).symbolize_keys!
|
40
|
-
end
|
41
|
+
self.all_manager_uuids = inventory_objects_data['all_manager_uuids']
|
41
42
|
end
|
42
43
|
|
43
44
|
# Serializes InventoryCollection's data storage into Array of Hashes
|
@@ -46,15 +47,14 @@ module InventoryRefresh
|
|
46
47
|
def to_hash
|
47
48
|
{
|
48
49
|
:name => name,
|
50
|
+
# TODO(lsmola) we do not support nested references here, should we?
|
51
|
+
:manager_uuids => targeted_scope.primary_references.values.map(&:full_reference),
|
52
|
+
:all_manager_uuids => all_manager_uuids,
|
49
53
|
:data => data.map { |x| data_to_hash(x.data) },
|
50
54
|
:partial_data => skeletal_primary_index.index_data.map { |x| data_to_hash(x.data) },
|
51
55
|
}
|
52
56
|
end
|
53
57
|
|
54
|
-
def sweep_scope_to_hash(sweep_scope)
|
55
|
-
sweep_scope.map { |x| data_to_hash(x) }
|
56
|
-
end
|
57
|
-
|
58
58
|
private
|
59
59
|
|
60
60
|
# Converts InventoryRefresh::InventoryObject or InventoryRefresh::InventoryObjectLazy into Hash
|
@@ -107,6 +107,10 @@ module InventoryRefresh
|
|
107
107
|
hash.transform_values do |value|
|
108
108
|
if value.kind_of?(Hash) && value['inventory_collection_name']
|
109
109
|
hash_to_lazy_relation(value, available_inventory_collections, depth)
|
110
|
+
elsif value.kind_of?(Array) && value.first.kind_of?(Hash) && value.first['inventory_collection_name']
|
111
|
+
# TODO(lsmola) do we need to compact it sooner? What if first element is nil? On the other hand, we want to
|
112
|
+
# deprecate this Vm HABTM assignment because it's not effective
|
113
|
+
value.compact.map { |x| hash_to_lazy_relation(x, available_inventory_collections, depth) }
|
110
114
|
else
|
111
115
|
value
|
112
116
|
end
|
@@ -124,6 +128,8 @@ module InventoryRefresh
|
|
124
128
|
data.transform_values do |value|
|
125
129
|
if inventory_object_lazy?(value) || inventory_object?(value)
|
126
130
|
lazy_relation_to_hash(value, depth)
|
131
|
+
elsif value.kind_of?(Array) && (inventory_object_lazy?(value.compact.first) || inventory_object?(value.compact.first))
|
132
|
+
value.compact.map { |x| lazy_relation_to_hash(x, depth) }
|
127
133
|
else
|
128
134
|
value
|
129
135
|
end
|
@@ -71,17 +71,30 @@ module InventoryRefresh
|
|
71
71
|
# InventoryCollection, since it is already persisted into the DB.
|
72
72
|
attr_accessor :saved
|
73
73
|
|
74
|
+
# If present, InventoryCollection switches into delete_complement mode, where it will
|
75
|
+
# delete every record from the DB, that is not present in this list. This is used for the batch processing,
|
76
|
+
# where we don't know which InventoryObject should be deleted, but we know all manager_uuids of all
|
77
|
+
# InventoryObject objects that exists in the provider.
|
78
|
+
#
|
79
|
+
# @return [Array, nil] nil or a list of all :manager_uuids that are present in the Provider's InventoryCollection.
|
80
|
+
attr_accessor :all_manager_uuids
|
81
|
+
|
82
|
+
# @return [Array, nil] Scope for applying :all_manager_uuids
|
83
|
+
attr_accessor :all_manager_uuids_scope
|
84
|
+
|
85
|
+
# @return [String] Timestamp in UTC before fetching :all_manager_uuids
|
86
|
+
attr_accessor :all_manager_uuids_timestamp
|
87
|
+
|
74
88
|
# @return [Set] A set of InventoryCollection objects that depends on this InventoryCollection object.
|
75
89
|
attr_accessor :dependees
|
76
90
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
:assert_graph_integrity
|
91
|
+
# @return [Array<Symbol>] @see #parent_inventory_collections documentation of InventoryCollection.new's initialize_ic_relations()
|
92
|
+
# parameters
|
93
|
+
attr_accessor :parent_inventory_collections
|
94
|
+
|
95
|
+
attr_accessor :attributes_blacklist, :attributes_whitelist
|
96
|
+
|
97
|
+
attr_reader :model_class, :strategy, :custom_save_block, :parent, :internal_attributes, :delete_method, :dependency_attributes, :manager_ref, :create_only, :association, :complete, :update_only, :transitive_dependency_attributes, :check_changed, :arel, :inventory_object_attributes, :name, :saver_strategy, :targeted_scope, :default_values, :targeted_arel, :targeted, :manager_ref_allowed_nil, :use_ar_object, :created_records, :updated_records, :deleted_records, :retention_strategy, :custom_reconnect_block, :batch_extra_attributes, :references_storage, :unconnected_edges, :assert_graph_integrity
|
85
98
|
|
86
99
|
delegate :<<,
|
87
100
|
:build,
|
@@ -112,6 +125,7 @@ module InventoryRefresh
|
|
112
125
|
:lazy_find_by,
|
113
126
|
:named_ref,
|
114
127
|
:primary_index,
|
128
|
+
:reindex_secondary_indexes!,
|
115
129
|
:skeletal_primary_index,
|
116
130
|
:to => :index_proxy
|
117
131
|
|
@@ -134,18 +148,28 @@ module InventoryRefresh
|
|
134
148
|
properties[:check_changed],
|
135
149
|
properties[:update_only],
|
136
150
|
properties[:use_ar_object],
|
151
|
+
properties[:targeted],
|
137
152
|
properties[:assert_graph_integrity])
|
138
153
|
|
139
154
|
init_strategies(properties[:strategy],
|
140
|
-
properties[:
|
155
|
+
properties[:saver_strategy],
|
156
|
+
properties[:retention_strategy],
|
157
|
+
properties[:delete_method])
|
141
158
|
|
142
159
|
init_references(properties[:manager_ref],
|
143
160
|
properties[:manager_ref_allowed_nil],
|
144
|
-
properties[:secondary_refs]
|
161
|
+
properties[:secondary_refs],
|
162
|
+
properties[:manager_uuids])
|
145
163
|
|
146
|
-
|
164
|
+
init_all_manager_uuids(properties[:all_manager_uuids],
|
165
|
+
properties[:all_manager_uuids_scope],
|
166
|
+
properties[:all_manager_uuids_timestamp])
|
147
167
|
|
148
|
-
|
168
|
+
init_ic_relations(properties[:dependency_attributes],
|
169
|
+
properties[:parent_inventory_collections])
|
170
|
+
|
171
|
+
init_arels(properties[:arel],
|
172
|
+
properties[:targeted_arel])
|
149
173
|
|
150
174
|
init_custom_procs(properties[:custom_save_block],
|
151
175
|
properties[:custom_reconnect_block])
|
@@ -203,16 +227,16 @@ module InventoryRefresh
|
|
203
227
|
|
204
228
|
# @return [Array<ActiveRecord::ConnectionAdapters::IndexDefinition>] array of all unique indexes known to model
|
205
229
|
def unique_indexes
|
206
|
-
@
|
230
|
+
return @unique_indexes if @unique_indexes
|
207
231
|
|
208
|
-
@
|
232
|
+
@unique_indexes = model_class.connection.indexes(model_class.table_name).select(&:unique)
|
209
233
|
|
210
|
-
if @
|
234
|
+
if @unique_indexes.blank?
|
211
235
|
raise "#{self} and its table #{model_class.table_name} must have a unique index defined, to"\
|
212
|
-
|
236
|
+
" be able to use saver_strategy :concurrent_safe_batch."
|
213
237
|
end
|
214
238
|
|
215
|
-
@
|
239
|
+
@unique_indexes
|
216
240
|
end
|
217
241
|
|
218
242
|
# Finds an index that fits the list of columns (keys) the best
|
@@ -222,7 +246,7 @@ module InventoryRefresh
|
|
222
246
|
# @return [ActiveRecord::ConnectionAdapters::IndexDefinition] unique index fitting the keys
|
223
247
|
def unique_index_for(keys)
|
224
248
|
@unique_index_for_keys_cache ||= {}
|
225
|
-
@unique_index_for_keys_cache[keys] if @unique_index_for_keys_cache[keys]
|
249
|
+
return @unique_index_for_keys_cache[keys] if @unique_index_for_keys_cache[keys]
|
226
250
|
|
227
251
|
# Take the uniq key having the least number of columns
|
228
252
|
@unique_index_for_keys_cache[keys] = uniq_keys_candidates(keys).min_by { |x| x.columns.count }
|
@@ -239,7 +263,7 @@ module InventoryRefresh
|
|
239
263
|
|
240
264
|
if unique_indexes.blank? || uniq_key_candidates.blank?
|
241
265
|
raise "#{self} and its table #{model_class.table_name} must have a unique index defined "\
|
242
|
-
|
266
|
+
"covering columns #{keys} to be able to use saver_strategy :concurrent_safe_batch."
|
243
267
|
end
|
244
268
|
|
245
269
|
uniq_key_candidates
|
@@ -268,7 +292,7 @@ module InventoryRefresh
|
|
268
292
|
def internal_timestamp_columns
|
269
293
|
return @internal_timestamp_columns if @internal_timestamp_columns
|
270
294
|
|
271
|
-
@internal_timestamp_columns = %i
|
295
|
+
@internal_timestamp_columns = %i[created_at created_on updated_at updated_on].collect do |timestamp_col|
|
272
296
|
timestamp_col if supports_column?(timestamp_col)
|
273
297
|
end.compact
|
274
298
|
end
|
@@ -282,11 +306,6 @@ module InventoryRefresh
|
|
282
306
|
@base_columns ||= (unique_index_columns + internal_columns + not_null_columns).uniq
|
283
307
|
end
|
284
308
|
|
285
|
-
# @return [Array<Symbol>] Array of all column names on the InventoryCollection
|
286
|
-
def all_column_names
|
287
|
-
@all_column_names ||= model_class.columns.map { |x| x.name.to_sym }
|
288
|
-
end
|
289
|
-
|
290
309
|
# @param value [Object] Object we want to test
|
291
310
|
# @return [Boolean] true is value is kind of InventoryRefresh::InventoryObject
|
292
311
|
def inventory_object?(value)
|
@@ -311,7 +330,7 @@ module InventoryRefresh
|
|
311
330
|
|
312
331
|
# Convert manager_ref list of attributes to list of DB columns
|
313
332
|
#
|
314
|
-
# @return [Array<String>]
|
333
|
+
# @return [Array<String>] true is processing of this InventoryCollection will be in targeted mode
|
315
334
|
def manager_ref_to_cols
|
316
335
|
# TODO(lsmola) this should contain the polymorphic _type, otherwise the IC with polymorphic unique key will get
|
317
336
|
# conflicts
|
@@ -342,25 +361,13 @@ module InventoryRefresh
|
|
342
361
|
#
|
343
362
|
# @return [Array<Symbol>] attributes that are needed for saving of the record
|
344
363
|
def fixed_attributes
|
345
|
-
not_null_attributes = []
|
346
|
-
|
347
364
|
if model_class
|
348
|
-
# Attrs having presence validator
|
349
365
|
presence_validators = model_class.validators.detect { |x| x.kind_of?(ActiveRecord::Validations::PresenceValidator) }
|
350
|
-
not_null_attributes += presence_validators.attributes if presence_validators.present?
|
351
|
-
|
352
|
-
# Column names having NOT NULL constraint
|
353
|
-
non_null_constraints = model_class.columns_hash.values.reject(&:null).map(&:name) - [model_class.primary_key]
|
354
|
-
not_null_attributes += non_null_constraints.map(&:to_sym)
|
355
|
-
|
356
|
-
# Column names having NOT NULL constraint transformed to relation names
|
357
|
-
not_null_attributes += non_null_constraints.map {|x| foreign_key_to_association_mapping[x]}.compact
|
358
366
|
end
|
359
367
|
# Attributes that has to be always on the entity, so attributes making unique index of the record + attributes
|
360
368
|
# that have presence validation
|
361
|
-
|
362
369
|
fixed_attributes = manager_ref
|
363
|
-
fixed_attributes +=
|
370
|
+
fixed_attributes += presence_validators.attributes if presence_validators.present?
|
364
371
|
fixed_attributes
|
365
372
|
end
|
366
373
|
|
@@ -422,6 +429,7 @@ module InventoryRefresh
|
|
422
429
|
:parent => parent,
|
423
430
|
:arel => arel,
|
424
431
|
:strategy => strategy,
|
432
|
+
:saver_strategy => saver_strategy,
|
425
433
|
:custom_save_block => custom_save_block,
|
426
434
|
# We want cloned IC to be update only, since this is used for cycle resolution
|
427
435
|
:update_only => true,
|
@@ -459,31 +467,79 @@ module InventoryRefresh
|
|
459
467
|
|
460
468
|
# @return [Integer] default batch size for talking to the DB
|
461
469
|
def batch_size
|
470
|
+
# TODO(lsmola) mode to the settings
|
462
471
|
1000
|
463
472
|
end
|
464
473
|
|
465
474
|
# @return [Integer] default batch size for talking to the DB if not using ApplicationRecord objects
|
466
475
|
def batch_size_pure_sql
|
476
|
+
# TODO(lsmola) mode to the settings
|
467
477
|
10_000
|
468
478
|
end
|
469
479
|
|
480
|
+
# Returns a list of stringified uuids of all scoped InventoryObjects, which is used for scoping in targeted mode
|
481
|
+
#
|
482
|
+
# @return [Array<String>] list of stringified uuids of all scoped InventoryObjects
|
483
|
+
def manager_uuids
|
484
|
+
# TODO(lsmola) LEGACY: this is still being used by :targetel_arel definitions and it expects array of strings
|
485
|
+
raise "This works only for :manager_ref size 1" if manager_ref.size > 1
|
486
|
+
|
487
|
+
key = manager_ref.first
|
488
|
+
transform_references_to_hashes(targeted_scope.primary_references).map { |x| x[key] }
|
489
|
+
end
|
490
|
+
|
470
491
|
# Builds a multiselection conditions like (table1.a = a1 AND table2.b = b1) OR (table1.a = a2 AND table2.b = b2)
|
471
492
|
#
|
472
493
|
# @param hashes [Array<Hash>] data we want to use for the query
|
473
494
|
# @param keys [Array<Symbol>] keys of attributes involved
|
474
495
|
# @return [String] A condition usable in .where of an ActiveRecord relation
|
475
|
-
def build_multi_selection_condition(hashes, keys =
|
496
|
+
def build_multi_selection_condition(hashes, keys = manager_ref)
|
476
497
|
arel_table = model_class.arel_table
|
477
498
|
# We do pure SQL OR, since Arel is nesting every .or into another parentheses, otherwise this would be just
|
478
499
|
# inject(:or) instead of to_sql with .join(" OR ")
|
479
500
|
hashes.map { |hash| "(#{keys.map { |key| arel_table[key].eq(hash[key]) }.inject(:and).to_sql})" }.join(" OR ")
|
480
501
|
end
|
481
502
|
|
482
|
-
#
|
483
|
-
#
|
484
|
-
# @return [InventoryRefresh::ApplicationRecordIterator] Iterator for the references and query
|
503
|
+
# @return [ActiveRecord::Relation] A relation that can fetch all data of this InventoryCollection from the DB
|
485
504
|
def db_collection_for_comparison
|
486
|
-
|
505
|
+
if targeted?
|
506
|
+
if targeted_arel.respond_to?(:call)
|
507
|
+
targeted_arel.call(self)
|
508
|
+
elsif parent_inventory_collections.present?
|
509
|
+
targeted_arel_default
|
510
|
+
else
|
511
|
+
targeted_iterator_for(targeted_scope.primary_references)
|
512
|
+
end
|
513
|
+
else
|
514
|
+
full_collection_for_comparison
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
# Builds targeted query limiting the results by the :references defined in parent_inventory_collections
|
519
|
+
#
|
520
|
+
# @return [InventoryRefresh::ApplicationRecordIterator] an iterator for default targeted arel
|
521
|
+
def targeted_arel_default
|
522
|
+
if parent_inventory_collections.collect { |x| x.model_class.base_class }.uniq.count > 1
|
523
|
+
raise "Multiple :parent_inventory_collections with different base class are not supported by default. Write "\
|
524
|
+
":targeted_arel manually, or separate [#{self}] into 2 InventoryCollection objects."
|
525
|
+
end
|
526
|
+
parent_collection = parent_inventory_collections.first
|
527
|
+
references = parent_inventory_collections.map { |x| x.targeted_scope.primary_references }.reduce({}, :merge!)
|
528
|
+
|
529
|
+
parent_collection.targeted_iterator_for(references, full_collection_for_comparison)
|
530
|
+
end
|
531
|
+
|
532
|
+
# Gets targeted references and transforms them into list of hashes
|
533
|
+
#
|
534
|
+
# @param references [Array, InventoryRefresh::InventoryCollection::TargetedScope] passed references
|
535
|
+
# @return [Array<Hash>] References transformed into the array of hashes
|
536
|
+
def transform_references_to_hashes(references)
|
537
|
+
if references.kind_of?(Array)
|
538
|
+
# Sliced InventoryRefresh::InventoryCollection::TargetedScope
|
539
|
+
references.map { |x| x.second.full_reference }
|
540
|
+
else
|
541
|
+
references.values.map(&:full_reference)
|
542
|
+
end
|
487
543
|
end
|
488
544
|
|
489
545
|
# Builds a multiselection conditions like (table1.a = a1 AND table2.b = b1) OR (table1.a = a2 AND table2.b = b2)
|
@@ -492,20 +548,20 @@ module InventoryRefresh
|
|
492
548
|
# @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] passed references
|
493
549
|
# @return [String] A condition usable in .where of an ActiveRecord relation
|
494
550
|
def targeted_selection_for(references)
|
495
|
-
build_multi_selection_condition(references
|
496
|
-
end
|
497
|
-
|
498
|
-
def select_keys
|
499
|
-
@select_keys ||= [@model_class.primary_key] + manager_ref_to_cols.map(&:to_s) + internal_columns.map(&:to_s)
|
500
|
-
end
|
501
|
-
|
502
|
-
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter] ActiveRecord connection
|
503
|
-
def get_connection
|
504
|
-
ActiveRecord::Base.connection
|
551
|
+
build_multi_selection_condition(transform_references_to_hashes(references))
|
505
552
|
end
|
506
553
|
|
507
|
-
|
508
|
-
|
554
|
+
# Returns iterator for the passed references and a query
|
555
|
+
#
|
556
|
+
# @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] Passed references
|
557
|
+
# @param query [ActiveRecord::Relation] relation that can fetch all data of this InventoryCollection from the DB
|
558
|
+
# @return [InventoryRefresh::ApplicationRecordIterator] Iterator for the references and query
|
559
|
+
def targeted_iterator_for(references, query = nil)
|
560
|
+
InventoryRefresh::ApplicationRecordIterator.new(
|
561
|
+
:inventory_collection => self,
|
562
|
+
:manager_uuids_set => references,
|
563
|
+
:query => query
|
564
|
+
)
|
509
565
|
end
|
510
566
|
|
511
567
|
# Builds an ActiveRecord::Relation that can fetch all the references from the DB
|
@@ -513,18 +569,15 @@ module InventoryRefresh
|
|
513
569
|
# @param references [Hash{String => InventoryRefresh::InventoryCollection::Reference}] passed references
|
514
570
|
# @return [ActiveRecord::Relation] relation that can fetch all the references from the DB
|
515
571
|
def db_collection_for_comparison_for(references)
|
516
|
-
|
517
|
-
if pure_sql_record_fetching?
|
518
|
-
return get_connection.query(query.select(*select_keys).to_sql)
|
519
|
-
end
|
520
|
-
|
521
|
-
query
|
572
|
+
full_collection_for_comparison.where(targeted_selection_for(references))
|
522
573
|
end
|
523
574
|
|
524
575
|
# @return [ActiveRecord::Relation] relation that can fetch all the references from the DB
|
525
576
|
def full_collection_for_comparison
|
526
577
|
return arel unless arel.nil?
|
578
|
+
|
527
579
|
rel = parent.send(association)
|
580
|
+
rel = rel.active if rel && supports_column?(:archived_at) && retention_strategy == :archive
|
528
581
|
rel
|
529
582
|
end
|
530
583
|
|
@@ -541,8 +594,6 @@ module InventoryRefresh
|
|
541
594
|
inventory_object_class.new(self, hash)
|
542
595
|
end
|
543
596
|
|
544
|
-
attr_writer :attributes_blacklist, :attributes_whitelist
|
545
|
-
|
546
597
|
private
|
547
598
|
|
548
599
|
# Creates dynamically a subclass of InventoryRefresh::InventoryObject, that will be used per InventoryCollection
|
@@ -574,9 +625,16 @@ module InventoryRefresh
|
|
574
625
|
def record_identity(record)
|
575
626
|
identity = record.try(:[], :id) || record.try(:[], "id") || record.try(:id)
|
576
627
|
raise "Cannot obtain identity of the #{record}" if identity.blank?
|
628
|
+
|
577
629
|
{
|
578
630
|
:id => identity
|
579
631
|
}
|
580
632
|
end
|
633
|
+
|
634
|
+
# TODO: Not used!
|
635
|
+
# @return [Array<Symbol>] all association attributes and foreign keys of the model class
|
636
|
+
def association_attributes
|
637
|
+
model_class.reflect_on_all_associations.map { |x| [x.name, x.foreign_key] }.flatten.compact.map(&:to_sym)
|
638
|
+
end
|
581
639
|
end
|
582
640
|
end
|