inventory_refresh 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|