lib/ecoportal/api/common/content/collection_model.rb in ecoportal-api-v2-1.1.6 vs lib/ecoportal/api/common/content/collection_model.rb in ecoportal-api-v2-1.1.7

- old
+ new

@@ -4,11 +4,10 @@ module Content # CollectionModel aims to deal with Arrays of actual objects. # @note to be able to refer to the correct element of the Collection, # it is required that those elements have a unique `key` that allows to identify them class CollectionModel < Content::DoubleModel - class << self attr_writer :klass attr_accessor :order_matters, :order_key # The attr that has been defined as `passkey` @@ -24,67 +23,75 @@ # Resolves to the nuclear `Class` of the elements # @note # - use block to define `klass` callback # @note When `klass` is resolved, if the items are of type # `DoubleModel`, it sets on the collection class the `items_key` + # @note when `klass` is directly resolved (not via doc) only once + # it will set @klass as resolved and will use this class from now on. + # This is an optimization to cut class lookups # @param value [Hash] base `doc` (raw object) to create the object with # @yield [doc] identifies the target `class` of the raw object # @yieldparam doc [Hash] # @yieldreturn [Klass] the target `class` # @return [Klass] the target `class` def klass(value = NOT_USED, &block) - if block - @klass = block - block.call(value) if value != NOT_USED + @klass = block if block_given? + + if @klass && !@class.is_a?(Proc) + @klass = resolve_class(@klass, exception: false) unless @klass.is_a?(Class) @klass - elsif used_param?(value) - if @klass.is_a?(Proc) - @klass.call(value) - else - resolve_class(@klass, exception: false) - end + elsif @klass.is_a?(Proc) && used_param?(value) + @klass.call(value) else - resolve_class(@klass, exception: false) + @klass end.tap do |result| next unless result.is_a?(Class) next unless result < Ecoportal::API::Common::Content::DoubleModel self.items_key = result.key end end + # @return [Boolean] are there the factory logics to build item objects defined? + def klass? + @klass || @new_item + end + + # Optimization + def new_item_class_based? + return false if @new_item.is_a?(Proc) + return false if @klass.is_a?(Proc) + return true if klass.is_a?(Class) + false + end + # Generates a new object of the target class # @note # - use block to define `new_item` callback, which will prevail over `klass` # - if `new_item` callback was **not** defined, it is required to defnie `klass` # @param doc [Hash] doc to parse # @note if block is given, it ignores `doc` # @yield [doc, parent, key] creates an object instance of the target `klass` # @yieldparam doc [Hash] # @yieldreturn [Klass] instance object of the target `klass` + # @parent [CollectionModel] the parent of the new item + # @key [Symbol, String] the key value to access the item within collection + # Please observe that items in a CollectionModel are identified via their key attr. + # Meaning that there is actually no need to define this argument. # @return [Klass] instance object of the target `klass` def new_item(doc = NOT_USED, parent: nil, key: nil, read_only: false, &block) - if block - @new_item = block - elsif used_param?(doc) - raise "You should define either a 'klass' or a 'new_item' callback first" unless klass? - if @new_item - @new_item.call(doc, parent, key) - else - if target_class = self.klass(doc) - doc.is_a?(target_class) ? doc : target_class.new(doc, parent: parent, key: key, read_only: read_only) - else - raise "Could not find a class for: #{doc}" - end - end - else - raise "To define the 'new_item' callback (factory), you need to use a block" - end - end + return (@new_item = block; nil) if block_given - # @return [Boolean] are there the factory logics to build item objects defined? - def klass? - @klass || @new_item + msg = "To define the 'new_item' callback (factory), you need to use a block" + raise msg unless used_param?(doc) + msg = "You should define either a 'klass' or a 'new_item' callback first" + raise msg unless klass? + return @new_item.call(doc, parent, key) if @new_item.is_a?(Proc) + + raise "Could not find a class for: #{doc}" unless target_class = klass(doc) + return doc if doc.is_a?(target_class) + + target_class.new(doc, parent: parent, key: key, read_only: read_only) end def doc_class(name) dim_class = new_class(name, inherits: Common::Content::ArrayModel) do |klass| klass.order_matters = order_matters @@ -164,10 +171,11 @@ [].tap do |elements| variable_set(:@_items, elements) _doc_items.each do |item_doc| elements << new_item(item_doc) end + @_items = elements if read_only? end end # Get an element usign the `key`. # @param value [String, Hash, Ecoportal::API::Common::Content::DoubleModel] @@ -273,10 +281,14 @@ end private def new_item(value) - self.class.new_item(value, parent: self, read_only: self._read_only) + if self.class.new_item_class_based? + self.class.klass.new(value, parent: self, read_only: self._read_only) + else + self.class.new_item(value, parent: self, read_only: self._read_only) + end end # Helper to remove tracked down instance variables def variable_remove!(key) if @items_by_key && (k = get_key(key)) && (item = @items_by_key[k])