inventory_refresh 0.1.3 → 0.2.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/.gitignore +6 -0
- data/Gemfile +4 -0
- data/bundler.d/.gitkeep +0 -0
- data/inventory_refresh.gemspec +4 -4
- data/lib/inventory_refresh/inventory_collection/builder.rb +249 -0
- data/lib/inventory_refresh/inventory_collection/graph.rb +0 -15
- data/lib/inventory_refresh/inventory_collection/helpers/associations_helper.rb +80 -0
- data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +456 -0
- data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +132 -0
- data/lib/inventory_refresh/inventory_collection/helpers.rb +6 -0
- data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +5 -5
- data/lib/inventory_refresh/inventory_collection/reference.rb +4 -0
- data/lib/inventory_refresh/inventory_collection/scanner.rb +111 -18
- data/lib/inventory_refresh/inventory_collection/serialization.rb +7 -7
- data/lib/inventory_refresh/inventory_collection/unconnected_edge.rb +19 -0
- data/lib/inventory_refresh/inventory_collection.rb +114 -649
- data/lib/inventory_refresh/inventory_object.rb +17 -11
- data/lib/inventory_refresh/inventory_object_lazy.rb +20 -10
- data/lib/inventory_refresh/persister.rb +212 -0
- data/lib/inventory_refresh/save_collection/base.rb +18 -3
- data/lib/inventory_refresh/save_collection/saver/base.rb +25 -62
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +73 -225
- data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +226 -0
- data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +115 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +122 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +24 -5
- data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +6 -6
- data/lib/inventory_refresh/save_collection/sweeper.rb +69 -0
- data/lib/inventory_refresh/save_inventory.rb +18 -8
- data/lib/inventory_refresh/target_collection.rb +12 -0
- data/lib/inventory_refresh/version.rb +1 -1
- data/lib/inventory_refresh.rb +1 -0
- metadata +24 -15
- data/lib/inventory_refresh/save_collection/recursive.rb +0 -52
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3265443d41b8a0158121da387b749145f05c55ff5b1a579dc8dfd84fca5b4c77
|
4
|
+
data.tar.gz: 7a740afeebf95a4f204c37cca426eee592360c27e6636ee0471604f7cdd9aaf9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6c8d595edc1cf199628804d4fd4a52234b0cb5b6194a2f80ab6f173775c1e3377ec959b628d67c38bb68b9b062a1c7b488554b1143f68a2f8a665db1134e1be
|
7
|
+
data.tar.gz: '065609f61113e2beef237730befdf7a79f1f4fb104baedfc6fb639b3d6c45156b0ead76aea9550848ecb337bb0494b590d7760ea9a413262321c18621b2a40c6'
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -4,3 +4,7 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in inventory_refresh.gemspec
|
6
6
|
gemspec
|
7
|
+
|
8
|
+
# Load other additional Gemfiles
|
9
|
+
# Developers can create a file ending in .rb under bundler.d/ to specify additional development dependencies
|
10
|
+
Dir.glob(File.join(__dir__, 'bundler.d/*.rb')).each { |f| eval_gemfile(File.expand_path(f, __dir__)) }
|
data/bundler.d/.gitkeep
ADDED
File without changes
|
data/inventory_refresh.gemspec
CHANGED
@@ -22,13 +22,13 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
-
spec.add_dependency "activerecord", "~> 5.0
|
25
|
+
spec.add_dependency "activerecord", "~> 5.0"
|
26
26
|
spec.add_dependency "more_core_extensions", "~> 3.5"
|
27
|
-
spec.add_dependency "pg"
|
27
|
+
spec.add_dependency "pg", "> 0"
|
28
28
|
|
29
29
|
spec.add_development_dependency "ancestry"
|
30
|
-
spec.add_development_dependency "bundler"
|
31
|
-
spec.add_development_dependency "
|
30
|
+
spec.add_development_dependency "bundler", "~> 2.0.1"
|
31
|
+
spec.add_development_dependency "factory_bot", "~> 4.11.1"
|
32
32
|
spec.add_development_dependency "rake", "~> 10.0"
|
33
33
|
spec.add_development_dependency "rspec", "~> 3.0"
|
34
34
|
spec.add_development_dependency "simplecov"
|
@@ -0,0 +1,249 @@
|
|
1
|
+
module InventoryRefresh
|
2
|
+
class InventoryCollection
|
3
|
+
class Builder
|
4
|
+
class MissingModelClassError < StandardError; end
|
5
|
+
|
6
|
+
def self.allowed_properties
|
7
|
+
%i(all_manager_uuids arel association
|
8
|
+
attributes_blacklist attributes_whitelist batch_extra_attributes
|
9
|
+
complete create_only custom_save_block
|
10
|
+
custom_reconnect_block default_values delete_method
|
11
|
+
dependency_attributes check_changed inventory_object_attributes
|
12
|
+
manager_ref manager_ref_allowed_nil manager_uuids
|
13
|
+
model_class name parent
|
14
|
+
parent_inventory_collections retention_strategy strategy
|
15
|
+
saver_strategy secondary_refs targeted
|
16
|
+
targeted_arel update_only use_ar_object
|
17
|
+
assert_graph_integrity).to_set
|
18
|
+
end
|
19
|
+
|
20
|
+
def allowed_properties
|
21
|
+
@allowed_properties ||= self.class.allowed_properties
|
22
|
+
end
|
23
|
+
|
24
|
+
# Default options for builder
|
25
|
+
# :adv_settings
|
26
|
+
# - values from Advanced settings (doesn't overwrite values specified in code)
|
27
|
+
# - @see method ManageIQ::Providers::Inventory::Persister.make_builder_settings()
|
28
|
+
# :shared_properties
|
29
|
+
# - any properties applied if missing (not explicitly specified)
|
30
|
+
def self.default_options
|
31
|
+
{
|
32
|
+
:shared_properties => {},
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Entry point
|
37
|
+
# Creates builder and builds data for inventory collection
|
38
|
+
# @param name [Symbol, Array] InventoryCollection.association value. <name> method not called when Array
|
39
|
+
# (optional) method with this name also used for concrete inventory collection specific properties
|
40
|
+
# @param persister_class [Class] used for "guessing" model_class
|
41
|
+
# @param options [Hash]
|
42
|
+
def self.prepare_data(name, persister_class, options = {})
|
43
|
+
options = default_options.merge(options)
|
44
|
+
builder = new(name, persister_class, options)
|
45
|
+
builder.construct_data
|
46
|
+
|
47
|
+
yield(builder) if block_given?
|
48
|
+
|
49
|
+
builder
|
50
|
+
end
|
51
|
+
|
52
|
+
# @see prepare_data()
|
53
|
+
def initialize(name, persister_class, options = self.class.default_options)
|
54
|
+
@name = name
|
55
|
+
@persister_class = persister_class
|
56
|
+
|
57
|
+
@properties = {}
|
58
|
+
@inventory_object_attributes = []
|
59
|
+
@default_values = {}
|
60
|
+
@dependency_attributes = {}
|
61
|
+
|
62
|
+
@options = options
|
63
|
+
skip_auto_inventory_attributes(false) if @options[:auto_inventory_attributes].nil?
|
64
|
+
skip_model_class(false) if @options[:without_model_class].nil?
|
65
|
+
|
66
|
+
@shared_properties = options[:shared_properties] # From persister
|
67
|
+
end
|
68
|
+
|
69
|
+
# Builds data for InventoryCollection
|
70
|
+
# Calls method @name (if exists) with specific properties
|
71
|
+
# Yields for overwriting provider-specific properties
|
72
|
+
def construct_data
|
73
|
+
add_properties({:association => @name}, :if_missing)
|
74
|
+
|
75
|
+
add_properties(@shared_properties, :if_missing)
|
76
|
+
|
77
|
+
send(@name.to_sym) if @name.respond_to?(:to_sym) && respond_to?(@name.to_sym)
|
78
|
+
|
79
|
+
if @properties[:model_class].nil?
|
80
|
+
add_properties(:model_class => auto_model_class) unless @options[:without_model_class]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Creates InventoryCollection
|
85
|
+
def to_inventory_collection
|
86
|
+
if @properties[:model_class].nil? && !@options[:without_model_class]
|
87
|
+
raise MissingModelClassError, "Missing model_class for :#{@name} (\"#{@name.to_s.classify}\" or subclass expected)."
|
88
|
+
end
|
89
|
+
|
90
|
+
::InventoryRefresh::InventoryCollection.new(to_hash)
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Missing method
|
95
|
+
# - add_some_property(value)
|
96
|
+
# converted to:
|
97
|
+
# - add_properties(:some_property => value)
|
98
|
+
#
|
99
|
+
def method_missing(method_name, *arguments, &block)
|
100
|
+
if method_name.to_s.starts_with?('add_')
|
101
|
+
add_properties(
|
102
|
+
method_name.to_s.gsub('add_', '').to_sym => arguments[0]
|
103
|
+
)
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def respond_to_missing?(method_name, _include_private = false)
|
110
|
+
method_name.to_s.starts_with?('add_')
|
111
|
+
end
|
112
|
+
|
113
|
+
# Merges @properties
|
114
|
+
# @see ManagerRefresh::InventoryCollection.initialize for list of properties
|
115
|
+
#
|
116
|
+
# @param props [Hash]
|
117
|
+
# @param mode [Symbol] :overwrite | :if_missing
|
118
|
+
def add_properties(props = {}, mode = :overwrite)
|
119
|
+
props.each_key { |property_name| assert_allowed_property(property_name) }
|
120
|
+
|
121
|
+
@properties = merge_hashes(@properties, props, mode)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Adds inventory object attributes (part of @properties)
|
125
|
+
def add_inventory_attributes(array)
|
126
|
+
@inventory_object_attributes += (array || [])
|
127
|
+
end
|
128
|
+
|
129
|
+
# Removes specified inventory object attributes
|
130
|
+
def remove_inventory_attributes(array)
|
131
|
+
@inventory_object_attributes -= (array || [])
|
132
|
+
end
|
133
|
+
|
134
|
+
# Clears all inventory object attributes
|
135
|
+
def clear_inventory_attributes!
|
136
|
+
@options[:auto_inventory_attributes] = false
|
137
|
+
@inventory_object_attributes = []
|
138
|
+
end
|
139
|
+
|
140
|
+
# Adds key/values to default values (InventoryCollection.default_values) (part of @properties)
|
141
|
+
def add_default_values(params = {}, mode = :overwrite)
|
142
|
+
@default_values = merge_hashes(@default_values, params, mode)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Evaluates lambda blocks
|
146
|
+
def evaluate_lambdas!(persister)
|
147
|
+
@default_values = evaluate_lambdas_on(@default_values, persister)
|
148
|
+
@dependency_attributes = evaluate_lambdas_on(@dependency_attributes, persister)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Adds key/values to dependency_attributes (part of @properties)
|
152
|
+
def add_dependency_attributes(attrs = {}, mode = :overwrite)
|
153
|
+
@dependency_attributes = merge_hashes(@dependency_attributes, attrs, mode)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Deletes key from dependency_attributes
|
157
|
+
def remove_dependency_attributes(key)
|
158
|
+
@dependency_attributes.delete(key)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns whole InventoryCollection properties
|
162
|
+
def to_hash
|
163
|
+
add_inventory_attributes(auto_inventory_attributes) if @options[:auto_inventory_attributes]
|
164
|
+
|
165
|
+
@properties[:inventory_object_attributes] ||= @inventory_object_attributes
|
166
|
+
|
167
|
+
@properties[:default_values] ||= {}
|
168
|
+
@properties[:default_values].merge!(@default_values)
|
169
|
+
|
170
|
+
@properties[:dependency_attributes] ||= {}
|
171
|
+
@properties[:dependency_attributes].merge!(@dependency_attributes)
|
172
|
+
|
173
|
+
@properties
|
174
|
+
end
|
175
|
+
|
176
|
+
protected
|
177
|
+
|
178
|
+
def assert_allowed_property(name)
|
179
|
+
unless allowed_properties.include?(name)
|
180
|
+
raise "InventoryCollection property :#{name} is not allowed. Allowed properties are:\n#{self.allowed_properties.to_a.map(&:to_s).join(', ')}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Extends source hash with
|
185
|
+
# - a) all keys from dest (overwrite mode)
|
186
|
+
# - b) missing keys (missing mode)
|
187
|
+
#
|
188
|
+
# @param mode [Symbol] :overwrite | :if_missing
|
189
|
+
def merge_hashes(source, dest, mode)
|
190
|
+
return source if source.nil? || dest.nil?
|
191
|
+
|
192
|
+
if mode == :overwrite
|
193
|
+
source.merge(dest)
|
194
|
+
else
|
195
|
+
dest.merge(source)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Derives model_class from @name
|
200
|
+
# Can be disabled by options :without_model_class => true
|
201
|
+
# @return [Class | nil] when class doesn't exist, returns nil
|
202
|
+
def auto_model_class
|
203
|
+
"::#{@name.to_s.classify}".safe_constantize
|
204
|
+
end
|
205
|
+
|
206
|
+
# Enables/disables auto_model_class and exception check
|
207
|
+
# @param skip [Boolean]
|
208
|
+
def skip_model_class(skip = true)
|
209
|
+
@options[:without_model_class] = skip
|
210
|
+
end
|
211
|
+
|
212
|
+
# Inventory object attributes are derived from setters
|
213
|
+
#
|
214
|
+
# Can be disabled by options :auto_inventory_attributes => false
|
215
|
+
# - attributes can be manually set via method add_inventory_attributes()
|
216
|
+
def auto_inventory_attributes
|
217
|
+
return if @properties[:model_class].nil?
|
218
|
+
|
219
|
+
(@properties[:model_class].new.methods - ar_base_class.methods).grep(/^[\w]+?\=$/).collect do |setter|
|
220
|
+
setter.to_s[0..setter.length - 2].to_sym
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# used for ignoring unrelated auto_inventory_attributes
|
225
|
+
def ar_base_class
|
226
|
+
ActiveRecord::Base
|
227
|
+
end
|
228
|
+
|
229
|
+
# Enables/disables auto_inventory_attributes
|
230
|
+
# @param skip [Boolean]
|
231
|
+
def skip_auto_inventory_attributes(skip = true)
|
232
|
+
@options[:auto_inventory_attributes] = !skip
|
233
|
+
end
|
234
|
+
|
235
|
+
# Evaluates lambda blocks in @default_values and @dependency_attributes
|
236
|
+
# @param values [Hash]
|
237
|
+
# @param persister [ManageIQ::Providers::Inventory::Persister]
|
238
|
+
def evaluate_lambdas_on(values, persister)
|
239
|
+
values&.transform_values do |value|
|
240
|
+
if value.respond_to?(:call)
|
241
|
+
value.call(persister)
|
242
|
+
else
|
243
|
+
value
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
@@ -52,26 +52,11 @@ module InventoryRefresh
|
|
52
52
|
# depth 10. We should throw a warning maybe asking for simplifying the interconnections in the models.
|
53
53
|
assert_graph!(edges)
|
54
54
|
|
55
|
-
self.nodes = sort_nodes(nodes)
|
56
|
-
|
57
55
|
self
|
58
56
|
end
|
59
57
|
|
60
58
|
private
|
61
59
|
|
62
|
-
# Sort the nodes putting child nodes as last. Child nodes are InventoryCollection objects having
|
63
|
-
# :parent_inventory_collections definition.
|
64
|
-
#
|
65
|
-
# @param nodes [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
|
66
|
-
# @return [Array<InventoryRefresh::InventoryCollection>] Sorted list of Inventory collection nodes
|
67
|
-
def sort_nodes(nodes)
|
68
|
-
# Separate to root nodes and child nodes, where child nodes are determined by having parent_inventory_collections
|
69
|
-
root_nodes, child_nodes = nodes.partition { |node| node.parent_inventory_collections.blank? }
|
70
|
-
# And order it that root nodes comes first, that way the disconnect of the child nodes should be always done as
|
71
|
-
# a part of the root nodes, as intended.
|
72
|
-
root_nodes + child_nodes
|
73
|
-
end
|
74
|
-
|
75
60
|
# Asserts all nodes are of class ::InventoryRefresh::InventoryCollection
|
76
61
|
#
|
77
62
|
# @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative "../helpers"
|
2
|
+
|
3
|
+
module InventoryRefresh
|
4
|
+
class InventoryCollection
|
5
|
+
module Helpers
|
6
|
+
module AssociationsHelper
|
7
|
+
# @return [Array<ActiveRecord::Reflection::BelongsToReflection">] All belongs_to associations
|
8
|
+
def belongs_to_associations
|
9
|
+
model_class.reflect_on_all_associations.select { |x| x.kind_of?(ActiveRecord::Reflection::BelongsToReflection) }
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Hash{Symbol => String}] Hash with association name mapped to foreign key column name
|
13
|
+
def association_to_foreign_key_mapping
|
14
|
+
return {} unless model_class
|
15
|
+
|
16
|
+
@association_to_foreign_key_mapping ||= belongs_to_associations.each_with_object({}) do |x, obj|
|
17
|
+
obj[x.name] = x.foreign_key
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Hash{String => Hash}] Hash with foreign_key column name mapped to association name
|
22
|
+
def foreign_key_to_association_mapping
|
23
|
+
return {} unless model_class
|
24
|
+
|
25
|
+
@foreign_key_to_association_mapping ||= belongs_to_associations.each_with_object({}) do |x, obj|
|
26
|
+
obj[x.foreign_key] = x.name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Hash{Symbol => String}] Hash with association name mapped to polymorphic foreign key type column name
|
31
|
+
def association_to_foreign_type_mapping
|
32
|
+
return {} unless model_class
|
33
|
+
|
34
|
+
@association_to_foreign_type_mapping ||= model_class.reflect_on_all_associations.each_with_object({}) do |x, obj|
|
35
|
+
obj[x.name] = x.foreign_type if x.polymorphic?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Hash{Symbol => String}] Hash with polymorphic foreign key type column name mapped to association name
|
40
|
+
def foreign_type_to_association_mapping
|
41
|
+
return {} unless model_class
|
42
|
+
|
43
|
+
@foreign_type_to_association_mapping ||= model_class.reflect_on_all_associations.each_with_object({}) do |x, obj|
|
44
|
+
obj[x.foreign_type] = x.name if x.polymorphic?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Hash{Symbol => String}] Hash with association name mapped to base class of the association
|
49
|
+
def association_to_base_class_mapping
|
50
|
+
return {} unless model_class
|
51
|
+
|
52
|
+
@association_to_base_class_mapping ||= model_class.reflect_on_all_associations.each_with_object({}) do |x, obj|
|
53
|
+
obj[x.name] = x.klass.base_class.name unless x.polymorphic?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Array<Symbol>] List of all column names that are foreign keys
|
58
|
+
def foreign_keys
|
59
|
+
return [] unless model_class
|
60
|
+
|
61
|
+
@foreign_keys_cache ||= belongs_to_associations.map(&:foreign_key).map!(&:to_sym)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Array<Symbol>] List of all column names that are foreign keys and cannot removed, otherwise we couldn't
|
65
|
+
# save the record
|
66
|
+
def fixed_foreign_keys
|
67
|
+
# Foreign keys that are part of a manager_ref must be present, otherwise the record would get lost. This is a
|
68
|
+
# minimum check we can do to not break a referential integrity.
|
69
|
+
return @fixed_foreign_keys_cache unless @fixed_foreign_keys_cache.nil?
|
70
|
+
|
71
|
+
manager_ref_set = (manager_ref - manager_ref_allowed_nil)
|
72
|
+
@fixed_foreign_keys_cache = manager_ref_set.map { |x| association_to_foreign_key_mapping[x] }.compact
|
73
|
+
@fixed_foreign_keys_cache += foreign_keys & manager_ref
|
74
|
+
@fixed_foreign_keys_cache.map!(&:to_sym)
|
75
|
+
@fixed_foreign_keys_cache
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|