diff --git a/app/models/manager_refresh/inventory_object.rb b/app/models/manager_refresh/inventory_object.rb index ae6bf9bf940..6d6797ff6fb 100644 --- a/app/models/manager_refresh/inventory_object.rb +++ b/app/models/manager_refresh/inventory_object.rb @@ -6,6 +6,9 @@ class InventoryObject delegate :manager_ref, :base_class_name, :model_class, :to => :inventory_collection delegate :[], :[]=, :to => :data + # @param inventory_collection [ManagerRefresh::InventoryCollection] InventoryCollection object owning the + # InventoryObject + # @param data [Hash] Data of the InventoryObject object def initialize(inventory_collection, data) @inventory_collection = inventory_collection @data = data @@ -14,10 +17,12 @@ def initialize(inventory_collection, data) @reference = inventory_collection.build_reference(data) end + # @return [String] stringified reference def manager_uuid reference.stringified_reference end + # @return [Hash] serialized InventoryObject into Lazy format def to_raw_lazy_relation { :type => "ManagerRefresh::InventoryObjectLazy", @@ -26,10 +31,16 @@ def to_raw_lazy_relation } end + # @return [ManagerRefresh::InventoryObject] returns self def load self end + # Transforms InventoryObject object data into hash format with keys that are column names and resolves correct + # values of the foreign keys (even the polymorphic ones) + # + # @param inventory_collection_scope [ManagerRefresh::InventoryCollection] parent InventoryCollection object + # @return [Hash] Data in DB format def attributes(inventory_collection_scope = nil) # We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with # different blacklist and whitelist. The generic code always passes a scope. @@ -78,6 +89,12 @@ def attributes(inventory_collection_scope = nil) attributes_for_saving end + # Transforms InventoryObject object data into hash format with keys that are column names and resolves correct + # values of the foreign keys (even the polymorphic ones) + # + # @param inventory_collection_scope [ManagerRefresh::InventoryCollection] parent InventoryCollection object + # @param all_attribute_keys [Array] Attribute keys we will modify based on object's data + # @return [Hash] Data in DB format def attributes_with_keys(inventory_collection_scope = nil, all_attribute_keys = []) # We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with # different blacklist and whitelist. The generic code always passes a scope. @@ -120,23 +137,33 @@ def attributes_with_keys(inventory_collection_scope = nil, all_attribute_keys = attributes_for_saving end + # Given hash of attributes, we assign them to InventoryObject object using its public writers + # + # @param attributes [Hash] attributes we want to assign + # @return [ManagerRefresh::InventoryObject] self def assign_attributes(attributes) attributes.each { |k, v| public_send("#{k}=", v) } self end + # @return [String] stringified UUID def to_s manager_uuid end + # @return [String] string format for nice logging def inspect "InventoryObject:('#{manager_uuid}', #{inventory_collection})" end + # @return [TrueClass] InventoryObject object is always a dependency def dependency? true end + # Adds setters and getters based on :inventory_object_attributes kwarg passed into InventoryCollection + # + # @param inventory_object_attributes [Array] def self.add_attributes(inventory_object_attributes) inventory_object_attributes.each do |attr| define_method("#{attr}=") do |value| @@ -151,11 +178,68 @@ def self.add_attributes(inventory_object_attributes) private + # @return [Set] all model's writer names + def allowed_writers + return [] unless model_class + + # Get all writers of a model + @allowed_writers ||= (model_class.new.methods - Object.methods).grep(/^[\w]+?\=$/).to_set + end + + # @return [Set] all model's reader names + def allowed_readers + return [] unless model_class + + # Get all readers inferred from writers of a model + @allowed_readers ||= allowed_writers.map { |x| x.to_s.delete("=").to_sym }.to_set + end + + def method_missing(method_name, *arguments, &block) + if allowed_writers.include?(method_name) + self.class.define_data_writer(method_name) + public_send(method_name, arguments[0]) + elsif allowed_readers.include?(method_name) + self.class.define_data_reader(method_name) + public_send(method_name) + else + super + end + end + + def respond_to_missing?(method_name, _include_private = false) + allowed_writers.include?(method_name) || allowed_readers.include?(method_name) || super + end + + def self.define_data_writer(data_key) + define_method(data_key) do |value| + public_send(:[]=, data_key.to_s.delete("=").to_sym, value) + end + end + private_class_method :define_data_writer + + def self.define_data_reader(data_key) + define_method(data_key) do + public_send(:[], data_key) + end + end + private_class_method :define_data_reader + + # Return true passed key representing a getter is an association + # + # @param inventory_collection_scope [ManagerRefresh::InventoryCollection] + # @param key [Symbol] key representing getter + # @return [Boolean] true if the passed key points to association def association?(inventory_collection_scope, key) # Is the key an association on inventory_collection_scope model class? !inventory_collection_scope.association_to_foreign_key_mapping[key].nil? end + # Return true if the attribute is allowed to be saved into the DB + # + # @param inventory_collection_scope [ManagerRefresh::InventoryCollection] InventoryCollection object owning the + # attribute + # @param key [Symbol] attribute name + # @return true if the attribute is allowed to be saved into the DB def allowed?(inventory_collection_scope, key) foreign_to_association = inventory_collection_scope.foreign_key_to_association_mapping[key] || inventory_collection_scope.foreign_type_to_association_mapping[key] @@ -171,6 +255,10 @@ def allowed?(inventory_collection_scope, key) true end + # Return true if the object is loadable, which we determine by a list of loadable classes. + # + # @param value [Object] object we test + # @return true if the object is loadable def loadable?(value) value.kind_of?(::ManagerRefresh::InventoryObjectLazy) || value.kind_of?(::ManagerRefresh::InventoryObject) || value.kind_of?(::ManagerRefresh::ApplicationRecordReference)