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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +47 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +4 -0
  5. data/.rspec_ci +4 -0
  6. data/.rubocop.yml +4 -0
  7. data/.rubocop_cc.yml +5 -0
  8. data/.rubocop_local.yml +2 -0
  9. data/.travis.yml +12 -0
  10. data/.yamllint +12 -0
  11. data/CHANGELOG.md +0 -0
  12. data/Gemfile +6 -0
  13. data/LICENSE +202 -0
  14. data/README.md +35 -0
  15. data/Rakefile +47 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +8 -0
  18. data/inventory_refresh.gemspec +34 -0
  19. data/lib/inventory_refresh.rb +11 -0
  20. data/lib/inventory_refresh/application_record_iterator.rb +56 -0
  21. data/lib/inventory_refresh/application_record_reference.rb +15 -0
  22. data/lib/inventory_refresh/graph.rb +157 -0
  23. data/lib/inventory_refresh/graph/topological_sort.rb +66 -0
  24. data/lib/inventory_refresh/inventory_collection.rb +1175 -0
  25. data/lib/inventory_refresh/inventory_collection/data_storage.rb +178 -0
  26. data/lib/inventory_refresh/inventory_collection/graph.rb +170 -0
  27. data/lib/inventory_refresh/inventory_collection/index/proxy.rb +230 -0
  28. data/lib/inventory_refresh/inventory_collection/index/type/base.rb +80 -0
  29. data/lib/inventory_refresh/inventory_collection/index/type/data.rb +26 -0
  30. data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +286 -0
  31. data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +116 -0
  32. data/lib/inventory_refresh/inventory_collection/reference.rb +96 -0
  33. data/lib/inventory_refresh/inventory_collection/references_storage.rb +106 -0
  34. data/lib/inventory_refresh/inventory_collection/scanner.rb +117 -0
  35. data/lib/inventory_refresh/inventory_collection/serialization.rb +140 -0
  36. data/lib/inventory_refresh/inventory_object.rb +303 -0
  37. data/lib/inventory_refresh/inventory_object_lazy.rb +151 -0
  38. data/lib/inventory_refresh/save_collection/base.rb +38 -0
  39. data/lib/inventory_refresh/save_collection/recursive.rb +52 -0
  40. data/lib/inventory_refresh/save_collection/saver/base.rb +390 -0
  41. data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
  42. data/lib/inventory_refresh/save_collection/saver/concurrent_safe.rb +71 -0
  43. data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +632 -0
  44. data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
  45. data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +85 -0
  46. data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +120 -0
  47. data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +196 -0
  48. data/lib/inventory_refresh/save_collection/topological_sort.rb +38 -0
  49. data/lib/inventory_refresh/save_inventory.rb +38 -0
  50. data/lib/inventory_refresh/target.rb +73 -0
  51. data/lib/inventory_refresh/target_collection.rb +80 -0
  52. data/lib/inventory_refresh/version.rb +3 -0
  53. data/tools/ci/create_db_user.sh +3 -0
  54. metadata +207 -0
@@ -0,0 +1,178 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module InventoryRefresh
4
+ class InventoryCollection
5
+ class DataStorage
6
+ # @return [Array<InventoryObject>] objects of the InventoryCollection in an Array
7
+ attr_accessor :data
8
+
9
+ attr_reader :index_proxy, :inventory_collection
10
+
11
+ delegate :each, :size, :to => :data
12
+
13
+ delegate :primary_index,
14
+ :build_primary_index_for,
15
+ :build_secondary_indexes_for,
16
+ :named_ref,
17
+ :skeletal_primary_index,
18
+ :to => :index_proxy
19
+
20
+ delegate :association_to_foreign_key_mapping,
21
+ :default_values,
22
+ :inventory_object?,
23
+ :inventory_object_lazy?,
24
+ :manager_ref,
25
+ :new_inventory_object,
26
+ :to => :inventory_collection
27
+
28
+ # @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object we want the storage
29
+ # for
30
+ # @param secondary_refs [Hash] Secondary_refs in format {:name_of_the_ref => [:attribute1, :attribute2]}
31
+ def initialize(inventory_collection, secondary_refs)
32
+ @inventory_collection = inventory_collection
33
+ @data = []
34
+
35
+ @index_proxy = InventoryRefresh::InventoryCollection::Index::Proxy.new(inventory_collection, secondary_refs)
36
+ end
37
+
38
+ # Adds passed InventoryObject into the InventoryCollection's storage
39
+ #
40
+ # @param inventory_object [InventoryRefresh::InventoryObject]
41
+ # @return [InventoryRefresh::InventoryCollection] Returns current InventoryCollection, to allow chaining
42
+ def <<(inventory_object)
43
+ if inventory_object.manager_uuid.present? && !primary_index.find(inventory_object.manager_uuid)
44
+ data << inventory_object
45
+
46
+ # TODO(lsmola) Maybe we do not need the secondary indexes here?
47
+ # Maybe we should index it like LocalDb indexes, on demand, and storing what was
48
+ # indexed? Maybe we should allow only lazy access and no direct find from a parser. Since for streaming
49
+ # refresh, things won't be parsed together and no full state will be taken.
50
+ build_primary_index_for(inventory_object)
51
+ build_secondary_indexes_for(inventory_object)
52
+ end
53
+ inventory_collection
54
+ end
55
+
56
+ alias push <<
57
+
58
+ # Finds of builds a new InventoryObject. By building it, we also put in into the InventoryCollection's storage.
59
+ #
60
+ # @param manager_uuid [String] manager_uuid of the InventoryObject
61
+ # @return [InventoryRefresh::InventoryObject] Found or built InventoryObject
62
+ def find_or_build(manager_uuid)
63
+ raise "The uuid consists of #{manager_ref.size} attributes, please find_or_build_by method" if manager_ref.size > 1
64
+
65
+ find_or_build_by(manager_ref.first => manager_uuid)
66
+ end
67
+
68
+ # (see #build)
69
+ def find_or_build_by(hash)
70
+ build(hash)
71
+ end
72
+
73
+ # Finds InventoryObject.
74
+ #
75
+ # @param hash [Hash] Hash that needs to contain attributes defined in :manager_ref of the InventoryCollection
76
+ # @return [InventoryRefresh::InventoryObject] Found or built InventoryObject object
77
+ def find_in_data(hash)
78
+ _hash, _uuid, inventory_object = primary_index_scan(hash)
79
+ inventory_object
80
+ end
81
+
82
+ # Finds of builds a new InventoryObject. By building it, we also put in into the InventoryCollection's storage.
83
+ #
84
+ # @param hash [Hash] Hash that needs to contain attributes defined in :manager_ref of the
85
+ # InventoryCollection
86
+ # @return [InventoryRefresh::InventoryObject] Found or built InventoryObject object
87
+ def build(hash)
88
+ hash, uuid, inventory_object = primary_index_scan(hash)
89
+
90
+ # Return InventoryObject if found in primary index
91
+ return inventory_object unless inventory_object.nil?
92
+
93
+ # We will take existing skeletal record, so we don't duplicate references for saving. We can have duplicated
94
+ # reference from local_db index, (if we are using .find in parser, that causes N+1 db queries), but that is ok,
95
+ # since that one is not being saved.
96
+ inventory_object = skeletal_primary_index.delete(uuid)
97
+
98
+ # We want to update the skeletal record with actual data
99
+ inventory_object&.assign_attributes(hash)
100
+
101
+ # Build the InventoryObject
102
+ inventory_object ||= new_inventory_object(enrich_data(hash))
103
+
104
+ # Store new InventoryObject and return it
105
+ push(inventory_object)
106
+ inventory_object
107
+ end
108
+
109
+ # Finds of builds a new InventoryObject with incomplete data.
110
+ #
111
+ # @param hash [Hash] Hash that needs to contain attributes defined in :manager_ref of the
112
+ # InventoryCollection
113
+ # @return [InventoryRefresh::InventoryObject] Found or built InventoryObject object
114
+ def build_partial(hash)
115
+ skeletal_primary_index.build(hash)
116
+ end
117
+
118
+ # Returns array of built InventoryObject objects
119
+ #
120
+ # @return [Array<InventoryRefresh::InventoryObject>] Array of built InventoryObject objects
121
+ def to_a
122
+ data
123
+ end
124
+
125
+ def to_hash
126
+ InventoryRefresh::InventoryCollection::Serialization.new(inventory_collection).to_hash
127
+ end
128
+
129
+ def from_hash(inventory_objects_data, available_inventory_collections)
130
+ InventoryRefresh::InventoryCollection::Serialization
131
+ .new(inventory_collection)
132
+ .from_hash(inventory_objects_data, available_inventory_collections)
133
+ end
134
+
135
+ private
136
+
137
+ # Scans primary index for existing InventoryObject, that would be equivalent to passed hash. It also returns
138
+ # enriched data and uuid, so we do not have to compute it multiple times.
139
+ #
140
+ # @param hash [Hash] Attributes for the InventoryObject
141
+ # @return [Array(Hash, String, InventoryRefresh::InventoryObject)] Returns enriched data, uuid and InventoryObject
142
+ # if found (otherwise nil)
143
+ def primary_index_scan(hash)
144
+ hash = enrich_data(hash)
145
+
146
+ assert_all_keys_present(hash)
147
+ assert_only_primary_index(hash)
148
+
149
+ uuid = ::InventoryRefresh::InventoryCollection::Reference.build_stringified_reference(hash, named_ref)
150
+ return hash, uuid, primary_index.find(uuid)
151
+ end
152
+
153
+ def assert_all_keys_present(hash)
154
+ if manager_ref.any? { |x| !hash.key?(x) }
155
+ raise "Needed find_or_build_by keys are: #{manager_ref}, data provided: #{hash}"
156
+ end
157
+ end
158
+
159
+ def assert_only_primary_index(data)
160
+ named_ref.each do |key|
161
+ if data[key].kind_of?(InventoryRefresh::InventoryObjectLazy) && !data[key].primary?
162
+ raise "Wrong index for key :#{key}, all references under this index must point to default :ref called"\
163
+ " :manager_ref. Any other :ref is not valid. This applies also to nested lazy links."
164
+ end
165
+ end
166
+ end
167
+
168
+ # Returns new hash enriched by (see InventoryRefresh::InventoryCollection#default_values) hash
169
+ #
170
+ # @param hash [Hash] Input hash
171
+ # @return [Hash] Enriched hash by (see InventoryRefresh::InventoryCollection#default_values)
172
+ def enrich_data(hash)
173
+ # This is 25% faster than default_values.merge(hash)
174
+ {}.merge!(default_values).merge!(hash)
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,170 @@
1
+ module InventoryRefresh
2
+ class InventoryCollection
3
+ class Graph < ::InventoryRefresh::Graph
4
+ # @param nodes [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
5
+ def initialize(nodes)
6
+ super(nodes)
7
+
8
+ assert_inventory_collections(nodes)
9
+ end
10
+
11
+ # Turns the graph into DAG (Directed Acyclic Graph)
12
+ #
13
+ # @return [InventoryRefresh::InventoryCollection::Graph] Graph object modified into DAG
14
+ def build_directed_acyclic_graph!
15
+ ################################################################################################################
16
+ ## Description of an algorithm for building DAG
17
+ ################################################################################################################
18
+ # Terms:
19
+ ##############
20
+ # Fixed Edges - Are edges that cannot be removed from Graph, in our case these are edges caused by attributes
21
+ # that has to be present before saving the record. These are attributes that are part of the
22
+ # record identity (unique index of the DB record) and attributes validated for presence.
23
+ # Feedback Edge Set - Is a set of edges that are causing a cycle in the graph
24
+ # DAG - Directed Acyclic Graph
25
+ #
26
+ # Alghoritm:
27
+ ##############
28
+ # Building a Feedback Edge Set:
29
+ # We will be building DAG from a Graph containing cycles, the algorithm is building a Feedback Edge Set by
30
+ # adding edges to a DAG made from Fixed Edges, while checking if by adding a new edge we haven't created
31
+ # a cycle in the graph.
32
+ # Converting original graph to DAG:
33
+ # Using the Feedback Edge Set, we remove the attributes causing cycles from the original graph. This way, we
34
+ # get a DAG, but the DAG is missing attributes.
35
+ # Using the Feedback Edge Set we create new Nodes, containing only removed attributes in a step before. These
36
+ # nodes will be attached to Graph according to their dependencies. By saving these nodes, we will add the
37
+ # missing relations.
38
+ ################################################################################################################
39
+
40
+ # Assert that Fixed edges do not have a cycle, we cannot move with fixed edges, so exception is thrown here
41
+ assert_graph!(fixed_edges)
42
+
43
+ # Collect Feedback Edge (Arc) Set
44
+ feedback_edge_set = build_feedback_edge_set(edges, fixed_edges)
45
+
46
+ # We will build a DAG using the Feedback Edge (Arc) Set. All edges from this set has to be removed, and the
47
+ # edges are transferred to newly created nodes.
48
+ convert_to_dag!(nodes, feedback_edge_set)
49
+
50
+ # And assert again we've really built a DAG
51
+ # TODO(lsmola) And if the result causes a cycle, we should repeat the build_dag method, with a max
52
+ # depth 10. We should throw a warning maybe asking for simplifying the interconnections in the models.
53
+ assert_graph!(edges)
54
+
55
+ self.nodes = sort_nodes(nodes)
56
+
57
+ self
58
+ end
59
+
60
+ private
61
+
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
+ # Asserts all nodes are of class ::InventoryRefresh::InventoryCollection
76
+ #
77
+ # @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
78
+ def assert_inventory_collections(inventory_collections)
79
+ inventory_collections.each do |inventory_collection|
80
+ unless inventory_collection.kind_of?(::InventoryRefresh::InventoryCollection)
81
+ raise "A InventoryRefresh::SaveInventory needs a InventoryCollection object, it got: #{inventory_collection.inspect}"
82
+ end
83
+ end
84
+ end
85
+
86
+ # Converts the graph into DAG
87
+ #
88
+ # @param nodes [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
89
+ # @param feedback_edge_set [Array<Array>] Is a set of edges that are causing a cycle in the graph
90
+ # @return [InventoryRefresh::InventoryCollection::Graph] Graph object modified into DAG
91
+ def convert_to_dag!(nodes, feedback_edge_set)
92
+ new_nodes = []
93
+ inventory_collection_transformations = {}
94
+ nodes.each do |inventory_collection|
95
+ feedback_dependencies = feedback_edge_set.select { |e| e.second == inventory_collection }.map(&:first)
96
+ attrs = inventory_collection.dependency_attributes_for(feedback_dependencies)
97
+
98
+ next if attrs.blank?
99
+
100
+ new_inventory_collection = inventory_collection.clone
101
+
102
+ # Add inventory_collection as a dependency of the new_inventory_collection, so we make sure it runs after
103
+ # TODO(lsmola) add a nice dependency_attributes setter? It's used also in actualize_dependencies method
104
+ new_inventory_collection.dependency_attributes[:__feedback_edge_set_parent] = Set.new([inventory_collection])
105
+ new_nodes << new_inventory_collection
106
+
107
+ inventory_collection.blacklist_attributes!(attrs)
108
+ new_inventory_collection.whitelist_attributes!(attrs)
109
+
110
+ # Store a simple hash for transforming inventory_collection to new_inventory_collection
111
+ inventory_collection_transformations[inventory_collection] = new_inventory_collection
112
+ end
113
+
114
+ all_nodes = nodes + new_nodes
115
+
116
+ # If we remove an attribute that was a dependency of another node, we need to move also the
117
+ # dependency. So e.g. floating_ip depends on network_port's attribute vm, but we move that attribute to new
118
+ # network_port inventory_collection. We will need to move also the dependency to point to the new
119
+ # inventory_collection.
120
+ #
121
+ # So we have to go through all dependencies that loads a key, which is the moved attribute. We can get a list
122
+ # of attributes that are using a key from transitive_dependency_attributes, from there we can get a list of
123
+ # dependencies. And from the list of dependencies, we can check which ones were moved just by looking into
124
+ # inventory_collection_transformations.
125
+ all_nodes.each do |inventory_collection|
126
+ inventory_collection.transitive_dependency_attributes.each do |transitive_dependency_attribute|
127
+ transitive_dependencies = inventory_collection.dependency_attributes[transitive_dependency_attribute]
128
+ next if transitive_dependencies.blank?
129
+
130
+ transitive_dependencies.map! do |dependency|
131
+ transformed_dependency = inventory_collection_transformations[dependency]
132
+ transformed_dependency.blank? ? dependency : transformed_dependency
133
+ end
134
+ end
135
+ end
136
+
137
+ # Add the new InventoryCollections to the list of nodes our our graph
138
+ construct_graph!(all_nodes)
139
+ end
140
+
141
+ # Build edges by introspecting the passed array of InventoryCollection objects
142
+ #
143
+ # @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
144
+ # @return [Array<Array>] Nested array representing edges
145
+ def build_edges(inventory_collections)
146
+ edges = []
147
+ transitive_edges = []
148
+ fixed_edges = []
149
+ inventory_collections.each do |inventory_collection|
150
+ inventory_collection.dependencies.each do |dependency|
151
+ fixed_edges << [dependency, inventory_collection] if inventory_collection.fixed_dependencies.include?(dependency)
152
+ if inventory_collection.dependency_attributes_for([dependency]).any? { |x| inventory_collection.transitive_dependency_attributes.include?(x) }
153
+ # The condition checks if the dependency is a transitive dependency, in other words a InventoryObjectLazy with :key
154
+ # pointing to another object.
155
+ transitive_edges << [dependency, inventory_collection]
156
+ else
157
+ edges << [dependency, inventory_collection]
158
+ end
159
+ end
160
+ end
161
+ # We put transitive edges to the end. Transitive edge is e.g.: given graph (X,Y,Z), we have a lazy link, from X
162
+ # to Y, making edge (Y, X), using a :key pointing to Z. Which means that also edge from Y to Z (Z, Y) exists.
163
+ # If the edge (Z, Y) is placed before (Y, X), we process it first. Then the edge (Y, X), causing hidden
164
+ # transitive relation X to Z (it's hidden because edge (Z, X) is not present), is processed as last and we do a
165
+ # more effective cycle removal if needed.
166
+ return edges + transitive_edges, fixed_edges
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,230 @@
1
+ require "inventory_refresh/inventory_collection/index/type/data"
2
+ require "inventory_refresh/inventory_collection/index/type/local_db"
3
+ require "inventory_refresh/inventory_collection/index/type/skeletal"
4
+ require "active_support/core_ext/module/delegation"
5
+
6
+ module InventoryRefresh
7
+ class InventoryCollection
8
+ module Index
9
+ class Proxy
10
+ attr_reader :skeletal_primary_index
11
+
12
+ # @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object owning the proxy
13
+ # @param secondary_refs [Hash] Secondary_refs in format {:name_of_the_ref => [:attribute1, :attribute2]}
14
+ def initialize(inventory_collection, secondary_refs = {})
15
+ @inventory_collection = inventory_collection
16
+
17
+ @primary_ref = {primary_index_ref => @inventory_collection.manager_ref}
18
+ @secondary_refs = secondary_refs
19
+ @all_refs = @primary_ref.merge(@secondary_refs)
20
+
21
+ @data_indexes = {}
22
+ @local_db_indexes = {}
23
+
24
+ @all_refs.each do |index_name, attribute_names|
25
+ @data_indexes[index_name] = InventoryRefresh::InventoryCollection::Index::Type::Data.new(
26
+ inventory_collection,
27
+ index_name,
28
+ attribute_names
29
+ )
30
+
31
+ @local_db_indexes[index_name] = InventoryRefresh::InventoryCollection::Index::Type::LocalDb.new(
32
+ inventory_collection,
33
+ index_name,
34
+ attribute_names,
35
+ @data_indexes[index_name]
36
+ )
37
+ end
38
+
39
+ @skeletal_primary_index = InventoryRefresh::InventoryCollection::Index::Type::Skeletal.new(
40
+ inventory_collection,
41
+ :skeletal_primary_index_ref,
42
+ named_ref,
43
+ primary_index
44
+ )
45
+ end
46
+
47
+ # Builds primary index for passed InventoryObject
48
+ #
49
+ # @param inventory_object [InventoryRefresh::InventoryObject] InventoryObject we want to index
50
+ # @return [InventoryRefresh::InventoryObject] Passed InventoryObject
51
+ def build_primary_index_for(inventory_object)
52
+ # Building the object, we need to provide all keys of a primary index
53
+
54
+ assert_index(inventory_object.data, primary_index_ref)
55
+ primary_index.store_index_for(inventory_object)
56
+ end
57
+
58
+ def build_secondary_indexes_for(inventory_object)
59
+ secondary_refs.keys.each do |ref|
60
+ data_index(ref).store_index_for(inventory_object)
61
+ end
62
+ end
63
+
64
+ def reindex_secondary_indexes!
65
+ data_indexes.each do |ref, index|
66
+ next if ref == primary_index_ref
67
+
68
+ index.reindex!
69
+ end
70
+ end
71
+
72
+ def primary_index
73
+ data_index(primary_index_ref)
74
+ end
75
+
76
+ def find(reference, ref: primary_index_ref)
77
+ # TODO(lsmola) lazy_find will support only hash, then we can remove the _by variant
78
+ # TODO(lsmola) this method should return lazy too, the rest of the finders should be deprecated
79
+ return if reference.nil?
80
+ assert_index(reference, ref)
81
+
82
+ reference = inventory_collection.build_reference(reference, ref)
83
+
84
+ case strategy
85
+ when :local_db_find_references, :local_db_cache_all
86
+ local_db_index_find(reference)
87
+ when :local_db_find_missing_references
88
+ find_in_data_or_skeletal_index(reference) || local_db_index_find(reference)
89
+ else
90
+ find_in_data_or_skeletal_index(reference)
91
+ end
92
+ end
93
+
94
+ def find_by(manager_uuid_hash, ref: primary_index_ref)
95
+ # TODO(lsmola) deprecate this, it's enough to have find method
96
+ find(manager_uuid_hash, :ref => ref)
97
+ end
98
+
99
+ def lazy_find_by(manager_uuid_hash, ref: primary_index_ref, key: nil, default: nil)
100
+ # TODO(lsmola) deprecate this, it's enough to have lazy_find method
101
+
102
+ lazy_find(manager_uuid_hash, :ref => ref, :key => key, :default => default)
103
+ end
104
+
105
+ def lazy_find(manager_uuid, ref: primary_index_ref, key: nil, default: nil, transform_nested_lazy_finds: false)
106
+ # TODO(lsmola) also, it should be enough to have only 1 find method, everything can be lazy, until we try to
107
+ # access the data
108
+ # TODO(lsmola) lazy_find will support only hash, then we can remove the _by variant
109
+ return if manager_uuid.nil?
110
+ assert_index(manager_uuid, ref)
111
+
112
+ ::InventoryRefresh::InventoryObjectLazy.new(inventory_collection,
113
+ manager_uuid,
114
+ :ref => ref,
115
+ :key => key,
116
+ :default => default,
117
+ :transform_nested_lazy_finds => transform_nested_lazy_finds)
118
+ end
119
+
120
+ def named_ref(ref = primary_index_ref)
121
+ all_refs[ref]
122
+ end
123
+
124
+ def primary_index_ref
125
+ :manager_ref
126
+ end
127
+
128
+ private
129
+
130
+ delegate :association_to_foreign_key_mapping,
131
+ :build_stringified_reference,
132
+ :parallel_safe?,
133
+ :strategy,
134
+ :to => :inventory_collection
135
+
136
+ attr_reader :all_refs, :data_indexes, :inventory_collection, :primary_ref, :local_db_indexes, :secondary_refs
137
+
138
+ def find_in_data_or_skeletal_index(reference)
139
+ if parallel_safe?
140
+ # With parallel safe strategies, we create skeletal nodes that we can look for
141
+ data_index_find(reference) || skeletal_index_find(reference)
142
+ else
143
+ data_index_find(reference)
144
+ end
145
+ end
146
+
147
+ def skeletal_index_find(reference)
148
+ # Find in skeletal index, but we are able to create skeletal index only for primary indexes
149
+ skeletal_primary_index.find(reference.stringified_reference) if reference.primary?
150
+ end
151
+
152
+ def data_index_find(reference)
153
+ data_index(reference.ref).find(reference.stringified_reference)
154
+ end
155
+
156
+ def local_db_index_find(reference)
157
+ local_db_index(reference.ref).find(reference)
158
+ end
159
+
160
+ def data_index(name)
161
+ data_indexes[name] || raise("Index :#{name} not defined for #{inventory_collection}")
162
+ end
163
+
164
+ def local_db_index(name)
165
+ local_db_indexes[name] || raise("Index :#{name} not defined for #{inventory_collection}")
166
+ end
167
+
168
+ def missing_keys(data_keys, ref)
169
+ named_ref(ref) - data_keys
170
+ end
171
+
172
+ def required_index_keys_present?(data_keys, ref)
173
+ missing_keys(data_keys, ref).empty?
174
+ end
175
+
176
+ def assert_relation_keys(data, ref)
177
+ named_ref(ref).each do |key|
178
+ # Skip if the key is not a foreign key
179
+ next unless association_to_foreign_key_mapping[key]
180
+ # Skip if data on key are nil or InventoryObject or InventoryObjectLazy
181
+ next if data[key].nil? || data[key].kind_of?(InventoryRefresh::InventoryObject) || data[key].kind_of?(InventoryRefresh::InventoryObjectLazy)
182
+ # Raise error since relation must be nil or InventoryObject or InventoryObjectLazy
183
+ raise "Wrong index for key :#{key}, the value must be of type Nil or InventoryObject or InventoryObjectLazy, got: #{data[key]}"
184
+ end
185
+ end
186
+
187
+ def assert_index_exists(ref)
188
+ raise "Index :#{ref} doesn't exist on #{inventory_collection}" if named_ref(ref).nil?
189
+ end
190
+
191
+ def assert_index(manager_uuid, ref)
192
+ # TODO(lsmola) do we need some production logging too? Maybe the refresh log level could drive this
193
+ # Let' do this really slick development and test env, but disable for production, since the checks are pretty
194
+ # slow.
195
+ # TODO: return if Rails.env.production?
196
+
197
+ if manager_uuid.kind_of?(InventoryRefresh::InventoryCollection::Reference)
198
+ # InventoryRefresh::InventoryCollection::Reference has been already asserted, skip
199
+ elsif manager_uuid.kind_of?(Hash)
200
+ # Test te index exists
201
+ assert_index_exists(ref)
202
+
203
+ # Test we are sending all keys required for the index
204
+ unless required_index_keys_present?(manager_uuid.keys, ref)
205
+ raise "Finder has missing keys for index :#{ref}, missing indexes are: #{missing_keys(manager_uuid.keys, ref)}"
206
+ end
207
+ # Test that keys, that are relations, are nil or InventoryObject or InventoryObjectlazy class
208
+ assert_relation_keys(manager_uuid, ref)
209
+ else
210
+ # Test te index exists
211
+ assert_index_exists(ref)
212
+
213
+ # Check that other value (possibly String or Integer)) has no composite index
214
+ if named_ref(ref).size > 1
215
+ right_format = "collection.find(#{named_ref(ref).map { |x| ":#{x} => 'X'" }.join(", ")}"
216
+
217
+ raise "The index :#{ref} has composite index, finder has to be called as: #{right_format})"
218
+ end
219
+
220
+ # Assert the that possible relation is nil or InventoryObject or InventoryObjectlazy class
221
+ assert_relation_keys({named_ref(ref).first => manager_uuid}, ref)
222
+ end
223
+ rescue => e
224
+ #_log.error("Error when asserting index: #{manager_uuid}, with ref: #{ref} of: #{inventory_collection}")
225
+ raise e
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end