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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/Gemfile +4 -0
  4. data/bundler.d/.gitkeep +0 -0
  5. data/inventory_refresh.gemspec +4 -4
  6. data/lib/inventory_refresh/inventory_collection/builder.rb +249 -0
  7. data/lib/inventory_refresh/inventory_collection/graph.rb +0 -15
  8. data/lib/inventory_refresh/inventory_collection/helpers/associations_helper.rb +80 -0
  9. data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +456 -0
  10. data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +132 -0
  11. data/lib/inventory_refresh/inventory_collection/helpers.rb +6 -0
  12. data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +5 -5
  13. data/lib/inventory_refresh/inventory_collection/reference.rb +4 -0
  14. data/lib/inventory_refresh/inventory_collection/scanner.rb +111 -18
  15. data/lib/inventory_refresh/inventory_collection/serialization.rb +7 -7
  16. data/lib/inventory_refresh/inventory_collection/unconnected_edge.rb +19 -0
  17. data/lib/inventory_refresh/inventory_collection.rb +114 -649
  18. data/lib/inventory_refresh/inventory_object.rb +17 -11
  19. data/lib/inventory_refresh/inventory_object_lazy.rb +20 -10
  20. data/lib/inventory_refresh/persister.rb +212 -0
  21. data/lib/inventory_refresh/save_collection/base.rb +18 -3
  22. data/lib/inventory_refresh/save_collection/saver/base.rb +25 -62
  23. data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +73 -225
  24. data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +226 -0
  25. data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +115 -0
  26. data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +122 -0
  27. data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +24 -5
  28. data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +6 -6
  29. data/lib/inventory_refresh/save_collection/sweeper.rb +69 -0
  30. data/lib/inventory_refresh/save_inventory.rb +18 -8
  31. data/lib/inventory_refresh/target_collection.rb +12 -0
  32. data/lib/inventory_refresh/version.rb +1 -1
  33. data/lib/inventory_refresh.rb +1 -0
  34. metadata +24 -15
  35. data/lib/inventory_refresh/save_collection/recursive.rb +0 -52
  36. 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: 626a8e6090e589367699c61e7e6c0a72de3cb836b1f08c5c83ba205a5c5ef20f
4
- data.tar.gz: 0e360910809bc50a3f36fadb597ab5543e9c6e13ce4e5cb520ec517ba7397d11
3
+ metadata.gz: 3265443d41b8a0158121da387b749145f05c55ff5b1a579dc8dfd84fca5b4c77
4
+ data.tar.gz: 7a740afeebf95a4f204c37cca426eee592360c27e6636ee0471604f7cdd9aaf9
5
5
  SHA512:
6
- metadata.gz: '08480055a3d389a312b14521cadf86096ccf1a20a1d964081ba9d2e8c22e6e9a884147fd82fc677c02ee84d789cf2238cc2f23028c7147569c1aedeb2391a020'
7
- data.tar.gz: 4aabdcba7bf3532f3141e78aa4b7ba34f4dac2082d16e685522c842e8ffbf033923c04b57f89182b405a2e4f3ea18ef3185e2c0a359be8c16db58dc8568f8a1c
6
+ metadata.gz: d6c8d595edc1cf199628804d4fd4a52234b0cb5b6194a2f80ab6f173775c1e3377ec959b628d67c38bb68b9b062a1c7b488554b1143f68a2f8a665db1134e1be
7
+ data.tar.gz: '065609f61113e2beef237730befdf7a79f1f4fb104baedfc6fb639b3d6c45156b0ead76aea9550848ecb337bb0494b590d7760ea9a413262321c18621b2a40c6'
data/.gitignore CHANGED
@@ -10,5 +10,11 @@
10
10
 
11
11
  /Gemfile.lock
12
12
 
13
+ # ignore included plugins in bundler.d
14
+ bundler.d/*
15
+
13
16
  # rspec failure tracking
14
17
  .rspec_status
18
+
19
+ # generated rubocop file
20
+ .rubocop-https*
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__)) }
File without changes
@@ -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.6"
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 "factory_girl", "~> 4.5.0"
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