inventory_refresh 0.1.3 → 0.2.0

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