inventory_refresh 0.0.1
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 +7 -0
- data/.codeclimate.yml +47 -0
- data/.gitignore +13 -0
- data/.rspec +4 -0
- data/.rspec_ci +4 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_cc.yml +5 -0
- data/.rubocop_local.yml +2 -0
- data/.travis.yml +12 -0
- data/.yamllint +12 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +6 -0
- data/LICENSE +202 -0
- data/README.md +35 -0
- data/Rakefile +47 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/inventory_refresh.gemspec +34 -0
- data/lib/inventory_refresh.rb +11 -0
- data/lib/inventory_refresh/application_record_iterator.rb +56 -0
- data/lib/inventory_refresh/application_record_reference.rb +15 -0
- data/lib/inventory_refresh/graph.rb +157 -0
- data/lib/inventory_refresh/graph/topological_sort.rb +66 -0
- data/lib/inventory_refresh/inventory_collection.rb +1175 -0
- data/lib/inventory_refresh/inventory_collection/data_storage.rb +178 -0
- data/lib/inventory_refresh/inventory_collection/graph.rb +170 -0
- data/lib/inventory_refresh/inventory_collection/index/proxy.rb +230 -0
- data/lib/inventory_refresh/inventory_collection/index/type/base.rb +80 -0
- data/lib/inventory_refresh/inventory_collection/index/type/data.rb +26 -0
- data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +286 -0
- data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +116 -0
- data/lib/inventory_refresh/inventory_collection/reference.rb +96 -0
- data/lib/inventory_refresh/inventory_collection/references_storage.rb +106 -0
- data/lib/inventory_refresh/inventory_collection/scanner.rb +117 -0
- data/lib/inventory_refresh/inventory_collection/serialization.rb +140 -0
- data/lib/inventory_refresh/inventory_object.rb +303 -0
- data/lib/inventory_refresh/inventory_object_lazy.rb +151 -0
- data/lib/inventory_refresh/save_collection/base.rb +38 -0
- data/lib/inventory_refresh/save_collection/recursive.rb +52 -0
- data/lib/inventory_refresh/save_collection/saver/base.rb +390 -0
- data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe.rb +71 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +632 -0
- data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +85 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +120 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +196 -0
- data/lib/inventory_refresh/save_collection/topological_sort.rb +38 -0
- data/lib/inventory_refresh/save_inventory.rb +38 -0
- data/lib/inventory_refresh/target.rb +73 -0
- data/lib/inventory_refresh/target_collection.rb +80 -0
- data/lib/inventory_refresh/version.rb +3 -0
- data/tools/ci/create_db_user.sh +3 -0
- metadata +207 -0
@@ -0,0 +1,303 @@
|
|
1
|
+
require "inventory_refresh/application_record_reference"
|
2
|
+
require "active_support/core_ext/module/delegation"
|
3
|
+
|
4
|
+
module InventoryRefresh
|
5
|
+
class InventoryObject
|
6
|
+
attr_accessor :object, :id
|
7
|
+
attr_reader :inventory_collection, :data, :reference
|
8
|
+
|
9
|
+
delegate :manager_ref, :base_class_name, :model_class, :to => :inventory_collection
|
10
|
+
delegate :[], :[]=, :to => :data
|
11
|
+
|
12
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object owning the
|
13
|
+
# InventoryObject
|
14
|
+
# @param data [Hash] Data of the InventoryObject object
|
15
|
+
def initialize(inventory_collection, data)
|
16
|
+
@inventory_collection = inventory_collection
|
17
|
+
@data = data
|
18
|
+
@object = nil
|
19
|
+
@id = nil
|
20
|
+
@reference = inventory_collection.build_reference(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String] stringified reference
|
24
|
+
def manager_uuid
|
25
|
+
reference.stringified_reference
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [InventoryRefresh::InventoryObject] returns self
|
29
|
+
def load
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def key
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# Transforms InventoryObject object data into hash format with keys that are column names and resolves correct
|
38
|
+
# values of the foreign keys (even the polymorphic ones)
|
39
|
+
#
|
40
|
+
# @param inventory_collection_scope [InventoryRefresh::InventoryCollection] parent InventoryCollection object
|
41
|
+
# @return [Hash] Data in DB format
|
42
|
+
def attributes(inventory_collection_scope = nil)
|
43
|
+
# We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with
|
44
|
+
# different blacklist and whitelist. The generic code always passes a scope.
|
45
|
+
inventory_collection_scope ||= inventory_collection
|
46
|
+
|
47
|
+
attributes_for_saving = {}
|
48
|
+
# First transform the values
|
49
|
+
data.each do |key, value|
|
50
|
+
if !allowed?(inventory_collection_scope, key)
|
51
|
+
next
|
52
|
+
elsif value.kind_of?(Array) && value.any? { |x| loadable?(x) }
|
53
|
+
# Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
|
54
|
+
# result
|
55
|
+
data[key] = value.compact.map(&:load).compact
|
56
|
+
# We can use built in _ids methods to assign array of ids into has_many relations. So e.g. the :key_pairs=
|
57
|
+
# relation setter will become :key_pair_ids=
|
58
|
+
attributes_for_saving[(key.to_s.singularize + "_ids").to_sym] = data[key].map(&:id).compact.uniq
|
59
|
+
elsif loadable?(value) || inventory_collection_scope.association_to_foreign_key_mapping[key]
|
60
|
+
# Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
|
61
|
+
# result
|
62
|
+
data[key] = value.load if value.respond_to?(:load)
|
63
|
+
if (foreign_key = inventory_collection_scope.association_to_foreign_key_mapping[key])
|
64
|
+
# We have an association to fill, lets fill also the :key, cause some other InventoryObject can refer to it
|
65
|
+
record_id = data[key].try(:id)
|
66
|
+
attributes_for_saving[foreign_key.to_sym] = record_id
|
67
|
+
|
68
|
+
if (foreign_type = inventory_collection_scope.association_to_foreign_type_mapping[key])
|
69
|
+
# If we have a polymorphic association, we need to also fill a base class name, but we want to nullify it
|
70
|
+
# if record_id is missing
|
71
|
+
base_class = data[key].try(:base_class_name) || data[key].class.try(:base_class).try(:name)
|
72
|
+
attributes_for_saving[foreign_type.to_sym] = record_id ? base_class : nil
|
73
|
+
end
|
74
|
+
elsif data[key].kind_of?(::InventoryRefresh::InventoryObject)
|
75
|
+
# We have an association to fill but not an Activerecord association, so e.g. Ancestry, lets just load
|
76
|
+
# it here. This way of storing ancestry is ineffective in DB call count, but RAM friendly
|
77
|
+
attributes_for_saving[key.to_sym] = data[key].base_class_name.constantize.find_by(:id => data[key].id)
|
78
|
+
else
|
79
|
+
# We have a normal attribute to fill
|
80
|
+
attributes_for_saving[key.to_sym] = data[key]
|
81
|
+
end
|
82
|
+
else
|
83
|
+
attributes_for_saving[key.to_sym] = value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
attributes_for_saving
|
88
|
+
end
|
89
|
+
|
90
|
+
# Transforms InventoryObject object data into hash format with keys that are column names and resolves correct
|
91
|
+
# values of the foreign keys (even the polymorphic ones)
|
92
|
+
#
|
93
|
+
# @param inventory_collection_scope [InventoryRefresh::InventoryCollection] parent InventoryCollection object
|
94
|
+
# @param all_attribute_keys [Array<Symbol>] Attribute keys we will modify based on object's data
|
95
|
+
# @return [Hash] Data in DB format
|
96
|
+
def attributes_with_keys(inventory_collection_scope = nil, all_attribute_keys = [])
|
97
|
+
# We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with
|
98
|
+
# different blacklist and whitelist. The generic code always passes a scope.
|
99
|
+
inventory_collection_scope ||= inventory_collection
|
100
|
+
|
101
|
+
attributes_for_saving = {}
|
102
|
+
# First transform the values
|
103
|
+
data.each do |key, value|
|
104
|
+
if !allowed?(inventory_collection_scope, key)
|
105
|
+
next
|
106
|
+
elsif loadable?(value) || inventory_collection_scope.association_to_foreign_key_mapping[key]
|
107
|
+
# Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
|
108
|
+
# result
|
109
|
+
data[key] = value.load if value.respond_to?(:load)
|
110
|
+
if (foreign_key = inventory_collection_scope.association_to_foreign_key_mapping[key])
|
111
|
+
# We have an association to fill, lets fill also the :key, cause some other InventoryObject can refer to it
|
112
|
+
record_id = data[key].try(:id)
|
113
|
+
foreign_key_to_sym = foreign_key.to_sym
|
114
|
+
attributes_for_saving[foreign_key_to_sym] = record_id
|
115
|
+
all_attribute_keys << foreign_key_to_sym
|
116
|
+
if (foreign_type = inventory_collection_scope.association_to_foreign_type_mapping[key])
|
117
|
+
# If we have a polymorphic association, we need to also fill a base class name, but we want to nullify it
|
118
|
+
# if record_id is missing
|
119
|
+
base_class = data[key].try(:base_class_name) || data[key].class.try(:base_class).try(:name)
|
120
|
+
foreign_type_to_sym = foreign_type.to_sym
|
121
|
+
attributes_for_saving[foreign_type_to_sym] = record_id ? base_class : nil
|
122
|
+
all_attribute_keys << foreign_type_to_sym
|
123
|
+
end
|
124
|
+
else
|
125
|
+
# We have a normal attribute to fill
|
126
|
+
attributes_for_saving[key] = data[key]
|
127
|
+
all_attribute_keys << key
|
128
|
+
end
|
129
|
+
else
|
130
|
+
attributes_for_saving[key] = value
|
131
|
+
all_attribute_keys << key
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
attributes_for_saving
|
136
|
+
end
|
137
|
+
|
138
|
+
# Given hash of attributes, we assign them to InventoryObject object using its public writers
|
139
|
+
#
|
140
|
+
# @param attributes [Hash] attributes we want to assign
|
141
|
+
# @return [InventoryRefresh::InventoryObject] self
|
142
|
+
def assign_attributes(attributes)
|
143
|
+
attributes.each do |k, v|
|
144
|
+
# We don't want timestamps or resource versions to be overwritten here, since those are driving the conditions
|
145
|
+
next if %i(resource_timestamps resource_timestamps_max resource_timestamp).include?(k)
|
146
|
+
next if %i(resource_versions resource_versions_max resource_version).include?(k)
|
147
|
+
|
148
|
+
if data[:resource_timestamp] && attributes[:resource_timestamp]
|
149
|
+
assign_only_newest(:resource_timestamp, :resource_timestamps, attributes, data, k, v)
|
150
|
+
elsif data[:resource_version] && attributes[:resource_version]
|
151
|
+
assign_only_newest(:resource_version, :resource_versions, attributes, data, k, v)
|
152
|
+
else
|
153
|
+
public_send("#{k}=", v)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
if attributes[:resource_timestamp]
|
158
|
+
assign_full_row_version_attr(:resource_timestamp, attributes, data)
|
159
|
+
elsif attributes[:resource_version]
|
160
|
+
assign_full_row_version_attr(:resource_version, attributes, data)
|
161
|
+
end
|
162
|
+
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
# @return [String] stringified UUID
|
167
|
+
def to_s
|
168
|
+
manager_uuid
|
169
|
+
end
|
170
|
+
|
171
|
+
# @return [String] string format for nice logging
|
172
|
+
def inspect
|
173
|
+
"InventoryObject:('#{manager_uuid}', #{inventory_collection})"
|
174
|
+
end
|
175
|
+
|
176
|
+
# @return [TrueClass] InventoryObject object is always a dependency
|
177
|
+
def dependency?
|
178
|
+
true
|
179
|
+
end
|
180
|
+
|
181
|
+
# Adds setters and getters based on :inventory_object_attributes kwarg passed into InventoryCollection
|
182
|
+
# Methods already defined should not be redefined (causes unexpected behaviour)
|
183
|
+
#
|
184
|
+
# @param inventory_object_attributes [Array<Symbol>]
|
185
|
+
def self.add_attributes(inventory_object_attributes)
|
186
|
+
defined_methods = InventoryRefresh::InventoryObject.instance_methods(false)
|
187
|
+
|
188
|
+
inventory_object_attributes.each do |attr|
|
189
|
+
unless defined_methods.include?("#{attr}=".to_sym)
|
190
|
+
define_method("#{attr}=") do |value|
|
191
|
+
data[attr] = value
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
unless defined_methods.include?(attr.to_sym)
|
196
|
+
define_method(attr) do
|
197
|
+
data[attr]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
# Assigns value based on the version attributes. If versions are specified, it asigns attribute only if it's
|
206
|
+
# newer than existing attribute.
|
207
|
+
#
|
208
|
+
# @param full_row_version_attr [Symbol] Attr name for full rows, allowed values are
|
209
|
+
# [:resource_timestamp, :resource_version]
|
210
|
+
# @param partial_row_version_attr [Symbol] Attr name for partial rows, allowed values are
|
211
|
+
# [:resource_timestamps, :resource_versions]
|
212
|
+
# @param attributes [Hash] New attributes we are assigning
|
213
|
+
# @param data [Hash] Existing attributes of the InventoryObject
|
214
|
+
# @param k [Symbol] Name of the attribute we are assigning
|
215
|
+
# @param v [Object] Value of the attribute we are assigning
|
216
|
+
def assign_only_newest(full_row_version_attr, partial_row_version_attr, attributes, data, k, v)
|
217
|
+
# If timestamps are in play, we will set only attributes that are newer
|
218
|
+
specific_attr_timestamp = attributes[partial_row_version_attr].try(:[], k)
|
219
|
+
specific_data_timestamp = data[partial_row_version_attr].try(:[], k)
|
220
|
+
|
221
|
+
assign = if !specific_attr_timestamp
|
222
|
+
# Data have no timestamp, we will ignore the check
|
223
|
+
true
|
224
|
+
elsif specific_attr_timestamp && !specific_data_timestamp
|
225
|
+
# Data specific timestamp is nil and we have new specific timestamp
|
226
|
+
if data.key?(k)
|
227
|
+
if attributes[full_row_version_attr] >= data[full_row_version_attr]
|
228
|
+
# We can save if the full timestamp is bigger, if the data already contains the attribute
|
229
|
+
true
|
230
|
+
end
|
231
|
+
else
|
232
|
+
# Data do not contain the attribute, so we are saving the newest
|
233
|
+
true
|
234
|
+
end
|
235
|
+
true
|
236
|
+
elsif specific_attr_timestamp > specific_data_timestamp
|
237
|
+
# both partial timestamps are there, newer must be bigger
|
238
|
+
true
|
239
|
+
end
|
240
|
+
|
241
|
+
if assign
|
242
|
+
public_send("#{k}=", v) # Attribute is newer than current one, lets use it
|
243
|
+
(data[partial_row_version_attr] ||= {})[k] = specific_attr_timestamp if specific_attr_timestamp # and set the latest timestamp
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Assigns attribute representing version of the whole row
|
248
|
+
#
|
249
|
+
# @param full_row_version_attr [Symbol] Attr name for full rows, allowed values are
|
250
|
+
# [:resource_timestamp, :resource_version]
|
251
|
+
# @param attributes [Hash] New attributes we are assigning
|
252
|
+
# @param data [Hash] Existing attributes of the InventoryObject
|
253
|
+
def assign_full_row_version_attr(full_row_version_attr, attributes, data)
|
254
|
+
if attributes[full_row_version_attr] && data[full_row_version_attr]
|
255
|
+
# If both timestamps are present, store the bigger one
|
256
|
+
data[full_row_version_attr] = attributes[full_row_version_attr] if attributes[full_row_version_attr] > data[full_row_version_attr]
|
257
|
+
elsif attributes[full_row_version_attr] && !data[full_row_version_attr]
|
258
|
+
# We are assigning timestamp that was missing
|
259
|
+
data[full_row_version_attr] = attributes[full_row_version_attr]
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Return true passed key representing a getter is an association
|
264
|
+
#
|
265
|
+
# @param inventory_collection_scope [InventoryRefresh::InventoryCollection]
|
266
|
+
# @param key [Symbol] key representing getter
|
267
|
+
# @return [Boolean] true if the passed key points to association
|
268
|
+
def association?(inventory_collection_scope, key)
|
269
|
+
# Is the key an association on inventory_collection_scope model class?
|
270
|
+
!inventory_collection_scope.association_to_foreign_key_mapping[key].nil?
|
271
|
+
end
|
272
|
+
|
273
|
+
# Return true if the attribute is allowed to be saved into the DB
|
274
|
+
#
|
275
|
+
# @param inventory_collection_scope [InventoryRefresh::InventoryCollection] InventoryCollection object owning the
|
276
|
+
# attribute
|
277
|
+
# @param key [Symbol] attribute name
|
278
|
+
# @return true if the attribute is allowed to be saved into the DB
|
279
|
+
def allowed?(inventory_collection_scope, key)
|
280
|
+
foreign_to_association = inventory_collection_scope.foreign_key_to_association_mapping[key] ||
|
281
|
+
inventory_collection_scope.foreign_type_to_association_mapping[key]
|
282
|
+
|
283
|
+
return false if inventory_collection_scope.attributes_blacklist.present? &&
|
284
|
+
(inventory_collection_scope.attributes_blacklist.include?(key) ||
|
285
|
+
(foreign_to_association && inventory_collection_scope.attributes_blacklist.include?(foreign_to_association)))
|
286
|
+
|
287
|
+
return false if inventory_collection_scope.attributes_whitelist.present? &&
|
288
|
+
(!inventory_collection_scope.attributes_whitelist.include?(key) &&
|
289
|
+
(!foreign_to_association || (foreign_to_association && inventory_collection_scope.attributes_whitelist.include?(foreign_to_association))))
|
290
|
+
|
291
|
+
true
|
292
|
+
end
|
293
|
+
|
294
|
+
# Return true if the object is loadable, which we determine by a list of loadable classes.
|
295
|
+
#
|
296
|
+
# @param value [Object] object we test
|
297
|
+
# @return true if the object is loadable
|
298
|
+
def loadable?(value)
|
299
|
+
value.kind_of?(::InventoryRefresh::InventoryObjectLazy) || value.kind_of?(::InventoryRefresh::InventoryObject) ||
|
300
|
+
value.kind_of?(::InventoryRefresh::ApplicationRecordReference)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require "active_support/core_ext/module/delegation"
|
2
|
+
|
3
|
+
module InventoryRefresh
|
4
|
+
class InventoryObjectLazy
|
5
|
+
attr_reader :reference, :inventory_collection, :key, :default, :transform_nested_lazy_finds
|
6
|
+
|
7
|
+
delegate :stringified_reference, :ref, :[], :to => :reference
|
8
|
+
|
9
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object owning the
|
10
|
+
# InventoryObject
|
11
|
+
# @param index_data [Hash] data of the InventoryObject object
|
12
|
+
# @param ref [Symbol] reference name
|
13
|
+
# @param key [Symbol] key name, will be used to fetch attribute from resolved InventoryObject
|
14
|
+
# @param default [Object] a default value used if the :key will resolve to nil
|
15
|
+
# @param transform_nested_lazy_finds [Boolean] True if we want to convert all lazy objects in InventoryObject
|
16
|
+
# objects and reset the Reference. TODO(lsmola) we should be able to do this automatically, then we can
|
17
|
+
# remove this option
|
18
|
+
def initialize(inventory_collection, index_data, ref: :manager_ref, key: nil, default: nil, transform_nested_lazy_finds: false)
|
19
|
+
@inventory_collection = inventory_collection
|
20
|
+
@reference = inventory_collection.build_reference(index_data, ref)
|
21
|
+
@key = key
|
22
|
+
@default = default
|
23
|
+
|
24
|
+
@transform_nested_lazy_finds = transform_nested_lazy_finds
|
25
|
+
|
26
|
+
# We do not support skeletal pre-create for :key, since :key will not be available, we want to use local_db_find
|
27
|
+
# instead.
|
28
|
+
skeletal_precreate! unless @key
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] stringified reference
|
32
|
+
def to_s
|
33
|
+
# TODO(lsmola) do we need this method?
|
34
|
+
stringified_reference
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String] string format for nice logging
|
38
|
+
def inspect
|
39
|
+
suffix = ""
|
40
|
+
suffix += ", ref: #{ref}" if ref.present?
|
41
|
+
suffix += ", key: #{key}" if key.present?
|
42
|
+
"InventoryObjectLazy:('#{self}', #{inventory_collection}#{suffix})"
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [InventoryRefresh::InventoryObject, Object] InventoryRefresh::InventoryObject instance or an attribute
|
46
|
+
# on key
|
47
|
+
def load
|
48
|
+
transform_nested_secondary_indexes! if transform_nested_lazy_finds && nested_secondary_index?
|
49
|
+
|
50
|
+
key ? load_object_with_key : load_object
|
51
|
+
end
|
52
|
+
|
53
|
+
# return [Boolean] true if the Lazy object is causing a dependency, Lazy link is always a dependency if no :key
|
54
|
+
# is provider or if it's transitive_dependency
|
55
|
+
def dependency?
|
56
|
+
# If key is not set, InventoryObjectLazy is a dependency, cause it points to the record itself. Otherwise
|
57
|
+
# InventoryObjectLazy is a dependency only if it points to an attribute which is a dependency or a relation.
|
58
|
+
!key || transitive_dependency?
|
59
|
+
end
|
60
|
+
|
61
|
+
# return [Boolean] true if the Lazy object is causing a transitive dependency, which happens if the :key points
|
62
|
+
# to an attribute that is causing a dependency.
|
63
|
+
def transitive_dependency?
|
64
|
+
# If the dependency is inventory_collection.lazy_find(:ems_ref, :key => :stack)
|
65
|
+
# and a :stack is a relation to another object, in the InventoryObject object,
|
66
|
+
# then this relation is considered transitive.
|
67
|
+
key && association?(key)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Boolean] true if the key is an association on inventory_collection_scope model class
|
71
|
+
def association?(key)
|
72
|
+
# TODO(lsmola) remove this if there will be better dependency scan, probably with transitive dependencies filled
|
73
|
+
# in a second pass, then we can get rid of this hardcoded symbols. Right now we are not able to introspect these.
|
74
|
+
return true if [:parent, :genealogy_parent].include?(key)
|
75
|
+
|
76
|
+
inventory_collection.dependency_attributes.key?(key) ||
|
77
|
+
!inventory_collection.association_to_foreign_key_mapping[key].nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
def transform_nested_secondary_indexes!(depth = 0)
|
81
|
+
raise "Nested references are too deep!" if depth > 20
|
82
|
+
|
83
|
+
keys.each do |x|
|
84
|
+
attr = full_reference[x]
|
85
|
+
next unless attr.kind_of?(InventoryRefresh::InventoryObjectLazy)
|
86
|
+
next if attr.primary?
|
87
|
+
|
88
|
+
if attr.nested_secondary_index?
|
89
|
+
attr.transform_nested_secondary_indexes!(depth + 1)
|
90
|
+
end
|
91
|
+
|
92
|
+
full_reference[x] = full_reference[x].load
|
93
|
+
end
|
94
|
+
|
95
|
+
# Rebuild the reference to get the right value
|
96
|
+
self.reference = inventory_collection.build_reference(full_reference, ref)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
delegate :parallel_safe?, :saved?, :saver_strategy, :skeletal_primary_index, :targeted?, :to => :inventory_collection
|
102
|
+
delegate :nested_secondary_index?, :primary?, :full_reference, :keys, :primary?, :to => :reference
|
103
|
+
|
104
|
+
attr_writer :reference
|
105
|
+
|
106
|
+
# Instead of loading the reference from the DB, we'll add the skeletal InventoryObject (having manager_ref and
|
107
|
+
# info from the default_values) to the correct InventoryCollection. Which will either be found in the DB or
|
108
|
+
# created as a skeletal object. The later refresh of the object will then fill the rest of the data, while not
|
109
|
+
# touching the reference.
|
110
|
+
#
|
111
|
+
# @return [InventoryRefresh::InventoryObject, NilClass] Returns pre-created InventoryObject or nil
|
112
|
+
def skeletal_precreate!
|
113
|
+
# We can do skeletal pre-create only for strategies using unique indexes. Since this can build records out of
|
114
|
+
# the given :arel scope, we will always attempt to create the recod, so we need unique index to avoid duplication
|
115
|
+
# of records.
|
116
|
+
return unless parallel_safe?
|
117
|
+
# Pre-create only for strategies that will be persisting data, i.e. are not saved already
|
118
|
+
return if saved?
|
119
|
+
# We can only do skeletal pre-create for primary index reference, since that is needed to create DB unique index
|
120
|
+
return unless primary?
|
121
|
+
# Full reference must be present
|
122
|
+
return if full_reference.blank?
|
123
|
+
|
124
|
+
# To avoid pre-creating invalid records all fields of a primary key must have value
|
125
|
+
# TODO(lsmola) for composite keys, it's still valid to have one of the keys nil, figure out how to allow this
|
126
|
+
return if keys.any? { |x| full_reference[x].blank? }
|
127
|
+
|
128
|
+
skeletal_primary_index.build(full_reference)
|
129
|
+
end
|
130
|
+
|
131
|
+
# @return [Object] value found or :key or default value if the value is nil
|
132
|
+
def load_object_with_key
|
133
|
+
# TODO(lsmola) Log error if we are accessing path that is present in blacklist or not present in whitelist
|
134
|
+
found = inventory_collection.find(reference)
|
135
|
+
if found.present?
|
136
|
+
if found.try(:data).present?
|
137
|
+
found.data[key] || default
|
138
|
+
else
|
139
|
+
found.public_send(key) || default
|
140
|
+
end
|
141
|
+
else
|
142
|
+
default
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [InventoryRefresh::InventoryObject, NilClass] InventoryRefresh::InventoryObject instance or nil if not found
|
147
|
+
def load_object
|
148
|
+
inventory_collection.find(reference)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|