class Ecoportal::API::Common::Content::CollectionModel

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

Attributes

klass[W]
order_key[RW]
order_matters[RW]

Public Class Methods

doc_class(name) click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 79
def doc_class(name)
  dim_class = new_class(name, inherits: Common::Content::ArrayModel) do |klass|
    klass.order_matters = order_matters
    klass.uniq          = uniq
  end
end
items_key() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 14
def items_key
  @items_key ||= "id"
end
items_key=(value) click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 18
def items_key=(value)
  @items_key = value && value.to_s.freeze
end
klass(value = NOT_USED, &block) click to toggle source

Resolves to the nuclear `Class` of the elements @note

- use block to define `klass` callback

@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`

# File lib/ecoportal/api/common/content/collection_model.rb, line 30
def klass(value = NOT_USED, &block)
  if block
    @klass = block
    block.call(value) if value != NOT_USED
  elsif used_param?(value)
    if @klass.is_a?(Proc)
      @klass.call(value)
    else
      resolve_class(@klass, exception: false)
    end
  else
    resolve_class(@klass, exception: false)
  end
end
klass?() click to toggle source

@return [Boolean] are there the factory logics to build item objects defined?

# File lib/ecoportal/api/common/content/collection_model.rb, line 75
def klass?
  @klass || @new_item
end
new(ini_doc = [], parent: self, key: nil) click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 90
def initialize(ini_doc = [], parent: self, key: nil)
  unless self.class.klass?
    raise "Undefined base 'klass' or 'new_item' callback for #{self.class}"
  end

  ini_doc = case ini_doc
            when Array
              ini_doc
            when Enumerable
              ini_doc.to_a
            else
              []
            end

  super(ini_doc, parent: parent, key: key)
end
new_item(doc = NOT_USED, parent: nil, key: nil, &block) click to toggle source

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` @return [Klass] instance object of the target `klass`

# File lib/ecoportal/api/common/content/collection_model.rb, line 55
def new_item(doc = NOT_USED, parent: nil, key: nil, &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)
      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

Public Instance Methods

[](value) click to toggle source

Get an element usign the `key`. @param value [String, Hash, Ecoportal::API::Common::Content::DoubleModel] @return [Object] the `items_class` element object

# File lib/ecoportal/api/common/content/collection_model.rb, line 151
def [](value)
  items_by_key[get_key(value)]
end
_doc_key(value) click to toggle source

Transforms `value` into the actual `key` to access the object in the doc `Array` @note

- The name of the method is after the paren't class method
- This method would have been better called `_doc_pos` :)
Calls superclass method Ecoportal::API::Common::Content::DoubleModel#_doc_key
# File lib/ecoportal/api/common/content/collection_model.rb, line 116
def _doc_key(value)
  #print "*(#{value.class})"
  return super(value) unless value.is_a?(Hash) || value.is_a?(Content::DoubleModel)
  if id = get_key(value)
    #print "^"
    _doc_items.index {|item| get_key(item) == id}.tap do |p|
      #print "{{#{p}}}"
    end
  else
    raise UnlinkedModel.new("Can't find child: #{value}")
  end
end
_items() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 138
def _items
  return @_items if @_items
  [].tap do |elements|
    variable_set(:@_items, elements)
    _doc_items.each do |item_doc|
      elements << new_item(item_doc)
    end
  end
end
delete!(value) click to toggle source

Deletes `value` from this `CollectionModel` instance @param value [String, Hash, Ecoportal::API::Common::Content::DoubleModel]

- When used as `String`, the `key` value (i.e. `id` value) is expected
- When used as `Hash`, it should be the `doc` of the target element
- When used as `DoubleModel`, it should be the specific object to be deleted
# File lib/ecoportal/api/common/content/collection_model.rb, line 188
def delete!(value)
  unless value.is_a?(Hash) || value.is_a?(Content::DoubleModel) || value.is_a?(String)
    raise "'Content::DoubleModel' or 'Hash' doc required"
  end
  if item = self[value]
    _doc_delete(item.doc)
    @indexed = false
    _items.delete(item)
  end
end
each(&block) click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 133
def each(&block)
  return to_enum(:each) unless block
  _items.each(&block)
end
empty?() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 130
def empty?;   count == 0; end
items_class() click to toggle source

@return [Class] the class of the elements of the Collection

# File lib/ecoportal/api/common/content/collection_model.rb, line 108
def items_class
  self.class.klass
end
length() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 129
def length;   count;      end
present?() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 131
def present?; count > 0;  end
upsert!(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED) { |item| ... } click to toggle source

Tries to find the element `value`, if it exists, it updates it

Otherwise it pushes it to the end

@value [Hash, Ecoportal::API::Common::Content::DoubleModel] the eleement to be added @return [Object] the `items_class` element object

# File lib/ecoportal/api/common/content/collection_model.rb, line 164
def upsert!(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED)
  unless value.is_a?(Hash) || value.is_a?(Content::DoubleModel)
    raise "'Content::DoubleModel' or 'Hash' doc required"
  end
  item_doc = value.is_a?(Content::DoubleModel)? value.doc : value
  item_doc = JSON.parse(item_doc.to_json)
  if item = self[value]
    item.replace_doc(item_doc)
  else
    _doc_upsert(item_doc, pos: pos, before: before, after: after).tap do |pos_idx|
      _items.insert(pos_idx, new_item(item_doc))
      @indexed = false
    end
  end
  (item || self[item_doc]).tap do |item|
    yield(item) if block_given?
  end
end
values_at(*keys) click to toggle source

@return [Array<Object>] the `items_class` element object

# File lib/ecoportal/api/common/content/collection_model.rb, line 156
def values_at(*keys)
  keys.map {|key| self[key]}
end

Protected Instance Methods

_doc_items() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 224
def _doc_items
  replace_doc([]) unless doc.is_a?(Array)
  doc
end
get_key(value) click to toggle source

Gets the `key` of the object `value`

# File lib/ecoportal/api/common/content/collection_model.rb, line 211
def get_key(value)
  case value
  when Content::DoubleModel
    value.key
  when Hash
    value[items_key]
  when String
    value
  when Numeric
    get_key(self.to_a[value])
  end
end
items_by_key() click to toggle source

@note it does not support a change of `id` on an existing item

# File lib/ecoportal/api/common/content/collection_model.rb, line 230
def items_by_key
  return @items_by_key if @indexed
  {}.tap do |hash|
    variable_set(:@items_by_key, hash)
    _items.each {|item| hash[item.key] = item}
    @indexed = true
  end
end
items_key() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 203
def items_key;      self.class.items_key;     end
on_change() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 205
def on_change
  @indexed = false
  #variables_remove!
end
order_matters?() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 201
def order_matters?; self.class.order_matters; end
uniq?() click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 202
def uniq?;          self.class.uniq;          end

Private Instance Methods

_doc_delete(value) click to toggle source

Deletes `value` from `doc` (here referred as `_doc_items`) @return [Object] the element deleted from `doc`

# File lib/ecoportal/api/common/content/collection_model.rb, line 263
def _doc_delete(value)
  if current_pos = _doc_key(value)
    _doc_items.delete_at(current_pos)
  end
end
_doc_upsert(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED) click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 269
def _doc_upsert(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED)
  current_pos = if elem = self[value]
    _doc_key(elem)
  end

  pos   = scope_position(pos: pos, before: before, after: after)
  pos ||= current_pos

  if current_pos && pos
    _doc_items.delete_at(current_pos)
    pos = (pos <= current_pos)? pos : pos - 1
  end

  pos = (pos && pos < _doc_items.length)? pos : _doc_items.length
  pos.tap do |i|
    _doc_items.insert(pos, value)
  end

end
new_item(value) click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 241
def new_item(value)
  self.class.new_item(value, parent: self)
end
scope_position(pos: NOT_USED, before: NOT_USED, after: NOT_USED) click to toggle source
# File lib/ecoportal/api/common/content/collection_model.rb, line 289
def scope_position(pos: NOT_USED, before: NOT_USED, after: NOT_USED)
  case
  when used_param?(pos)
    if elem = self[pos]
      _doc_key(elem) - 1
    end
  when used_param?(before)
    if elem = self[before]
      _doc_key(elem) - 1
    end
  when used_param?(after)
    if elem = self[after]
      _doc_key(elem)
    end
  end
end
variable_remove!(key) click to toggle source

Helper to remove tracked down instance variables

# File lib/ecoportal/api/common/content/collection_model.rb, line 246
def variable_remove!(key)
  if @items_by_key && (k = get_key(key)) && (item = @items_by_key[k])
    _items.delete(item) if _items.include?(item)
    @items_by_key.delete(k)
  else
    super(key)
  end
end
variables_remove!() click to toggle source

Removes all the persistent variables

# File lib/ecoportal/api/common/content/collection_model.rb, line 256
def variables_remove!
  @indexed = false
  super
end