Class: Ecoportal::API::Common::Content::CollectionModel

Inherits:
DoubleModel
  • Object
show all
Includes:
Enumerable
Defined in:
lib/ecoportal/api/common/content/collection_model.rb

Overview

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

CollectionModel aims to deal with Arrays of actual objects.

Constant Summary

Constants inherited from DoubleModel

DoubleModel::NOT_USED

Constants included from ClassHelpers

Ecoportal::API::Common::Content::ClassHelpers::NOT_USED

Class Attribute Summary collapse

Attributes inherited from DoubleModel

#_key, #_parent, #_read_only

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from DoubleModel

#as_json, #as_update, #consolidate!, #dirty?, #doc, embeds_many, embeds_one, enforce!, #key, #key=, key?, new_uuid, #original_doc, pass_reader, pass_writer, passarray, passboolean, passdate, passforced, passkey, passthrough, #print_pretty, read_only!, read_only?, #read_only?, #replace_doc, #reset!, #resolved_doc_key, #root, #to_json

Methods included from ClassHelpers

#inheritable_attrs, #inheritable_class_vars, #inherited, #instance_variable_name, #new_class, #resolve_class, #to_constant, #to_time, #uid, #used_param?

Constructor Details

#initialize(ini_doc = [], parent: self, key: nil, read_only: false) ⇒ CollectionModel

Returns a new instance of CollectionModel.



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 106

def initialize(ini_doc = [], parent: self, key: nil, read_only: false)
  msg = "Undefined base 'klass' or 'new_item' callback for #{self.class}"
  raise msg unless self.class.klass?

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

  super(ini_doc, parent: parent, key: key, read_only: read_only)
end

Class Attribute Details

.klass(value = NOT_USED) {|doc| ... } ⇒ Class, ...

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

Resolves to the nuclear Class of the elements

Parameters:

  • value (Hash) (defaults to: NOT_USED)

    base doc (raw object) to create the object with

Yields:

  • (doc)

    identifies the target class of the raw object

Yield Parameters:

  • doc (Hash)

Yield Returns:

  • (Klass)

    the target class

Returns:

  • (Class, Proc, Hash)

    the target class

    • Hash tracks a symbol pending to be resovle from its referrer
    • Class an already resolve class
    • Proc a forker that pivots between multiple classes


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 39

def klass(value = NOT_USED, &block)
  @klass = block if block_given?

  if @klass.is_a?(Proc) && used_param?(value)
    @klass.call(value)
  elsif @klass && !@klass.is_a?(Proc) && !@klass.is_a?(Class)
    @klass = resolve_class(@klass, exception: false)
    @klass
  else
    @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

.order_keyObject

Returns the value of attribute order_key.



11
12
13
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 11

def order_key
  @order_key
end

.order_mattersObject

Returns the value of attribute order_matters.



11
12
13
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 11

def order_matters
  @order_matters
end

Class Method Details

.items_keyObject

The attr that has been defined as passkey in the item class



15
16
17
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 15

def items_key
  @items_key ||= "id"
end

.items_key=(value) ⇒ Object



19
20
21
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 19

def items_key=(value)
  @items_key = value && value.to_s.freeze
end

.klass?Boolean

Returns are there the factory logics to build item objects defined?.

Returns:

  • (Boolean)

    are there the factory logics to build item objects defined?



57
58
59
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 57

def klass?
  @klass || @new_item
end

.new_item(doc = NOT_USED, parent: nil, key: nil, read_only: false) {|doc, parent, key| ... } ⇒ Klass

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
Note:

if block is given, it ignores doc

Generates a new object of the target class

Parameters:

  • doc (Hash) (defaults to: NOT_USED)

    doc to parse

Yields:

  • (doc, parent, key)

    creates an object instance of the target klass

Yield Parameters:

  • doc (Hash)

Yield Returns:

  • (Klass)

    instance object of the target klass

Returns:

  • (Klass)

    instance object of the target klass



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 83

def new_item(doc = NOT_USED, parent: nil, key: nil, read_only: false, &block)
  if block_given?
    @new_item = block
    return
  end

  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

.new_item_class_based?Boolean

Optimization

Returns:

  • (Boolean)


62
63
64
65
66
67
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 62

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

Instance Method Details

#[](value) ⇒ Object

Get an element usign the key.

Parameters:

Returns:

  • (Object)

    the items_class element object



190
191
192
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 190

def [](value)
  items_by_key[get_key(value)]
end

#_doc_key(value) ⇒ Object

Note:
  • The name of the method is after the paren't class method
  • This method would have been better called _doc_pos :)

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



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 136

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
    show_str =
      case value
      when Hash
        value.pretty_inspect
      when Content::DoubleModel
        "#{value} with key: #{value.class.key} (items_key: #{self.class.items_key})"
      else
        value
      end

    raise UnlinkedModel, "Can't find child: #{show_str}"
  end
end

#_doc_pos(value) ⇒ Object



128
129
130
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 128

def _doc_pos(value)
  _doc_key(value)
end

#_itemsObject



176
177
178
179
180
181
182
183
184
185
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 176

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
    @_items = elements if read_only?
  end
end

#clearObject

Deletes all the elements of this CollectionModel instance



230
231
232
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 230

def clear
  to_a.each {|item| delete!(item)}
end

#delete!(value) ⇒ Object

Deletes value from this CollectionModel instance

Parameters:

  • 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


239
240
241
242
243
244
245
246
247
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 239

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
  return unless (item = self[value])
  _doc_delete(item.doc)
  @indexed = false
  _items.delete(item)
end

#each(&block) ⇒ Object



171
172
173
174
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 171

def each(&block)
  return to_enum(:each) unless block
  _items.each(&block)
end

#empty?Boolean

Returns:

  • (Boolean)


163
164
165
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 163

def empty?
  count&.zero?
end

#include?(value) ⇒ Boolean

Checks if an element exists in the collection

Parameters:

Returns:

  • (Boolean)

    whether or not it is included



197
198
199
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 197

def include?(value)
  items_by_key.key?(get_key(value))
end

#items_classClass

Returns the class of the elements of the Collection.

Returns:

  • (Class)

    the class of the elements of the Collection



124
125
126
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 124

def items_class
  self.class.klass
end

#lengthObject



159
160
161
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 159

def length
  count
end

#present?Boolean

Returns:

  • (Boolean)


167
168
169
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 167

def present?
  count&.positive?
end

#upsert!(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED) ⇒ Object

Tries to find the element value, if it exists, it updates it Otherwise it pushes it to the end

Returns:

  • (Object)

    the items_class element object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 210

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. Given #{value.class}"
  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 |itm|
    yield(itm) if block_given?
  end
end

#values_at(*keys) ⇒ Array<Object>

Returns the items_class element object.

Returns:

  • (Array<Object>)

    the items_class element object



202
203
204
# File 'lib/ecoportal/api/common/content/collection_model.rb', line 202

def values_at(*keys)
  keys.map {|key| self[key]}
end