forked from ManageIQ/manageiq
-
Notifications
You must be signed in to change notification settings - Fork 0
/
inventory_object_lazy.rb
151 lines (125 loc) · 6.6 KB
/
inventory_object_lazy.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
module ManagerRefresh
class InventoryObjectLazy
include Vmdb::Logging
attr_reader :reference, :inventory_collection, :key, :default, :transform_nested_lazy_finds
delegate :stringified_reference, :ref, :[], :to => :reference
# @param inventory_collection [ManagerRefresh::InventoryCollection] InventoryCollection object owning the
# InventoryObject
# @param index_data [Hash] data of the InventoryObject object
# @param ref [Symbol] reference name
# @param key [Symbol] key name, will be used to fetch attribute from resolved InventoryObject
# @param default [Object] a default value used if the :key will resolve to nil
# @param transform_nested_lazy_finds [Boolean] True if we want to convert all lazy objects in InventoryObject
# objects and reset the Reference. TODO(lsmola) we should be able to do this automatically, then we can
# remove this option
def initialize(inventory_collection, index_data, ref: :manager_ref, key: nil, default: nil, transform_nested_lazy_finds: false)
@inventory_collection = inventory_collection
@reference = inventory_collection.build_reference(index_data, ref)
@key = key
@default = default
@transform_nested_lazy_finds = transform_nested_lazy_finds
# We do not support skeletal pre-create for :key, since :key will not be available, we want to use local_db_find
# instead.
skeletal_precreate! unless @key
end
# @return [String] stringified reference
def to_s
# TODO(lsmola) do we need this method?
stringified_reference
end
# @return [String] string format for nice logging
def inspect
suffix = ""
suffix += ", ref: #{ref}" if ref.present?
suffix += ", key: #{key}" if key.present?
"InventoryObjectLazy:('#{self}', #{inventory_collection}#{suffix})"
end
# @return [ManagerRefresh::InventoryObject, Object] ManagerRefresh::InventoryObject instance or an attribute
# on key
def load
transform_nested_secondary_indexes! if transform_nested_lazy_finds && nested_secondary_index?
key ? load_object_with_key : load_object
end
# return [Boolean] true if the Lazy object is causing a dependency, Lazy link is always a dependency if no :key
# is provider or if it's transitive_dependency
def dependency?
# If key is not set, InventoryObjectLazy is a dependency, cause it points to the record itself. Otherwise
# InventoryObjectLazy is a dependency only if it points to an attribute which is a dependency or a relation.
!key || transitive_dependency?
end
# return [Boolean] true if the Lazy object is causing a transitive dependency, which happens if the :key points
# to an attribute that is causing a dependency.
def transitive_dependency?
# If the dependency is inventory_collection.lazy_find(:ems_ref, :key => :stack)
# and a :stack is a relation to another object, in the InventoryObject object,
# then this relation is considered transitive.
key && association?(key)
end
# @return [Boolean] true if the key is an association on inventory_collection_scope model class
def association?(key)
# TODO(lsmola) remove this if there will be better dependency scan, probably with transitive dependencies filled
# in a second pass, then we can get rid of this hardcoded symbols. Right now we are not able to introspect these.
return true if [:parent, :genealogy_parent].include?(key)
inventory_collection.dependency_attributes.key?(key) ||
!inventory_collection.association_to_foreign_key_mapping[key].nil?
end
def transform_nested_secondary_indexes!(depth = 0)
raise "Nested references are too deep!" if depth > 20
keys.each do |x|
attr = full_reference[x]
next unless attr.kind_of?(ManagerRefresh::InventoryObjectLazy)
next if attr.primary?
if attr.nested_secondary_index?
attr.transform_nested_secondary_indexes!(depth + 1)
end
full_reference[x] = full_reference[x].load
end
# Rebuild the reference to get the right value
self.reference = inventory_collection.build_reference(full_reference, ref)
end
private
delegate :parallel_safe?, :saved?, :saver_strategy, :skeletal_primary_index, :targeted?, :to => :inventory_collection
delegate :nested_secondary_index?, :primary?, :full_reference, :keys, :primary?, :to => :reference
attr_writer :reference
# Instead of loading the reference from the DB, we'll add the skeletal InventoryObject (having manager_ref and
# info from the builder_params) to the correct InventoryCollection. Which will either be found in the DB or
# created as a skeletal object. The later refresh of the object will then fill the rest of the data, while not
# touching the reference.
#
# @return [ManagerRefresh::InventoryObject, NilClass] Returns pre-created InventoryObject or nil
def skeletal_precreate!
# We can do skeletal pre-create only for strategies using unique indexes. Since this can build records out of
# the given :arel scope, we will always attempt to create the recod, so we need unique index to avoid duplication
# of records.
return unless parallel_safe?
# Pre-create only for strategies that will be persisting data, i.e. are not saved already
return if saved?
# We can only do skeletal pre-create for primary index reference, since that is needed to create DB unique index
return unless primary?
# Full reference must be present
return if full_reference.blank?
# To avoid pre-creating invalid records all fields of a primary key must have value
# TODO(lsmola) for composite keys, it's still valid to have one of the keys nil, figure out how to allow this
return if keys.any? { |x| full_reference[x].blank? }
skeletal_primary_index.build(full_reference)
end
# @return [Object] value found or :key or default value if the value is nil
def load_object_with_key
# TODO(lsmola) Log error if we are accessing path that is present in blacklist or not present in whitelist
found = inventory_collection.find(reference)
if found.present?
if found.try(:data).present?
found.data[key] || default
else
found.public_send(key) || default
end
else
default
end
end
# @return [ManagerRefresh::InventoryObject, NilClass] ManagerRefresh::InventoryObject instance or nil if not found
def load_object
inventory_collection.find(reference)
end
end
end