inventory_refresh 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,303 @@
1
+ require "inventory_refresh/application_record_reference"
2
+ require "active_support/core_ext/module/delegation"
3
+
4
+ module InventoryRefresh
5
+ class InventoryObject
6
+ attr_accessor :object, :id
7
+ attr_reader :inventory_collection, :data, :reference
8
+
9
+ delegate :manager_ref, :base_class_name, :model_class, :to => :inventory_collection
10
+ delegate :[], :[]=, :to => :data
11
+
12
+ # @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object owning the
13
+ # InventoryObject
14
+ # @param data [Hash] Data of the InventoryObject object
15
+ def initialize(inventory_collection, data)
16
+ @inventory_collection = inventory_collection
17
+ @data = data
18
+ @object = nil
19
+ @id = nil
20
+ @reference = inventory_collection.build_reference(data)
21
+ end
22
+
23
+ # @return [String] stringified reference
24
+ def manager_uuid
25
+ reference.stringified_reference
26
+ end
27
+
28
+ # @return [InventoryRefresh::InventoryObject] returns self
29
+ def load
30
+ self
31
+ end
32
+
33
+ def key
34
+ nil
35
+ end
36
+
37
+ # Transforms InventoryObject object data into hash format with keys that are column names and resolves correct
38
+ # values of the foreign keys (even the polymorphic ones)
39
+ #
40
+ # @param inventory_collection_scope [InventoryRefresh::InventoryCollection] parent InventoryCollection object
41
+ # @return [Hash] Data in DB format
42
+ def attributes(inventory_collection_scope = nil)
43
+ # We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with
44
+ # different blacklist and whitelist. The generic code always passes a scope.
45
+ inventory_collection_scope ||= inventory_collection
46
+
47
+ attributes_for_saving = {}
48
+ # First transform the values
49
+ data.each do |key, value|
50
+ if !allowed?(inventory_collection_scope, key)
51
+ next
52
+ elsif value.kind_of?(Array) && value.any? { |x| loadable?(x) }
53
+ # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
54
+ # result
55
+ data[key] = value.compact.map(&:load).compact
56
+ # We can use built in _ids methods to assign array of ids into has_many relations. So e.g. the :key_pairs=
57
+ # relation setter will become :key_pair_ids=
58
+ attributes_for_saving[(key.to_s.singularize + "_ids").to_sym] = data[key].map(&:id).compact.uniq
59
+ elsif loadable?(value) || inventory_collection_scope.association_to_foreign_key_mapping[key]
60
+ # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
61
+ # result
62
+ data[key] = value.load if value.respond_to?(:load)
63
+ if (foreign_key = inventory_collection_scope.association_to_foreign_key_mapping[key])
64
+ # We have an association to fill, lets fill also the :key, cause some other InventoryObject can refer to it
65
+ record_id = data[key].try(:id)
66
+ attributes_for_saving[foreign_key.to_sym] = record_id
67
+
68
+ if (foreign_type = inventory_collection_scope.association_to_foreign_type_mapping[key])
69
+ # If we have a polymorphic association, we need to also fill a base class name, but we want to nullify it
70
+ # if record_id is missing
71
+ base_class = data[key].try(:base_class_name) || data[key].class.try(:base_class).try(:name)
72
+ attributes_for_saving[foreign_type.to_sym] = record_id ? base_class : nil
73
+ end
74
+ elsif data[key].kind_of?(::InventoryRefresh::InventoryObject)
75
+ # We have an association to fill but not an Activerecord association, so e.g. Ancestry, lets just load
76
+ # it here. This way of storing ancestry is ineffective in DB call count, but RAM friendly
77
+ attributes_for_saving[key.to_sym] = data[key].base_class_name.constantize.find_by(:id => data[key].id)
78
+ else
79
+ # We have a normal attribute to fill
80
+ attributes_for_saving[key.to_sym] = data[key]
81
+ end
82
+ else
83
+ attributes_for_saving[key.to_sym] = value
84
+ end
85
+ end
86
+
87
+ attributes_for_saving
88
+ end
89
+
90
+ # Transforms InventoryObject object data into hash format with keys that are column names and resolves correct
91
+ # values of the foreign keys (even the polymorphic ones)
92
+ #
93
+ # @param inventory_collection_scope [InventoryRefresh::InventoryCollection] parent InventoryCollection object
94
+ # @param all_attribute_keys [Array<Symbol>] Attribute keys we will modify based on object's data
95
+ # @return [Hash] Data in DB format
96
+ def attributes_with_keys(inventory_collection_scope = nil, all_attribute_keys = [])
97
+ # We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with
98
+ # different blacklist and whitelist. The generic code always passes a scope.
99
+ inventory_collection_scope ||= inventory_collection
100
+
101
+ attributes_for_saving = {}
102
+ # First transform the values
103
+ data.each do |key, value|
104
+ if !allowed?(inventory_collection_scope, key)
105
+ next
106
+ elsif loadable?(value) || inventory_collection_scope.association_to_foreign_key_mapping[key]
107
+ # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
108
+ # result
109
+ data[key] = value.load if value.respond_to?(:load)
110
+ if (foreign_key = inventory_collection_scope.association_to_foreign_key_mapping[key])
111
+ # We have an association to fill, lets fill also the :key, cause some other InventoryObject can refer to it
112
+ record_id = data[key].try(:id)
113
+ foreign_key_to_sym = foreign_key.to_sym
114
+ attributes_for_saving[foreign_key_to_sym] = record_id
115
+ all_attribute_keys << foreign_key_to_sym
116
+ if (foreign_type = inventory_collection_scope.association_to_foreign_type_mapping[key])
117
+ # If we have a polymorphic association, we need to also fill a base class name, but we want to nullify it
118
+ # if record_id is missing
119
+ base_class = data[key].try(:base_class_name) || data[key].class.try(:base_class).try(:name)
120
+ foreign_type_to_sym = foreign_type.to_sym
121
+ attributes_for_saving[foreign_type_to_sym] = record_id ? base_class : nil
122
+ all_attribute_keys << foreign_type_to_sym
123
+ end
124
+ else
125
+ # We have a normal attribute to fill
126
+ attributes_for_saving[key] = data[key]
127
+ all_attribute_keys << key
128
+ end
129
+ else
130
+ attributes_for_saving[key] = value
131
+ all_attribute_keys << key
132
+ end
133
+ end
134
+
135
+ attributes_for_saving
136
+ end
137
+
138
+ # Given hash of attributes, we assign them to InventoryObject object using its public writers
139
+ #
140
+ # @param attributes [Hash] attributes we want to assign
141
+ # @return [InventoryRefresh::InventoryObject] self
142
+ def assign_attributes(attributes)
143
+ attributes.each do |k, v|
144
+ # We don't want timestamps or resource versions to be overwritten here, since those are driving the conditions
145
+ next if %i(resource_timestamps resource_timestamps_max resource_timestamp).include?(k)
146
+ next if %i(resource_versions resource_versions_max resource_version).include?(k)
147
+
148
+ if data[:resource_timestamp] && attributes[:resource_timestamp]
149
+ assign_only_newest(:resource_timestamp, :resource_timestamps, attributes, data, k, v)
150
+ elsif data[:resource_version] && attributes[:resource_version]
151
+ assign_only_newest(:resource_version, :resource_versions, attributes, data, k, v)
152
+ else
153
+ public_send("#{k}=", v)
154
+ end
155
+ end
156
+
157
+ if attributes[:resource_timestamp]
158
+ assign_full_row_version_attr(:resource_timestamp, attributes, data)
159
+ elsif attributes[:resource_version]
160
+ assign_full_row_version_attr(:resource_version, attributes, data)
161
+ end
162
+
163
+ self
164
+ end
165
+
166
+ # @return [String] stringified UUID
167
+ def to_s
168
+ manager_uuid
169
+ end
170
+
171
+ # @return [String] string format for nice logging
172
+ def inspect
173
+ "InventoryObject:('#{manager_uuid}', #{inventory_collection})"
174
+ end
175
+
176
+ # @return [TrueClass] InventoryObject object is always a dependency
177
+ def dependency?
178
+ true
179
+ end
180
+
181
+ # Adds setters and getters based on :inventory_object_attributes kwarg passed into InventoryCollection
182
+ # Methods already defined should not be redefined (causes unexpected behaviour)
183
+ #
184
+ # @param inventory_object_attributes [Array<Symbol>]
185
+ def self.add_attributes(inventory_object_attributes)
186
+ defined_methods = InventoryRefresh::InventoryObject.instance_methods(false)
187
+
188
+ inventory_object_attributes.each do |attr|
189
+ unless defined_methods.include?("#{attr}=".to_sym)
190
+ define_method("#{attr}=") do |value|
191
+ data[attr] = value
192
+ end
193
+ end
194
+
195
+ unless defined_methods.include?(attr.to_sym)
196
+ define_method(attr) do
197
+ data[attr]
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ private
204
+
205
+ # Assigns value based on the version attributes. If versions are specified, it asigns attribute only if it's
206
+ # newer than existing attribute.
207
+ #
208
+ # @param full_row_version_attr [Symbol] Attr name for full rows, allowed values are
209
+ # [:resource_timestamp, :resource_version]
210
+ # @param partial_row_version_attr [Symbol] Attr name for partial rows, allowed values are
211
+ # [:resource_timestamps, :resource_versions]
212
+ # @param attributes [Hash] New attributes we are assigning
213
+ # @param data [Hash] Existing attributes of the InventoryObject
214
+ # @param k [Symbol] Name of the attribute we are assigning
215
+ # @param v [Object] Value of the attribute we are assigning
216
+ def assign_only_newest(full_row_version_attr, partial_row_version_attr, attributes, data, k, v)
217
+ # If timestamps are in play, we will set only attributes that are newer
218
+ specific_attr_timestamp = attributes[partial_row_version_attr].try(:[], k)
219
+ specific_data_timestamp = data[partial_row_version_attr].try(:[], k)
220
+
221
+ assign = if !specific_attr_timestamp
222
+ # Data have no timestamp, we will ignore the check
223
+ true
224
+ elsif specific_attr_timestamp && !specific_data_timestamp
225
+ # Data specific timestamp is nil and we have new specific timestamp
226
+ if data.key?(k)
227
+ if attributes[full_row_version_attr] >= data[full_row_version_attr]
228
+ # We can save if the full timestamp is bigger, if the data already contains the attribute
229
+ true
230
+ end
231
+ else
232
+ # Data do not contain the attribute, so we are saving the newest
233
+ true
234
+ end
235
+ true
236
+ elsif specific_attr_timestamp > specific_data_timestamp
237
+ # both partial timestamps are there, newer must be bigger
238
+ true
239
+ end
240
+
241
+ if assign
242
+ public_send("#{k}=", v) # Attribute is newer than current one, lets use it
243
+ (data[partial_row_version_attr] ||= {})[k] = specific_attr_timestamp if specific_attr_timestamp # and set the latest timestamp
244
+ end
245
+ end
246
+
247
+ # Assigns attribute representing version of the whole row
248
+ #
249
+ # @param full_row_version_attr [Symbol] Attr name for full rows, allowed values are
250
+ # [:resource_timestamp, :resource_version]
251
+ # @param attributes [Hash] New attributes we are assigning
252
+ # @param data [Hash] Existing attributes of the InventoryObject
253
+ def assign_full_row_version_attr(full_row_version_attr, attributes, data)
254
+ if attributes[full_row_version_attr] && data[full_row_version_attr]
255
+ # If both timestamps are present, store the bigger one
256
+ data[full_row_version_attr] = attributes[full_row_version_attr] if attributes[full_row_version_attr] > data[full_row_version_attr]
257
+ elsif attributes[full_row_version_attr] && !data[full_row_version_attr]
258
+ # We are assigning timestamp that was missing
259
+ data[full_row_version_attr] = attributes[full_row_version_attr]
260
+ end
261
+ end
262
+
263
+ # Return true passed key representing a getter is an association
264
+ #
265
+ # @param inventory_collection_scope [InventoryRefresh::InventoryCollection]
266
+ # @param key [Symbol] key representing getter
267
+ # @return [Boolean] true if the passed key points to association
268
+ def association?(inventory_collection_scope, key)
269
+ # Is the key an association on inventory_collection_scope model class?
270
+ !inventory_collection_scope.association_to_foreign_key_mapping[key].nil?
271
+ end
272
+
273
+ # Return true if the attribute is allowed to be saved into the DB
274
+ #
275
+ # @param inventory_collection_scope [InventoryRefresh::InventoryCollection] InventoryCollection object owning the
276
+ # attribute
277
+ # @param key [Symbol] attribute name
278
+ # @return true if the attribute is allowed to be saved into the DB
279
+ def allowed?(inventory_collection_scope, key)
280
+ foreign_to_association = inventory_collection_scope.foreign_key_to_association_mapping[key] ||
281
+ inventory_collection_scope.foreign_type_to_association_mapping[key]
282
+
283
+ return false if inventory_collection_scope.attributes_blacklist.present? &&
284
+ (inventory_collection_scope.attributes_blacklist.include?(key) ||
285
+ (foreign_to_association && inventory_collection_scope.attributes_blacklist.include?(foreign_to_association)))
286
+
287
+ return false if inventory_collection_scope.attributes_whitelist.present? &&
288
+ (!inventory_collection_scope.attributes_whitelist.include?(key) &&
289
+ (!foreign_to_association || (foreign_to_association && inventory_collection_scope.attributes_whitelist.include?(foreign_to_association))))
290
+
291
+ true
292
+ end
293
+
294
+ # Return true if the object is loadable, which we determine by a list of loadable classes.
295
+ #
296
+ # @param value [Object] object we test
297
+ # @return true if the object is loadable
298
+ def loadable?(value)
299
+ value.kind_of?(::InventoryRefresh::InventoryObjectLazy) || value.kind_of?(::InventoryRefresh::InventoryObject) ||
300
+ value.kind_of?(::InventoryRefresh::ApplicationRecordReference)
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,151 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module InventoryRefresh
4
+ class InventoryObjectLazy
5
+ attr_reader :reference, :inventory_collection, :key, :default, :transform_nested_lazy_finds
6
+
7
+ delegate :stringified_reference, :ref, :[], :to => :reference
8
+
9
+ # @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object owning the
10
+ # InventoryObject
11
+ # @param index_data [Hash] data of the InventoryObject object
12
+ # @param ref [Symbol] reference name
13
+ # @param key [Symbol] key name, will be used to fetch attribute from resolved InventoryObject
14
+ # @param default [Object] a default value used if the :key will resolve to nil
15
+ # @param transform_nested_lazy_finds [Boolean] True if we want to convert all lazy objects in InventoryObject
16
+ # objects and reset the Reference. TODO(lsmola) we should be able to do this automatically, then we can
17
+ # remove this option
18
+ def initialize(inventory_collection, index_data, ref: :manager_ref, key: nil, default: nil, transform_nested_lazy_finds: false)
19
+ @inventory_collection = inventory_collection
20
+ @reference = inventory_collection.build_reference(index_data, ref)
21
+ @key = key
22
+ @default = default
23
+
24
+ @transform_nested_lazy_finds = transform_nested_lazy_finds
25
+
26
+ # We do not support skeletal pre-create for :key, since :key will not be available, we want to use local_db_find
27
+ # instead.
28
+ skeletal_precreate! unless @key
29
+ end
30
+
31
+ # @return [String] stringified reference
32
+ def to_s
33
+ # TODO(lsmola) do we need this method?
34
+ stringified_reference
35
+ end
36
+
37
+ # @return [String] string format for nice logging
38
+ def inspect
39
+ suffix = ""
40
+ suffix += ", ref: #{ref}" if ref.present?
41
+ suffix += ", key: #{key}" if key.present?
42
+ "InventoryObjectLazy:('#{self}', #{inventory_collection}#{suffix})"
43
+ end
44
+
45
+ # @return [InventoryRefresh::InventoryObject, Object] InventoryRefresh::InventoryObject instance or an attribute
46
+ # on key
47
+ def load
48
+ transform_nested_secondary_indexes! if transform_nested_lazy_finds && nested_secondary_index?
49
+
50
+ key ? load_object_with_key : load_object
51
+ end
52
+
53
+ # return [Boolean] true if the Lazy object is causing a dependency, Lazy link is always a dependency if no :key
54
+ # is provider or if it's transitive_dependency
55
+ def dependency?
56
+ # If key is not set, InventoryObjectLazy is a dependency, cause it points to the record itself. Otherwise
57
+ # InventoryObjectLazy is a dependency only if it points to an attribute which is a dependency or a relation.
58
+ !key || transitive_dependency?
59
+ end
60
+
61
+ # return [Boolean] true if the Lazy object is causing a transitive dependency, which happens if the :key points
62
+ # to an attribute that is causing a dependency.
63
+ def transitive_dependency?
64
+ # If the dependency is inventory_collection.lazy_find(:ems_ref, :key => :stack)
65
+ # and a :stack is a relation to another object, in the InventoryObject object,
66
+ # then this relation is considered transitive.
67
+ key && association?(key)
68
+ end
69
+
70
+ # @return [Boolean] true if the key is an association on inventory_collection_scope model class
71
+ def association?(key)
72
+ # TODO(lsmola) remove this if there will be better dependency scan, probably with transitive dependencies filled
73
+ # in a second pass, then we can get rid of this hardcoded symbols. Right now we are not able to introspect these.
74
+ return true if [:parent, :genealogy_parent].include?(key)
75
+
76
+ inventory_collection.dependency_attributes.key?(key) ||
77
+ !inventory_collection.association_to_foreign_key_mapping[key].nil?
78
+ end
79
+
80
+ def transform_nested_secondary_indexes!(depth = 0)
81
+ raise "Nested references are too deep!" if depth > 20
82
+
83
+ keys.each do |x|
84
+ attr = full_reference[x]
85
+ next unless attr.kind_of?(InventoryRefresh::InventoryObjectLazy)
86
+ next if attr.primary?
87
+
88
+ if attr.nested_secondary_index?
89
+ attr.transform_nested_secondary_indexes!(depth + 1)
90
+ end
91
+
92
+ full_reference[x] = full_reference[x].load
93
+ end
94
+
95
+ # Rebuild the reference to get the right value
96
+ self.reference = inventory_collection.build_reference(full_reference, ref)
97
+ end
98
+
99
+ private
100
+
101
+ delegate :parallel_safe?, :saved?, :saver_strategy, :skeletal_primary_index, :targeted?, :to => :inventory_collection
102
+ delegate :nested_secondary_index?, :primary?, :full_reference, :keys, :primary?, :to => :reference
103
+
104
+ attr_writer :reference
105
+
106
+ # Instead of loading the reference from the DB, we'll add the skeletal InventoryObject (having manager_ref and
107
+ # info from the default_values) to the correct InventoryCollection. Which will either be found in the DB or
108
+ # created as a skeletal object. The later refresh of the object will then fill the rest of the data, while not
109
+ # touching the reference.
110
+ #
111
+ # @return [InventoryRefresh::InventoryObject, NilClass] Returns pre-created InventoryObject or nil
112
+ def skeletal_precreate!
113
+ # We can do skeletal pre-create only for strategies using unique indexes. Since this can build records out of
114
+ # the given :arel scope, we will always attempt to create the recod, so we need unique index to avoid duplication
115
+ # of records.
116
+ return unless parallel_safe?
117
+ # Pre-create only for strategies that will be persisting data, i.e. are not saved already
118
+ return if saved?
119
+ # We can only do skeletal pre-create for primary index reference, since that is needed to create DB unique index
120
+ return unless primary?
121
+ # Full reference must be present
122
+ return if full_reference.blank?
123
+
124
+ # To avoid pre-creating invalid records all fields of a primary key must have value
125
+ # TODO(lsmola) for composite keys, it's still valid to have one of the keys nil, figure out how to allow this
126
+ return if keys.any? { |x| full_reference[x].blank? }
127
+
128
+ skeletal_primary_index.build(full_reference)
129
+ end
130
+
131
+ # @return [Object] value found or :key or default value if the value is nil
132
+ def load_object_with_key
133
+ # TODO(lsmola) Log error if we are accessing path that is present in blacklist or not present in whitelist
134
+ found = inventory_collection.find(reference)
135
+ if found.present?
136
+ if found.try(:data).present?
137
+ found.data[key] || default
138
+ else
139
+ found.public_send(key) || default
140
+ end
141
+ else
142
+ default
143
+ end
144
+ end
145
+
146
+ # @return [InventoryRefresh::InventoryObject, NilClass] InventoryRefresh::InventoryObject instance or nil if not found
147
+ def load_object
148
+ inventory_collection.find(reference)
149
+ end
150
+ end
151
+ end