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,80 @@
|
|
1
|
+
require "active_support/core_ext/module/delegation"
|
2
|
+
|
3
|
+
module InventoryRefresh
|
4
|
+
class InventoryCollection
|
5
|
+
module Index
|
6
|
+
module Type
|
7
|
+
class Base
|
8
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection owning the index
|
9
|
+
# @param index_name
|
10
|
+
def initialize(inventory_collection, index_name, attribute_names, *_args)
|
11
|
+
@index = {}
|
12
|
+
|
13
|
+
@inventory_collection = inventory_collection
|
14
|
+
@index_name = index_name
|
15
|
+
@attribute_names = attribute_names
|
16
|
+
|
17
|
+
assert_attribute_names!
|
18
|
+
end
|
19
|
+
|
20
|
+
delegate :keys, :to => :index
|
21
|
+
|
22
|
+
# Indexes passed InventoryObject
|
23
|
+
#
|
24
|
+
# @param inventory_object [InventoryRefresh::InventoryObject] InventoryObject we want to index
|
25
|
+
# @return [InventoryRefresh::InventoryObject] InventoryObject object
|
26
|
+
def store_index_for(inventory_object)
|
27
|
+
index[build_stringified_reference(inventory_object.data, attribute_names)] = inventory_object
|
28
|
+
end
|
29
|
+
|
30
|
+
# Rebuilds the indexes for all InventoryObject objects
|
31
|
+
def reindex!
|
32
|
+
self.index = {}
|
33
|
+
data.each do |inventory_object|
|
34
|
+
store_index_for(inventory_object)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Array] Returns index data
|
39
|
+
def index_data
|
40
|
+
index.values
|
41
|
+
end
|
42
|
+
|
43
|
+
# Find value based on index_value
|
44
|
+
#
|
45
|
+
# @param _index_value [String] a index_value of the InventoryObject we search for
|
46
|
+
def find(_index_value)
|
47
|
+
raise "Implement in subclass"
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
attr_reader :attribute_names, :index, :index_name, :inventory_collection
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
attr_writer :index
|
57
|
+
|
58
|
+
delegate :build_stringified_reference, :data, :model_class, :custom_save_block, :to => :inventory_collection
|
59
|
+
|
60
|
+
# Asserts that InventoryCollection model has attributes specified for index
|
61
|
+
def assert_attribute_names!
|
62
|
+
# Skip for manually defined nodes
|
63
|
+
return if model_class.nil?
|
64
|
+
# When we do custom saving, we allow any indexes to be passed, to no limit the user
|
65
|
+
return unless custom_save_block.nil?
|
66
|
+
|
67
|
+
# We cannot simply do model_class.method_defined?(attribute_name.to_sym), because e.g. db attributes seems
|
68
|
+
# to be create lazily
|
69
|
+
test_model_object = model_class.new
|
70
|
+
attribute_names.each do |attribute_name|
|
71
|
+
unless test_model_object.respond_to?(attribute_name.to_sym)
|
72
|
+
raise "Invalid definition of index :#{index_name}, there is no attribute :#{attribute_name} on model #{model_class}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "inventory_refresh/inventory_collection/index/type/base"
|
2
|
+
|
3
|
+
module InventoryRefresh
|
4
|
+
class InventoryCollection
|
5
|
+
module Index
|
6
|
+
module Type
|
7
|
+
class Data < InventoryRefresh::InventoryCollection::Index::Type::Base
|
8
|
+
# Find value based on index_value
|
9
|
+
#
|
10
|
+
# @param index_value [String] a index_value of the InventoryObject we search in data
|
11
|
+
def find(index_value)
|
12
|
+
index[index_value]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Deletes and returns the value on the index_value
|
16
|
+
#
|
17
|
+
# @param index_value [String] a index_value of the InventoryObject we search for
|
18
|
+
# @return [InventoryObject|nil] Returns found value or nil
|
19
|
+
def delete(index_value)
|
20
|
+
index.delete(index_value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
require "inventory_refresh/inventory_collection/index/type/base"
|
2
|
+
require "inventory_refresh/application_record_reference"
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
require "more_core_extensions/core_ext/hash"
|
5
|
+
|
6
|
+
module InventoryRefresh
|
7
|
+
class InventoryCollection
|
8
|
+
module Index
|
9
|
+
module Type
|
10
|
+
class LocalDb < InventoryRefresh::InventoryCollection::Index::Type::Base
|
11
|
+
# (see InventoryRefresh::InventoryCollection::Index::Type::Base#initialize)
|
12
|
+
# @param data_index[InventoryRefresh::InventoryCollection::Index::Type::Data] Related data index, so we can
|
13
|
+
# figure out what data we are building vs. what we need to fetch from the DB
|
14
|
+
def initialize(inventory_collection, index_name, attribute_names, data_index)
|
15
|
+
super
|
16
|
+
|
17
|
+
@index = nil
|
18
|
+
@loaded_references = Set.new
|
19
|
+
@data_index = data_index
|
20
|
+
@all_references_loaded = false
|
21
|
+
end
|
22
|
+
|
23
|
+
# Finds reference in the DB. Using a configured strategy we cache obtained data in the index, so the
|
24
|
+
# same find will not hit database twice. Also if we use lazy_links and this is called when
|
25
|
+
# data_collection_finalized?, we load all data from the DB, referenced by lazy_links, in one query.
|
26
|
+
#
|
27
|
+
# @param reference [InventoryRefresh::InventoryCollection::Reference] Reference we want to find
|
28
|
+
def find(reference)
|
29
|
+
# Use the cached index only data_collection_finalized?, meaning no new reference can occur
|
30
|
+
if data_collection_finalized? && all_references_loaded? && index
|
31
|
+
return index[reference.stringified_reference]
|
32
|
+
else
|
33
|
+
return index[reference.stringified_reference] if index && index[reference.stringified_reference]
|
34
|
+
# We haven't found the reference, lets add it to the list of references and load it
|
35
|
+
add_reference(reference)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Put our existing data_index keys into loaded references
|
39
|
+
loaded_references.merge(data_index.keys)
|
40
|
+
# Load the rest of the references from the DB
|
41
|
+
populate_index!
|
42
|
+
|
43
|
+
self.all_references_loaded = true if data_collection_finalized?
|
44
|
+
|
45
|
+
index[reference.stringified_reference]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
attr_accessor :all_references_loaded, :schema
|
51
|
+
attr_reader :data_index, :loaded_references
|
52
|
+
attr_writer :index
|
53
|
+
|
54
|
+
delegate :add_reference,
|
55
|
+
:arel,
|
56
|
+
:association,
|
57
|
+
:association_to_base_class_mapping,
|
58
|
+
:association_to_foreign_key_mapping,
|
59
|
+
:association_to_foreign_type_mapping,
|
60
|
+
:attribute_references,
|
61
|
+
:data_collection_finalized?,
|
62
|
+
:db_relation,
|
63
|
+
:inventory_object?,
|
64
|
+
:inventory_object_lazy?,
|
65
|
+
:model_class,
|
66
|
+
:new_inventory_object,
|
67
|
+
:parent,
|
68
|
+
:references,
|
69
|
+
:strategy,
|
70
|
+
:stringify_joiner,
|
71
|
+
:table_name,
|
72
|
+
:to => :inventory_collection
|
73
|
+
|
74
|
+
alias all_references_loaded? all_references_loaded
|
75
|
+
|
76
|
+
# Fills index with InventoryObjects obtained from the DB
|
77
|
+
def populate_index!
|
78
|
+
# Load only new references from the DB
|
79
|
+
new_references = index_references - loaded_references
|
80
|
+
# And store which references we've already loaded
|
81
|
+
loaded_references.merge(new_references)
|
82
|
+
|
83
|
+
# Initialize index in nil
|
84
|
+
self.index ||= {}
|
85
|
+
|
86
|
+
return if new_references.blank? # Return if all references are already loaded
|
87
|
+
|
88
|
+
# TODO(lsmola) selected need to contain also :keys used in other InventoryCollections pointing to this one, once
|
89
|
+
# we get list of all keys for each InventoryCollection ,we can uncomnent
|
90
|
+
# selected = [:id] + attribute_names.map { |x| model_class.reflect_on_association(x).try(:foreign_key) || x }
|
91
|
+
# selected << :type if model_class.new.respond_to? :type
|
92
|
+
# load_from_db.select(selected).find_each do |record|
|
93
|
+
|
94
|
+
full_references = references[index_name].select { |key, _value| new_references.include?(key) } # O(1) include on Set
|
95
|
+
full_references = full_references.values.map(&:full_reference)
|
96
|
+
|
97
|
+
schema = get_schema(full_references)
|
98
|
+
paths = schema.keys
|
99
|
+
rails_friendly_includes_schema = get_rails_friendly_includes_schema(paths)
|
100
|
+
|
101
|
+
all_values = full_references.map do |ref|
|
102
|
+
schema.map do |schema_item_path, arel_column|
|
103
|
+
arel_column.eq(fetch_hash_path(schema_item_path, ref))
|
104
|
+
end.inject(:and)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return the the correct relation based on strategy and selection&projection
|
108
|
+
projection = nil
|
109
|
+
|
110
|
+
db_relation(rails_friendly_includes_schema, all_values, projection).find_each do |record|
|
111
|
+
process_db_record!(record, paths)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Builds a multiselection conditions like (table1.a = a1 AND table2.b = b1) OR (table1.a = a2 AND table2.b = b2)
|
116
|
+
# @param all_values [Array<Arel::Nodes::And>] nested array of arel nodes
|
117
|
+
# @return [String] A condition usable in .where of an ActiveRecord relation
|
118
|
+
def build_multi_selection_condition(all_values)
|
119
|
+
# We do pure SQL OR, since Arel is nesting every .or into another parentheses, otherwise this would be just
|
120
|
+
# all_values.inject(:or)
|
121
|
+
all_values.map { |value| "(#{value.to_sql})" }.join(" OR ")
|
122
|
+
end
|
123
|
+
|
124
|
+
# Traverses the schema_item_path e.g. [:hardware, :vm_or_template, :ems_ref] and gets the value on the path
|
125
|
+
# in hash.
|
126
|
+
# @param path [Array] path for traversing hash e.g. [:hardware, :vm_or_template, :ems_ref]
|
127
|
+
# @param hash [Hash] nested hash with data e.g. {:hardware => {:vm_or_template => {:ems_ref => "value"}}}
|
128
|
+
# @return [Object] value in the Hash on the path, in the example that would be "value"
|
129
|
+
def fetch_hash_path(path, hash)
|
130
|
+
path.inject(hash) { |x, r| x.try(:[], r) }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Traverses the schema_item_path e.g. [:hardware, :vm_or_template, :ems_ref] and gets the value on the path
|
134
|
+
# in object.
|
135
|
+
# @param path [Array] path for traversing hash e.g. [:hardware, :vm_or_template, :ems_ref]
|
136
|
+
# @param object [ApplicationRecord] an ApplicationRecord fetched from the DB
|
137
|
+
# @return [Object] value in the Hash on the path, in the example that would be "value"
|
138
|
+
def fetch_object_path(path, object)
|
139
|
+
path.inject(object) { |x, r| x.public_send(r) }
|
140
|
+
end
|
141
|
+
|
142
|
+
# For full_reference {:hardware => lazy_find_hardware(lazy_find_vm_or_template(:ems_ref))}
|
143
|
+
# we get schema of
|
144
|
+
# {[:hardware, :vm_or_template, :ems_ref] => VmOrTemplate.arel_table[:ems_ref]]
|
145
|
+
#
|
146
|
+
# @param full_references [Hash] InventoryRefresh::InventoryCollection::Reference object full_reference method
|
147
|
+
# containing full reference to InventoryObject
|
148
|
+
# @return [Hash] Hash containing key representing path to record's attribute and value representing arel
|
149
|
+
# definition of column
|
150
|
+
def get_schema(full_references)
|
151
|
+
@schema ||= get_schema_recursive(attribute_names, model_class.arel_table, full_references.first, {}, [], 0)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Converts an array of paths to attributes in different DB tables into rails friendly format, that can be used
|
155
|
+
# for correct DB JOIN of those tables (references&includes methods)
|
156
|
+
# @param [Array[Array]] Nested array with paths e.g. [[:hardware, :vm_or_template, :ems_ref], [:description]
|
157
|
+
# @return [Array] A rails friendly format for ActiveRecord relation .includes and .references
|
158
|
+
def get_rails_friendly_includes_schema(paths)
|
159
|
+
return @rails_friendly_includes_schema if @rails_friendly_includes_schema
|
160
|
+
|
161
|
+
nested_hashes_schema = {}
|
162
|
+
# Ignore last value in path and build nested hash from paths, e.g. paths
|
163
|
+
# [[:hardware, :vm_or_template, :ems_ref], [:description] will be transformed to
|
164
|
+
# [[:hardware, :vm_or_template]], so only relations we need for DB join and then to nested hash
|
165
|
+
# {:hardware => {:vm_or_template => {}}}
|
166
|
+
paths.map { |x| x[0..-2] }.select(&:present?).each { |x| nested_hashes_schema.store_path(x, {}) }
|
167
|
+
# Convert nested Hash to Rails friendly format, e.g. {:hardware => {:vm_or_template => {}}} will be
|
168
|
+
# transformed to [:hardware => :vm_or_template]
|
169
|
+
@rails_friendly_includes_schema = transform_hash_to_rails_friendly_array_recursive(nested_hashes_schema, [])
|
170
|
+
end
|
171
|
+
|
172
|
+
# @param hash [Hash] Nested hash representing join schema e.g. {:hardware => {:vm_or_template => {}}}
|
173
|
+
# @param current_layer [Array] One layer of the joins schema
|
174
|
+
# @return [Array] Transformed hash applicable for Rails .joins, e.g. [:hardware => [:vm_or_template]]
|
175
|
+
def transform_hash_to_rails_friendly_array_recursive(hash, current_layer)
|
176
|
+
array_attributes, hash_attributes = hash.partition { |_key, value| value.blank? }
|
177
|
+
|
178
|
+
array_attributes = array_attributes.map(&:first)
|
179
|
+
current_layer.concat(array_attributes)
|
180
|
+
# current_array.concat(array_attributes)
|
181
|
+
if hash_attributes.present?
|
182
|
+
last_hash_attr = hash_attributes.each_with_object({}) do |(key, value), obj|
|
183
|
+
obj[key] = transform_hash_to_rails_friendly_array_recursive(value, [])
|
184
|
+
end
|
185
|
+
current_layer << last_hash_attr
|
186
|
+
end
|
187
|
+
|
188
|
+
current_layer
|
189
|
+
end
|
190
|
+
|
191
|
+
# A recursive method for getting a schema out of full_reference (see #get_schema)
|
192
|
+
#
|
193
|
+
# @param attribute_names [Array<Symbol>] Array of attribute names
|
194
|
+
# @param arel_table [Arel::Table]
|
195
|
+
# @param data [Hash] The full reference layer
|
196
|
+
# @param schema [Hash] Recursively built schema
|
197
|
+
# @param path [Array] Recursively build path
|
198
|
+
# @param total_level [Integer] Guard for max recursive nesting
|
199
|
+
# @return [Hash] Recursively built schema
|
200
|
+
def get_schema_recursive(attribute_names, arel_table, data, schema, path, total_level)
|
201
|
+
raise "Nested too deep" if total_level > 100
|
202
|
+
|
203
|
+
attribute_names.each do |key|
|
204
|
+
new_path = path + [key]
|
205
|
+
|
206
|
+
value = data[key]
|
207
|
+
|
208
|
+
if inventory_object?(value)
|
209
|
+
get_schema_recursive(value.inventory_collection.manager_ref,
|
210
|
+
value.inventory_collection.model_class.arel_table,
|
211
|
+
value,
|
212
|
+
schema,
|
213
|
+
new_path,
|
214
|
+
total_level + 1)
|
215
|
+
elsif inventory_object_lazy?(value)
|
216
|
+
get_schema_recursive(value.inventory_collection.index_proxy.named_ref(value.ref),
|
217
|
+
value.inventory_collection.model_class.arel_table,
|
218
|
+
value.reference.full_reference,
|
219
|
+
schema,
|
220
|
+
new_path,
|
221
|
+
total_level + 1)
|
222
|
+
else
|
223
|
+
schema[new_path] = arel_table[key]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
schema
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns keys of the reference
|
231
|
+
#
|
232
|
+
# @return [Array] Keys of the reference
|
233
|
+
def index_references
|
234
|
+
Set.new(references[index_name].try(:keys) || [])
|
235
|
+
end
|
236
|
+
|
237
|
+
# Return a Rails relation that will be used to obtain the records we need to load from the DB
|
238
|
+
#
|
239
|
+
# @param rails_friendly_includes_schema [Array] Schema usable in .includes and .references methods of
|
240
|
+
# ActiveRecord relation object
|
241
|
+
# @param all_values [Array<Array>] nested array of values in format [[a1, b1], [a2, b2]] the nested array
|
242
|
+
# values must have the order of column_names
|
243
|
+
# @param projection [Array] A projection array resulting in Project operation (in Relation algebra terms)
|
244
|
+
# @return [ActiveRecord::AssociationRelation] relation object having filtered data
|
245
|
+
def db_relation(rails_friendly_includes_schema, all_values = nil, projection = nil)
|
246
|
+
relation = if !parent.nil? && !association.nil?
|
247
|
+
parent.send(association)
|
248
|
+
elsif !arel.nil?
|
249
|
+
arel
|
250
|
+
end
|
251
|
+
relation = relation.where(build_multi_selection_condition(all_values)) if relation && all_values
|
252
|
+
relation = relation.select(projection) if relation && projection
|
253
|
+
relation = relation.includes(rails_friendly_includes_schema).references(rails_friendly_includes_schema) if rails_friendly_includes_schema.present?
|
254
|
+
relation || model_class.none
|
255
|
+
end
|
256
|
+
|
257
|
+
# Takes ApplicationRecord record, converts it to the InventoryObject and places it to index
|
258
|
+
#
|
259
|
+
# @param record [ApplicationRecord] ApplicationRecord record we want to place to the index
|
260
|
+
def process_db_record!(record, paths)
|
261
|
+
# Important fact is that the path was added as .includes in the query, so this doesn't generate n+1 queries
|
262
|
+
index_value = InventoryRefresh::InventoryCollection::Reference.stringify_reference(
|
263
|
+
paths.map { |path| fetch_object_path(path, record) }
|
264
|
+
)
|
265
|
+
|
266
|
+
attributes = record.attributes.symbolize_keys
|
267
|
+
attribute_references.each do |ref|
|
268
|
+
# We need to fill all references that are relations, we will use a InventoryRefresh::ApplicationRecordReference which
|
269
|
+
# can be used for filling a relation and we don't need to do any query here.
|
270
|
+
# TODO(lsmola) maybe loading all, not just referenced here? Otherwise this will have issue for db_cache_all
|
271
|
+
# and find used in parser
|
272
|
+
# TODO(lsmola) the last usage of this should be lazy_find_by with :key specified, maybe we can get rid of this?
|
273
|
+
next unless (foreign_key = association_to_foreign_key_mapping[ref])
|
274
|
+
base_class_name = attributes[association_to_foreign_type_mapping[ref].try(:to_sym)] || association_to_base_class_mapping[ref]
|
275
|
+
id = attributes[foreign_key.to_sym]
|
276
|
+
attributes[ref] = InventoryRefresh::ApplicationRecordReference.new(base_class_name, id)
|
277
|
+
end
|
278
|
+
|
279
|
+
index[index_value] = new_inventory_object(attributes)
|
280
|
+
index[index_value].id = record.id
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require "inventory_refresh/inventory_collection/index/type/base"
|
2
|
+
require "active_support/core_ext/module/delegation"
|
3
|
+
|
4
|
+
module InventoryRefresh
|
5
|
+
class InventoryCollection
|
6
|
+
module Index
|
7
|
+
module Type
|
8
|
+
class Skeletal < InventoryRefresh::InventoryCollection::Index::Type::Base
|
9
|
+
# (see InventoryRefresh::InventoryCollection::Index::Type::Base#initialize)
|
10
|
+
# @param primary_index [InventoryRefresh::InventoryCollection::Index::Type::Data] Data index of primary_index
|
11
|
+
def initialize(inventory_collection, index_name, attribute_names, primary_index)
|
12
|
+
super
|
13
|
+
|
14
|
+
@primary_index = primary_index
|
15
|
+
end
|
16
|
+
|
17
|
+
delegate :default_values,
|
18
|
+
:new_inventory_object,
|
19
|
+
:named_ref,
|
20
|
+
:to => :inventory_collection
|
21
|
+
|
22
|
+
delegate :blank?,
|
23
|
+
:each,
|
24
|
+
:each_value,
|
25
|
+
:to => :index
|
26
|
+
|
27
|
+
# Find value based on index_value
|
28
|
+
#
|
29
|
+
# @param index_value [String] a index_value of the InventoryObject we search for
|
30
|
+
# @return [InventoryObject|nil] Returns found value or nil
|
31
|
+
def find(index_value)
|
32
|
+
index[index_value]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Deletes and returns the value on the index_value
|
36
|
+
#
|
37
|
+
# @param index_value [String] a index_value of the InventoryObject we search for
|
38
|
+
# @return [InventoryObject|nil] Returns found value or nil
|
39
|
+
def delete(index_value)
|
40
|
+
index.delete(index_value)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Takes value from primary_index and inserts it to skeletal index
|
44
|
+
#
|
45
|
+
# @param index_value [String] a index_value of the InventoryObject we search for
|
46
|
+
# @return [InventoryObject|nil] Returns found value or nil
|
47
|
+
def skeletonize_primary_index(index_value)
|
48
|
+
inventory_object = primary_index.delete(index_value)
|
49
|
+
return unless inventory_object
|
50
|
+
fill_versions!(inventory_object.data)
|
51
|
+
|
52
|
+
index[index_value] = inventory_object
|
53
|
+
end
|
54
|
+
|
55
|
+
# Builds index record with skeletal InventoryObject and returns it. Or it returns existing InventoryObject
|
56
|
+
# that is found in primary_index or skeletal_primary_index.
|
57
|
+
#
|
58
|
+
# @param attributes [Hash] Skeletal data of the index, must contain unique index keys and everything else
|
59
|
+
# needed for creating the record in the Database
|
60
|
+
# @return [InventoryObject] Returns built InventoryObject or existing InventoryObject with new attributes
|
61
|
+
# assigned
|
62
|
+
def build(attributes)
|
63
|
+
attributes = {}.merge!(default_values).merge!(attributes)
|
64
|
+
fill_versions!(attributes)
|
65
|
+
|
66
|
+
# If the primary index is already filled, we don't want populate skeletal index
|
67
|
+
uuid = ::InventoryRefresh::InventoryCollection::Reference.build_stringified_reference(attributes, named_ref)
|
68
|
+
if (inventory_object = primary_index.find(uuid))
|
69
|
+
return inventory_object.assign_attributes(attributes)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return if skeletal index already exists
|
73
|
+
if (inventory_object = index[uuid])
|
74
|
+
return inventory_object.assign_attributes(attributes)
|
75
|
+
end
|
76
|
+
|
77
|
+
# We want to populate a new skeletal index
|
78
|
+
inventory_object = new_inventory_object(attributes)
|
79
|
+
index[inventory_object.manager_uuid] = inventory_object
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
attr_reader :primary_index
|
85
|
+
|
86
|
+
# Add versions columns into the passed attributes
|
87
|
+
#
|
88
|
+
# @param attributes [Hash] Attributes we want to extend with version related attributes
|
89
|
+
def fill_versions!(attributes)
|
90
|
+
if inventory_collection.supports_resource_timestamps_max? && attributes[:resource_timestamp]
|
91
|
+
fill_specific_version_attr(:resource_timestamps, :resource_timestamp, attributes)
|
92
|
+
elsif inventory_collection.supports_resource_versions_max? && attributes[:resource_version]
|
93
|
+
fill_specific_version_attr(:resource_versions, :resource_version, attributes)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Add specific versions columns into the passed attributes
|
98
|
+
#
|
99
|
+
# @param partial_row_version_attr [Symbol] Attr name for partial rows, allowed values are
|
100
|
+
# [:resource_timestamps, :resource_versions]
|
101
|
+
# @param full_row_version_attr [Symbol] Attr name for full rows, allowed values are
|
102
|
+
# [:resource_timestamp, :resource_version]
|
103
|
+
# @param attributes [Hash] Attributes we want to extend with version related attributes
|
104
|
+
def fill_specific_version_attr(partial_row_version_attr, full_row_version_attr, attributes)
|
105
|
+
# We have to symbolize, since serializing persistor makes these strings
|
106
|
+
(attributes[partial_row_version_attr] ||= {}).symbolize_keys!
|
107
|
+
|
108
|
+
(attributes.keys - inventory_collection.base_columns).each do |key|
|
109
|
+
attributes[partial_row_version_attr][key] ||= attributes[full_row_version_attr]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|