ecoportal-api-v2 1.1.6 → 1.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,7 +5,7 @@ module Ecoportal
5
5
  module Content
6
6
  module ClassHelpers
7
7
  include Common::BaseClass
8
- NOT_USED = "no_used!"
8
+ NOT_USED = "no_used!".freeze
9
9
 
10
10
  # Class resolver
11
11
  # @note it caches the resolved `klass`es
@@ -18,21 +18,21 @@ module Ecoportal
18
18
  @resolved ||= {}
19
19
  @resolved[klass] ||=
20
20
  case klass
21
- when Class
22
- klass
23
- when String
24
- begin
25
- Kernel.const_get(klass)
26
- rescue NameError => e
27
- raise if exception
28
- end
29
- when Symbol
30
- source_class.resolve_class(source_class.send(klass))
31
- when Hash
32
- referrer, referred = klass.first
33
- resolve_class(referred, source_class: referrer, exception: exception)
34
- else
35
- raise "Unknown class: #{klass}" if exception
21
+ when Class
22
+ klass
23
+ when String
24
+ begin
25
+ Kernel.const_get(klass)
26
+ rescue NameError
27
+ raise if exception
28
+ end
29
+ when Symbol
30
+ source_class.resolve_class(source_class.send(klass))
31
+ when Hash
32
+ referrer, referred = klass.first
33
+ resolve_class(referred, source_class: referrer, exception: exception)
34
+ else
35
+ raise "Unknown class: #{klass}" if exception
36
36
  end
37
37
  end
38
38
 
@@ -41,11 +41,11 @@ module Ecoportal
41
41
  # @param key [String, Symbol] to be normalized
42
42
  # @return [String] a correct constant name
43
43
  def to_constant(key)
44
- str_name = key.to_s.strip.split(/::/).compact.map do |str|
44
+ key.to_s.strip.split(/::/).compact.map do |str|
45
45
  str.slice(0).upcase + str.slice(1..-1)
46
- end.join("").split(/[\-\_ :]+/i).compact.map do |str|
46
+ end.join.split(/[\-\_ :]+/i).compact.map do |str|
47
47
  str.slice(0).upcase + str.slice(1..-1)
48
- end.join("")
48
+ end.join
49
49
  end
50
50
 
51
51
  # Helper to create an instance variable `name`
@@ -60,7 +60,7 @@ module Ecoportal
60
60
  # Generates random ids in hexadecimal to use in class name generation.
61
61
  # @param len [Integeter] length of the `uid`
62
62
  # @return [String] a random unique id of length `len`
63
- def uid(len = 8);
63
+ def uid(len = 8)
64
64
  SecureRandom.hex(len/2)
65
65
  end
66
66
 
@@ -72,8 +72,8 @@ module Ecoportal
72
72
  # @yieldparam child_class [Class] the new class
73
73
  # @return [Class] the new generated class
74
74
  def new_class(name = "Child#{uid}", inherits: self, namespace: inherits)
75
- name = name.to_s.to_sym.freeze
76
- class_name = to_constant(name)
75
+ name = name.to_s.to_sym.freeze
76
+ class_name = to_constant(name)
77
77
 
78
78
  unless target_class = resolve_class("#{namespace}::#{class_name}", exception: false)
79
79
  target_class = Class.new(inherits)
@@ -92,19 +92,17 @@ module Ecoportal
92
92
  # @return
93
93
  def to_time(value, exception: true)
94
94
  case value
95
- when NilClass
95
+ when Time, NilClass
96
96
  value
97
97
  when String
98
98
  begin
99
99
  Time.parse(value)
100
- rescue ArgumentArgument => e
100
+ rescue ArgumentArgument
101
101
  raise if exception
102
102
  nil
103
103
  end
104
104
  when Date
105
105
  Time.parse(value.to_s)
106
- when Time
107
- value
108
106
  else
109
107
  to_time(value.to_s) if value.respond_to?(:to_s)
110
108
  end
@@ -147,6 +145,7 @@ module Ecoportal
147
145
  # - mutating methods would reflect the changes on other classes as well
148
146
  # - therefore, `freeze` will be called on the values that are inherited.
149
147
  def inherited(subclass)
148
+ super
150
149
  inheritable_class_vars.each do |var|
151
150
  instance_var = instance_variable_name(var)
152
151
  value = instance_variable_get(instance_var)
@@ -6,7 +6,6 @@ module Ecoportal
6
6
  # @note to be able to refer to the correct element of the Collection,
7
7
  # it is required that those elements have a unique `key` that allows to identify them
8
8
  class CollectionModel < Content::DoubleModel
9
-
10
9
  class << self
11
10
  attr_writer :klass
12
11
  attr_accessor :order_matters, :order_key
@@ -26,24 +25,27 @@ module Ecoportal
26
25
  # - use block to define `klass` callback
27
26
  # @note When `klass` is resolved, if the items are of type
28
27
  # `DoubleModel`, it sets on the collection class the `items_key`
28
+ # @note when `klass` is directly resolved (not via doc) only once
29
+ # it will set @klass as resolved and will use this class from now on.
30
+ # This is an optimization to cut class lookups
29
31
  # @param value [Hash] base `doc` (raw object) to create the object with
30
32
  # @yield [doc] identifies the target `class` of the raw object
31
33
  # @yieldparam doc [Hash]
32
34
  # @yieldreturn [Klass] the target `class`
33
- # @return [Klass] the target `class`
35
+ # @return [Class, Proc, Hash] the target `class`
36
+ # - `Hash` tracks a symbol pending to be resovle from its referrer
37
+ # - `Class` an already resolve class
38
+ # - `Proc` a forker that pivots between multiple classes
34
39
  def klass(value = NOT_USED, &block)
35
- if block
36
- @klass = block
37
- block.call(value) if value != NOT_USED
40
+ @klass = block if block_given?
41
+
42
+ if @klass.is_a?(Proc) && used_param?(value)
43
+ @klass.call(value)
44
+ elsif @klass && !@klass.is_a?(Proc) && !@klass.is_a?(Class)
45
+ @klass = resolve_class(@klass, exception: false)
38
46
  @klass
39
- elsif used_param?(value)
40
- if @klass.is_a?(Proc)
41
- @klass.call(value)
42
- else
43
- resolve_class(@klass, exception: false)
44
- end
45
47
  else
46
- resolve_class(@klass, exception: false)
48
+ @klass
47
49
  end.tap do |result|
48
50
  next unless result.is_a?(Class)
49
51
  next unless result < Ecoportal::API::Common::Content::DoubleModel
@@ -51,6 +53,19 @@ module Ecoportal
51
53
  end
52
54
  end
53
55
 
56
+ # @return [Boolean] are there the factory logics to build item objects defined?
57
+ def klass?
58
+ @klass || @new_item
59
+ end
60
+
61
+ # Optimization
62
+ def new_item_class_based?
63
+ return false if @new_item.is_a?(Proc)
64
+ return false if klass.is_a?(Proc)
65
+ return true if klass.is_a?(Class)
66
+ false
67
+ end
68
+
54
69
  # Generates a new object of the target class
55
70
  # @note
56
71
  # - use block to define `new_item` callback, which will prevail over `klass`
@@ -60,36 +75,27 @@ module Ecoportal
60
75
  # @yield [doc, parent, key] creates an object instance of the target `klass`
61
76
  # @yieldparam doc [Hash]
62
77
  # @yieldreturn [Klass] instance object of the target `klass`
78
+ # @parent [CollectionModel] the parent of the new item
79
+ # @key [Symbol, String] the key value to access the item within collection
80
+ # Please observe that items in a CollectionModel are identified via their key attr.
81
+ # Meaning that there is actually no need to define this argument.
63
82
  # @return [Klass] instance object of the target `klass`
64
83
  def new_item(doc = NOT_USED, parent: nil, key: nil, read_only: false, &block)
65
- if block
84
+ if block_given?
66
85
  @new_item = block
67
- elsif used_param?(doc)
68
- raise "You should define either a 'klass' or a 'new_item' callback first" unless klass?
69
- if @new_item
70
- @new_item.call(doc, parent, key)
71
- else
72
- if target_class = self.klass(doc)
73
- doc.is_a?(target_class) ? doc : target_class.new(doc, parent: parent, key: key, read_only: read_only)
74
- else
75
- raise "Could not find a class for: #{doc}"
76
- end
77
- end
78
- else
79
- raise "To define the 'new_item' callback (factory), you need to use a block"
86
+ return
80
87
  end
81
- end
82
88
 
83
- # @return [Boolean] are there the factory logics to build item objects defined?
84
- def klass?
85
- @klass || @new_item
86
- end
89
+ msg = "To define the 'new_item' callback (factory), you need to use a block"
90
+ raise msg unless used_param?(doc)
91
+ msg = "You should define either a 'klass' or a 'new_item' callback first"
92
+ raise msg unless klass?
93
+ return @new_item.call(doc, parent, key) if @new_item.is_a?(Proc)
87
94
 
88
- def doc_class(name)
89
- dim_class = new_class(name, inherits: Common::Content::ArrayModel) do |klass|
90
- klass.order_matters = order_matters
91
- klass.uniq = uniq
92
- end
95
+ raise "Could not find a class for: #{doc}" unless (target_class = klass(doc))
96
+ return doc if doc.is_a?(target_class)
97
+
98
+ target_class.new(doc, parent: parent, key: key, read_only: read_only)
93
99
  end
94
100
  end
95
101
 
@@ -98,18 +104,18 @@ module Ecoportal
98
104
  inheritable_class_vars :klass, :order_matters, :order_key, :items_key, :new_item
99
105
 
100
106
  def initialize(ini_doc = [], parent: self, key: nil, read_only: false)
101
- unless self.class.klass?
102
- raise "Undefined base 'klass' or 'new_item' callback for #{self.class}"
103
- end
104
-
105
- ini_doc = case ini_doc
106
- when Array
107
- ini_doc
108
- when Enumerable
109
- ini_doc.to_a
110
- else
111
- []
112
- end
107
+ msg = "Undefined base 'klass' or 'new_item' callback for #{self.class}"
108
+ raise msg unless self.class.klass?
109
+
110
+ ini_doc =
111
+ case ini_doc
112
+ when Array
113
+ ini_doc
114
+ when Enumerable
115
+ ini_doc.to_a
116
+ else
117
+ []
118
+ end
113
119
 
114
120
  super(ini_doc, parent: parent, key: key, read_only: read_only)
115
121
  end
@@ -130,7 +136,7 @@ module Ecoportal
130
136
  def _doc_key(value)
131
137
  #print "*(#{value.class})"
132
138
  return super(value) unless value.is_a?(Hash) || value.is_a?(Content::DoubleModel)
133
- if id = get_key(value)
139
+ if (id = get_key(value))
134
140
  #print "^"
135
141
  _doc_items.index {|item| get_key(item) == id}.tap do |p|
136
142
  #print "{{#{p}}}"
@@ -150,9 +156,17 @@ module Ecoportal
150
156
  end
151
157
  end
152
158
 
153
- def length; count; end
154
- def empty?; count == 0; end
155
- def present?; count > 0; end
159
+ def length
160
+ count
161
+ end
162
+
163
+ def empty?
164
+ count&.zero?
165
+ end
166
+
167
+ def present?
168
+ count&.positive?
169
+ end
156
170
 
157
171
  def each(&block)
158
172
  return to_enum(:each) unless block
@@ -166,6 +180,7 @@ module Ecoportal
166
180
  _doc_items.each do |item_doc|
167
181
  elements << new_item(item_doc)
168
182
  end
183
+ @_items = elements if read_only?
169
184
  end
170
185
  end
171
186
 
@@ -198,7 +213,7 @@ module Ecoportal
198
213
  end
199
214
  item_doc = value.is_a?(Content::DoubleModel)? value.doc : value
200
215
  item_doc = JSON.parse(item_doc.to_json)
201
- if item = self[value]
216
+ if (item = self[value])
202
217
  item.replace_doc(item_doc)
203
218
  else
204
219
  _doc_upsert(item_doc, pos: pos, before: before, after: after).tap do |pos_idx|
@@ -206,14 +221,14 @@ module Ecoportal
206
221
  @indexed = false
207
222
  end
208
223
  end
209
- (item || self[item_doc]).tap do |item|
210
- yield(item) if block_given?
224
+ (item || self[item_doc]).tap do |itm|
225
+ yield(itm) if block_given?
211
226
  end
212
227
  end
213
228
 
214
229
  # Deletes all the elements of this `CollectionModel` instance
215
230
  def clear
216
- self.to_a.each {|item| delete!(item)}
231
+ to_a.each {|item| delete!(item)}
217
232
  end
218
233
 
219
234
  # Deletes `value` from this `CollectionModel` instance
@@ -225,18 +240,25 @@ module Ecoportal
225
240
  unless value.is_a?(Hash) || value.is_a?(Content::DoubleModel) || value.is_a?(String)
226
241
  raise "'Content::DoubleModel' or 'Hash' doc required"
227
242
  end
228
- if item = self[value]
229
- _doc_delete(item.doc)
230
- @indexed = false
231
- _items.delete(item)
232
- end
243
+ return unless (item = self[value])
244
+ _doc_delete(item.doc)
245
+ @indexed = false
246
+ _items.delete(item)
233
247
  end
234
248
 
235
249
  protected
236
250
 
237
- def order_matters?; self.class.order_matters; end
238
- def uniq?; self.class.uniq; end
239
- def items_key; self.class.items_key; end
251
+ def order_matters?
252
+ self.class.order_matters
253
+ end
254
+
255
+ def uniq?
256
+ self.class.uniq
257
+ end
258
+
259
+ def items_key
260
+ self.class.items_key
261
+ end
240
262
 
241
263
  def on_change
242
264
  @indexed = false
@@ -253,7 +275,7 @@ module Ecoportal
253
275
  when String
254
276
  value
255
277
  when Numeric
256
- get_key(self.to_a[value])
278
+ get_key(to_a[value])
257
279
  end
258
280
  end
259
281
 
@@ -275,7 +297,11 @@ module Ecoportal
275
297
  private
276
298
 
277
299
  def new_item(value)
278
- self.class.new_item(value, parent: self, read_only: self._read_only)
300
+ if self.class.new_item_class_based?
301
+ self.class.klass.new(value, parent: self, read_only: _read_only)
302
+ else
303
+ self.class.new_item(value, parent: self, read_only: _read_only)
304
+ end
279
305
  end
280
306
 
281
307
  # Helper to remove tracked down instance variables
@@ -297,48 +323,46 @@ module Ecoportal
297
323
  # Deletes `value` from `doc` (here referred as `_doc_items`)
298
324
  # @return [Object] the element deleted from `doc`
299
325
  def _doc_delete(value)
300
- if current_pos = _doc_key(value)
301
- _doc_items.delete_at(current_pos)
302
- end
326
+ return unless (current_pos = _doc_key(value))
327
+
328
+ _doc_items.delete_at(current_pos)
303
329
  end
304
330
 
305
331
  def _doc_upsert(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED)
306
- current_pos = if elem = self[value]
307
- _doc_key(elem)
308
- end
332
+ current_pos =
333
+ if (elem = self[value])
334
+ _doc_key(elem)
335
+ end
309
336
 
310
337
  pos = scope_position(pos: pos, before: before, after: after)
311
338
  pos ||= current_pos
312
339
 
313
340
  if current_pos && pos
314
341
  _doc_items.delete_at(current_pos)
315
- pos = (pos <= current_pos)? pos : pos - 1
342
+ pos = pos <= current_pos ? pos : pos - 1
316
343
  end
317
344
 
318
- pos = (pos && pos < _doc_items.length)? pos : _doc_items.length
319
- pos.tap do |i|
345
+ pos = (pos && pos < _doc_items.length)? pos : _doc_items.length # rubocop:disable Style/TernaryParentheses
346
+ pos.tap do |_i|
320
347
  _doc_items.insert(pos, value)
321
348
  end
322
-
323
349
  end
324
350
 
325
351
  def scope_position(pos: NOT_USED, before: NOT_USED, after: NOT_USED)
326
- case
327
- when used_param?(pos)
328
- if elem = self[pos]
352
+ if used_param?(pos)
353
+ if (elem = self[pos])
329
354
  _doc_key(elem) - 1
330
355
  end
331
- when used_param?(before)
332
- if elem = self[before]
356
+ elsif used_param?(before)
357
+ if (elem = self[before])
333
358
  _doc_key(elem) - 1
334
359
  end
335
- when used_param?(after)
336
- if elem = self[after]
360
+ elsif used_param?(after)
361
+ if (elem = self[after])
337
362
  _doc_key(elem)
338
363
  end
339
364
  end
340
365
  end
341
-
342
366
  end
343
367
  end
344
368
  end