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])