ecoportal-api-v2 2.0.11 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/CHANGELOG.md +35 -1
- data/ecoportal-api-v2.gemspec +1 -1
- data/lib/ecoportal/api/common/content/array_model.rb +20 -3
- data/lib/ecoportal/api/common/content/class_helpers.rb +4 -2
- data/lib/ecoportal/api/common/content/client.rb +12 -6
- data/lib/ecoportal/api/common/content/collection_model/doc/items.rb +33 -0
- data/lib/ecoportal/api/common/content/collection_model/doc/rooted_key.rb +68 -0
- data/lib/ecoportal/api/common/content/collection_model/doc.rb +25 -0
- data/lib/ecoportal/api/common/content/collection_model/doc_mutation/delete.rb +33 -0
- data/lib/ecoportal/api/common/content/collection_model/doc_mutation/position.rb +42 -0
- data/lib/ecoportal/api/common/content/collection_model/doc_mutation/upsert.rb +45 -0
- data/lib/ecoportal/api/common/content/collection_model/doc_mutation.rb +27 -0
- data/lib/ecoportal/api/common/content/collection_model/model/cache.rb +44 -0
- data/lib/ecoportal/api/common/content/collection_model/model/items.rb +43 -0
- data/lib/ecoportal/api/common/content/collection_model/model/iterable.rb +44 -0
- data/lib/ecoportal/api/common/content/collection_model/model/lookup.rb +45 -0
- data/lib/ecoportal/api/common/content/collection_model/model/numeric_key.rb +40 -0
- data/lib/ecoportal/api/common/content/collection_model/model/var_tracking.rb +50 -0
- data/lib/ecoportal/api/common/content/collection_model/model.rb +33 -0
- data/lib/ecoportal/api/common/content/collection_model/modifiers/items_key.rb +57 -0
- data/lib/ecoportal/api/common/content/collection_model/modifiers/items_klass.rb +144 -0
- data/lib/ecoportal/api/common/content/collection_model/modifiers/items_order.rb +38 -0
- data/lib/ecoportal/api/common/content/collection_model/modifiers.rb +27 -0
- data/lib/ecoportal/api/common/content/collection_model/mutation/clear.rb +30 -0
- data/lib/ecoportal/api/common/content/collection_model/mutation/delete.rb +42 -0
- data/lib/ecoportal/api/common/content/collection_model/mutation/upsert.rb +56 -0
- data/lib/ecoportal/api/common/content/collection_model/mutation.rb +27 -0
- data/lib/ecoportal/api/common/content/collection_model.rb +19 -351
- data/lib/ecoportal/api/common/content/doc_helpers.rb +18 -16
- data/lib/ecoportal/api/common/content/double_model/attributable/base.rb +23 -0
- data/lib/ecoportal/api/common/content/double_model/attributable/enforce.rb +62 -0
- data/lib/ecoportal/api/common/content/double_model/attributable/nesting/cascaded_callback.rb +104 -0
- data/lib/ecoportal/api/common/content/double_model/attributable/nesting/embeddable.rb +76 -0
- data/lib/ecoportal/api/common/content/double_model/attributable/nesting/keyable.rb +119 -0
- data/lib/ecoportal/api/common/content/double_model/attributable/nesting.rb +136 -0
- data/lib/ecoportal/api/common/content/double_model/attributable/passthrough.rb +70 -0
- data/lib/ecoportal/api/common/content/double_model/attributable/simple.rb +48 -0
- data/lib/ecoportal/api/common/content/double_model/attributable.rb +30 -0
- data/lib/ecoportal/api/common/content/double_model/base.rb +29 -0
- data/lib/ecoportal/api/common/content/double_model/diffable_model.rb +43 -0
- data/lib/ecoportal/api/common/content/double_model/double_doc/base.rb +23 -0
- data/lib/ecoportal/api/common/content/double_model/double_doc/linkable_doc.rb +87 -0
- data/lib/ecoportal/api/common/content/double_model/double_doc/replaceable_doc.rb +61 -0
- data/lib/ecoportal/api/common/content/double_model/double_doc/reset_consolidate.rb +54 -0
- data/lib/ecoportal/api/common/content/double_model/double_doc/rooted_key.rb +49 -0
- data/lib/ecoportal/api/common/content/double_model/double_doc.rb +31 -0
- data/lib/ecoportal/api/common/content/double_model/hash_helpers.rb +40 -0
- data/lib/ecoportal/api/common/content/double_model/modifiers/read_only_able.rb +73 -0
- data/lib/ecoportal/api/common/content/double_model/modifiers/rootable.rb +64 -0
- data/lib/ecoportal/api/common/content/double_model/modifiers.rb +24 -0
- data/lib/ecoportal/api/common/content/double_model/parented.rb +22 -0
- data/lib/ecoportal/api/common/content/double_model/var_tracking.rb +45 -0
- data/lib/ecoportal/api/common/content/double_model.rb +28 -486
- data/lib/ecoportal/api/common/content/hash_diff_patch.rb +2 -1
- data/lib/ecoportal/api/common/content/includer.rb +16 -0
- data/lib/ecoportal/api/common/content/model_helpers.rb +14 -16
- data/lib/ecoportal/api/common/content.rb +1 -0
- data/lib/ecoportal/api/v2/page/component.rb +46 -46
- data/lib/ecoportal/api/v2/page/components.rb +2 -2
- data/lib/ecoportal/api/v2/page.rb +14 -14
- data/lib/ecoportal/api/v2/pages/page_stage/task.rb +2 -2
- data/lib/ecoportal/api/v2/pages/page_stage/tasks.rb +3 -3
- data/lib/ecoportal/api/v2/pages/page_stage.rb +8 -8
- data/lib/ecoportal/api/v2/pages/stages.rb +2 -2
- data/lib/ecoportal/api/v2/pages.rb +15 -15
- data/lib/ecoportal/api/v2/registers.rb +20 -19
- data/lib/ecoportal/api/v2/s3/files/batch_upload.rb +1 -0
- data/lib/ecoportal/api/v2/s3/files/poll.rb +5 -5
- data/lib/ecoportal/api/v2/s3/files/poll_status.rb +3 -3
- data/lib/ecoportal/api/v2/s3/files.rb +6 -6
- data/lib/ecoportal/api/v2/s3.rb +5 -5
- data/lib/ecoportal/api/v2.rb +18 -8
- data/lib/ecoportal/api/v2_version.rb +1 -1
- metadata +51 -5
@@ -6,363 +6,31 @@ 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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
31
|
-
# @param value [Hash] base `doc` (raw object) to create the object with
|
32
|
-
# @yield [doc] identifies the target `class` of the raw object
|
33
|
-
# @yieldparam doc [Hash]
|
34
|
-
# @yieldreturn [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
|
39
|
-
def klass(value = NOT_USED, &block)
|
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)
|
46
|
-
@klass
|
47
|
-
else
|
48
|
-
@klass
|
49
|
-
end.tap do |result|
|
50
|
-
next unless result.is_a?(Class)
|
51
|
-
next unless result < Ecoportal::API::Common::Content::DoubleModel
|
52
|
-
self.items_key = result.key
|
53
|
-
end
|
54
|
-
end
|
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
|
-
|
69
|
-
# Generates a new object of the target class
|
70
|
-
# @note
|
71
|
-
# - use block to define `new_item` callback, which will prevail over `klass`
|
72
|
-
# - if `new_item` callback was **not** defined, it is required to defnie `klass`
|
73
|
-
# @param doc [Hash] doc to parse
|
74
|
-
# @note if block is given, it ignores `doc`
|
75
|
-
# @yield [doc, parent, key] creates an object instance of the target `klass`
|
76
|
-
# @yieldparam doc [Hash]
|
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.
|
82
|
-
# @return [Klass] instance object of the target `klass`
|
83
|
-
def new_item(doc = NOT_USED, parent: nil, key: nil, read_only: false, &block)
|
84
|
-
if block_given?
|
85
|
-
@new_item = block
|
86
|
-
return
|
87
|
-
end
|
88
|
-
|
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)
|
94
|
-
|
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)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
include Enumerable
|
103
|
-
|
104
|
-
inheritable_class_vars :klass, :order_matters, :order_key, :items_key, :new_item
|
105
|
-
|
106
|
-
def initialize(ini_doc = [], parent: self, key: nil, read_only: false)
|
9
|
+
require 'ecoportal/api/common/content/collection_model/modifiers'
|
10
|
+
require 'ecoportal/api/common/content/collection_model/doc'
|
11
|
+
require 'ecoportal/api/common/content/collection_model/model'
|
12
|
+
require 'ecoportal/api/common/content/collection_model/doc_mutation'
|
13
|
+
require 'ecoportal/api/common/content/collection_model/mutation'
|
14
|
+
|
15
|
+
include Modifiers
|
16
|
+
include Doc
|
17
|
+
include Model
|
18
|
+
include DocMutation
|
19
|
+
include Mutation
|
20
|
+
|
21
|
+
def initialize(
|
22
|
+
ini_doc = [],
|
23
|
+
parent: self,
|
24
|
+
key: nil,
|
25
|
+
read_only: false
|
26
|
+
)
|
107
27
|
msg = "Undefined base 'klass' or 'new_item' callback for #{self.class}"
|
108
28
|
raise msg unless self.class.klass?
|
109
29
|
|
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
|
119
|
-
|
120
|
-
super(ini_doc, parent: parent, key: key, read_only: read_only)
|
121
|
-
end
|
122
|
-
|
123
|
-
# @return [Class] the class of the elements of the Collection
|
124
|
-
def items_class
|
125
|
-
self.class.klass
|
126
|
-
end
|
127
|
-
|
128
|
-
def _doc_pos(value)
|
129
|
-
_doc_key(value)
|
130
|
-
end
|
131
|
-
|
132
|
-
# Transforms `value` into the actual `key` to access the object in the doc `Array`
|
133
|
-
# @note
|
134
|
-
# - The name of the method is after the paren't class method
|
135
|
-
# - This method would have been better called `_doc_pos` :)
|
136
|
-
def _doc_key(value)
|
137
|
-
#print "*(#{value.class})"
|
138
|
-
return super(value) unless value.is_a?(Hash) || value.is_a?(Content::DoubleModel)
|
139
|
-
if (id = get_key(value))
|
140
|
-
#print "^"
|
141
|
-
_doc_items.index {|item| get_key(item) == id}.tap do |p|
|
142
|
-
#print "{{#{p}}}"
|
143
|
-
end
|
144
|
-
else
|
145
|
-
show_str =
|
146
|
-
case value
|
147
|
-
when Hash
|
148
|
-
value.pretty_inspect
|
149
|
-
when Content::DoubleModel
|
150
|
-
"#{value} with key: #{value.class.key} (items_key: #{self.class.items_key})"
|
151
|
-
else
|
152
|
-
value
|
153
|
-
end
|
154
|
-
|
155
|
-
raise UnlinkedModel, "Can't find child: #{show_str}"
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
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
|
170
|
-
|
171
|
-
def each(&block)
|
172
|
-
return to_enum(:each) unless block
|
173
|
-
_items.each(&block)
|
174
|
-
end
|
175
|
-
|
176
|
-
def _items
|
177
|
-
return @_items if @_items
|
178
|
-
[].tap do |elements|
|
179
|
-
variable_set(:@_items, elements)
|
180
|
-
_doc_items.each do |item_doc|
|
181
|
-
elements << new_item(item_doc)
|
182
|
-
end
|
183
|
-
@_items = elements if read_only?
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
# Get an element usign the `key`.
|
188
|
-
# @param value [String, Hash, Ecoportal::API::Common::Content::DoubleModel]
|
189
|
-
# @return [Object] the `items_class` element object
|
190
|
-
def [](value)
|
191
|
-
items_by_key[get_key(value)]
|
192
|
-
end
|
193
|
-
|
194
|
-
# Checks if an element exists in the collection
|
195
|
-
# @param value [String, Hash, Ecoportal::API::Common::Content::DoubleModel]
|
196
|
-
# @return [Boolean] whether or not it is included
|
197
|
-
def include?(value)
|
198
|
-
items_by_key.key?(get_key(value))
|
199
|
-
end
|
200
|
-
|
201
|
-
# @return [Array<Object>] the `items_class` element object
|
202
|
-
def values_at(*keys)
|
203
|
-
keys.map {|key| self[key]}
|
204
|
-
end
|
205
|
-
|
206
|
-
# Tries to find the element `value`, if it exists, it updates it
|
207
|
-
# Otherwise it pushes it to the end
|
208
|
-
# @value [Hash, Ecoportal::API::Common::Content::DoubleModel] the eleement to be added
|
209
|
-
# @return [Object] the `items_class` element object
|
210
|
-
def upsert!(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED)
|
211
|
-
unless value.is_a?(Hash) || value.is_a?(Content::DoubleModel)
|
212
|
-
raise "'Content::DoubleModel' or 'Hash' doc required. Given #{value.class}"
|
213
|
-
end
|
214
|
-
item_doc = value.is_a?(Content::DoubleModel)? value.doc : value
|
215
|
-
item_doc = JSON.parse(item_doc.to_json)
|
216
|
-
if (item = self[value])
|
217
|
-
item.replace_doc(item_doc)
|
218
|
-
else
|
219
|
-
_doc_upsert(item_doc, pos: pos, before: before, after: after).tap do |pos_idx|
|
220
|
-
_items.insert(pos_idx, new_item(item_doc))
|
221
|
-
@indexed = false
|
222
|
-
end
|
223
|
-
end
|
224
|
-
(item || self[item_doc]).tap do |itm|
|
225
|
-
yield(itm) if block_given?
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
# Deletes all the elements of this `CollectionModel` instance
|
230
|
-
def clear
|
231
|
-
to_a.each {|item| delete!(item)}
|
232
|
-
end
|
233
|
-
|
234
|
-
# Deletes `value` from this `CollectionModel` instance
|
235
|
-
# @param value [String, Hash, Ecoportal::API::Common::Content::DoubleModel]
|
236
|
-
# - When used as `String`, the `key` value (i.e. `id` value) is expected
|
237
|
-
# - When used as `Hash`, it should be the `doc` of the target element
|
238
|
-
# - When used as `DoubleModel`, it should be the specific object to be deleted
|
239
|
-
def delete!(value)
|
240
|
-
unless value.is_a?(Hash) || value.is_a?(Content::DoubleModel) || value.is_a?(String)
|
241
|
-
raise "'Content::DoubleModel' or 'Hash' doc required"
|
242
|
-
end
|
243
|
-
return unless (item = self[value])
|
244
|
-
_doc_delete(item.doc)
|
245
|
-
@indexed = false
|
246
|
-
_items.delete(item)
|
247
|
-
end
|
248
|
-
|
249
|
-
protected
|
250
|
-
|
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
|
262
|
-
|
263
|
-
def on_change
|
264
|
-
@indexed = false
|
265
|
-
#variables_remove!
|
266
|
-
end
|
267
|
-
|
268
|
-
# Gets the `key` of the object `value`
|
269
|
-
def get_key(value)
|
270
|
-
case value
|
271
|
-
when Content::DoubleModel
|
272
|
-
value.key
|
273
|
-
when Hash
|
274
|
-
value[items_key]
|
275
|
-
when String
|
276
|
-
value
|
277
|
-
when Numeric
|
278
|
-
get_key(to_a[value])
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
def _doc_items
|
283
|
-
replace_doc([]) unless doc.is_a?(Array)
|
284
|
-
doc
|
285
|
-
end
|
286
|
-
|
287
|
-
# @note it does not support a change of `id` on an existing item
|
288
|
-
def items_by_key
|
289
|
-
return @items_by_key if @indexed
|
290
|
-
{}.tap do |hash|
|
291
|
-
variable_set(:@items_by_key, hash)
|
292
|
-
_items.each {|item| hash[item.key] = item}
|
293
|
-
@indexed = true
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
private
|
298
|
-
|
299
|
-
def new_item(value)
|
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
|
305
|
-
end
|
306
|
-
|
307
|
-
# Helper to remove tracked down instance variables
|
308
|
-
def variable_remove!(key)
|
309
|
-
if @items_by_key && (k = get_key(key)) && (item = @items_by_key[k])
|
310
|
-
_items.delete(item) if _items.include?(item)
|
311
|
-
@items_by_key.delete(k)
|
312
|
-
else
|
313
|
-
super(key)
|
314
|
-
end
|
315
|
-
end
|
30
|
+
ini_doc = to_ini_doc(ini_doc)
|
316
31
|
|
317
|
-
# Removes all the persistent variables
|
318
|
-
def variables_remove!
|
319
|
-
@indexed = false
|
320
32
|
super
|
321
33
|
end
|
322
|
-
|
323
|
-
# Deletes `value` from `doc` (here referred as `_doc_items`)
|
324
|
-
# @return [Object] the element deleted from `doc`
|
325
|
-
def _doc_delete(value)
|
326
|
-
return unless (current_pos = _doc_key(value))
|
327
|
-
|
328
|
-
_doc_items.delete_at(current_pos)
|
329
|
-
end
|
330
|
-
|
331
|
-
def _doc_upsert(value, pos: NOT_USED, before: NOT_USED, after: NOT_USED)
|
332
|
-
elem = self[value]
|
333
|
-
current_pos = nil
|
334
|
-
current_pos = _doc_key(elem) if elem
|
335
|
-
|
336
|
-
pos = scope_position(pos: pos, before: before, after: after)
|
337
|
-
pos ||= current_pos
|
338
|
-
|
339
|
-
if current_pos && pos
|
340
|
-
_doc_items.delete_at(current_pos)
|
341
|
-
pos -= 1 unless pos <= current_pos
|
342
|
-
end
|
343
|
-
|
344
|
-
pos = _doc_items.length unless pos && pos < _doc_items.length
|
345
|
-
|
346
|
-
pos.tap do |_i|
|
347
|
-
_doc_items.insert(pos, value)
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
def scope_position(pos: NOT_USED, before: NOT_USED, after: NOT_USED)
|
352
|
-
if used_param?(pos)
|
353
|
-
if (elem = self[pos])
|
354
|
-
_doc_key(elem) - 1
|
355
|
-
end
|
356
|
-
elsif used_param?(before)
|
357
|
-
if (elem = self[before])
|
358
|
-
_doc_key(elem) - 1
|
359
|
-
end
|
360
|
-
elsif used_param?(after)
|
361
|
-
if (elem = self[after])
|
362
|
-
_doc_key(elem)
|
363
|
-
end
|
364
|
-
end
|
365
|
-
end
|
366
34
|
end
|
367
35
|
end
|
368
36
|
end
|
@@ -3,21 +3,20 @@ module Ecoportal
|
|
3
3
|
module Common
|
4
4
|
module Content
|
5
5
|
module DocHelpers
|
6
|
-
|
7
6
|
# Helper used to build the `body` of an HTTP request
|
8
7
|
# @param doc [Page, Hash] hashable object
|
9
8
|
# @return [Hash, nil] the `patch` formated `data` ready to include as `body` of a HTTP request
|
10
|
-
def get_body(doc, level:
|
9
|
+
def get_body(doc, level: 'page')
|
11
10
|
{}.tap do |body|
|
12
|
-
body[
|
13
|
-
|
11
|
+
body[level.to_s] =
|
12
|
+
if doc.respond_to?(:as_update)
|
14
13
|
doc.as_update
|
15
|
-
|
14
|
+
elsif doc.respond_to?(:as_json)
|
16
15
|
HashDiffPatch.patch_diff(doc.as_json, nil)
|
17
|
-
|
16
|
+
elsif doc.is_a?(Hash)
|
18
17
|
HashDiffPatch.patch_diff(doc, nil)
|
19
18
|
else
|
20
|
-
raise "Could not get body for doc: #{doc}"
|
19
|
+
raise ArgumentError, "Could not get body for doc: #{doc}"
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
@@ -27,11 +26,13 @@ module Ecoportal
|
|
27
26
|
# @param exception [Boolean] states if `id` **must** be present
|
28
27
|
# @return [Hash, nil] the `patch` formated `data` ready to include as `body` of a HTTP request
|
29
28
|
def get_id(doc, exception: true)
|
30
|
-
id
|
31
|
-
id ||= doc.id
|
32
|
-
id ||= doc[
|
33
|
-
id ||= doc
|
34
|
-
|
29
|
+
id = nil
|
30
|
+
id ||= doc.id if doc.respond_to?(:id)
|
31
|
+
id ||= doc['id'] if doc.is_a?(Hash)
|
32
|
+
id ||= doc if doc.is_a?(String)
|
33
|
+
|
34
|
+
raise 'No ID has been given!' unless id || !exception
|
35
|
+
|
35
36
|
id
|
36
37
|
end
|
37
38
|
|
@@ -40,6 +41,7 @@ module Ecoportal
|
|
40
41
|
# @return [Array<String>] the `id` s thereof
|
41
42
|
def array_ids(arr)
|
42
43
|
return [] if !arr.is_a?(Array) || arr.empty?
|
44
|
+
|
43
45
|
arr.map {|item| get_id(item, exception: false)}
|
44
46
|
end
|
45
47
|
|
@@ -48,6 +50,7 @@ module Ecoportal
|
|
48
50
|
# @return [Integer] the `position` thereof
|
49
51
|
def array_id_index(arr, id)
|
50
52
|
return unless arr.is_a?(Array)
|
53
|
+
|
51
54
|
arr.index {|item| get_id(item, exception: false) == id}
|
52
55
|
end
|
53
56
|
|
@@ -55,11 +58,10 @@ module Ecoportal
|
|
55
58
|
# @param doc [Array] the source Array
|
56
59
|
# @return [Integer] the `object` with that `id`
|
57
60
|
def array_id_item(arr, id)
|
58
|
-
|
59
|
-
arr[idx]
|
60
|
-
end
|
61
|
-
end
|
61
|
+
return unless (idx = array_id_index(arr, id))
|
62
62
|
|
63
|
+
arr[idx]
|
64
|
+
end
|
63
65
|
end
|
64
66
|
end
|
65
67
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ecoportal
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module Content
|
5
|
+
class DoubleModel
|
6
|
+
module Attributable
|
7
|
+
module Base
|
8
|
+
class << self
|
9
|
+
include Content::Includer
|
10
|
+
|
11
|
+
def included(base)
|
12
|
+
super
|
13
|
+
|
14
|
+
include_missing(base, DoubleModel::DoubleDoc)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Ecoportal
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module Content
|
5
|
+
class DoubleModel
|
6
|
+
module Attributable
|
7
|
+
module Enforce
|
8
|
+
class << self
|
9
|
+
include Content::Includer
|
10
|
+
|
11
|
+
def included(base)
|
12
|
+
super
|
13
|
+
base.extend Content::ClassHelpers
|
14
|
+
|
15
|
+
include_missing(base, Attributable::Passthrough)
|
16
|
+
|
17
|
+
base.extend ClassMethods
|
18
|
+
base.inheritable_class_vars :model_forced_keys
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# These are methods that should always be present in patch update
|
24
|
+
# @note
|
25
|
+
# - `DoubleModel` can be used with objects that do not use `patch_ver`
|
26
|
+
# - This ensures that they will get the correct patch update model.
|
27
|
+
# @param method [Symbol] the method that exposes the value
|
28
|
+
# as well as its `key` in the underlying `Hash` model.
|
29
|
+
# @param default [Value] the default value that
|
30
|
+
# this `key` will be written in the model when it doesn't exist.
|
31
|
+
def passforced(method, default:, read_only: false)
|
32
|
+
model_forced_keys[method.to_s.freeze] = default
|
33
|
+
passthrough(method, read_only: read_only)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Ensures `doc` has the `model_forced_keys`. If it doesn't, it adds those
|
37
|
+
# missing with the defined `default` values
|
38
|
+
def enforce!(doc)
|
39
|
+
return unless doc.is_a?(Hash)
|
40
|
+
return if model_forced_keys.empty?
|
41
|
+
|
42
|
+
model_forced_keys.each do |key, default|
|
43
|
+
doc[key] = default unless doc.key?(key)
|
44
|
+
end
|
45
|
+
|
46
|
+
doc
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# The list of keys that will be forced in the model
|
52
|
+
def model_forced_keys
|
53
|
+
@model_forced_keys ||= {}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Ecoportal
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module Content
|
5
|
+
class DoubleModel
|
6
|
+
module Attributable
|
7
|
+
module Nesting
|
8
|
+
class << self
|
9
|
+
def included(base)
|
10
|
+
super
|
11
|
+
base.extend Content::ClassHelpers
|
12
|
+
|
13
|
+
base.extend ClassMethods
|
14
|
+
base.inheritable_class_vars :_cascaded_attributes
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def _cascaded_attribute!(meth, doc_key = meth, inherited: false)
|
20
|
+
meth = meth.to_sym
|
21
|
+
doc_key = doc_key.to_s
|
22
|
+
|
23
|
+
dont_override = _cascaded_attributes.key?(meth) && inherited
|
24
|
+
return _cascaded_attributes[meth] if dont_override
|
25
|
+
|
26
|
+
_cascaded_attributes[meth] = doc_key
|
27
|
+
subclasses.each do |subclass|
|
28
|
+
subclass._cascaded_attribute!(meth, doc_key, inherited: true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def _cascaded_attributes
|
33
|
+
@_cascaded_attributes ||={}
|
34
|
+
end
|
35
|
+
|
36
|
+
def _cascaded_methods
|
37
|
+
_cascaded_attributes.keys
|
38
|
+
end
|
39
|
+
|
40
|
+
def _cascaded_doc_keys
|
41
|
+
_cascaded_attributes.values.uniq
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# INSTANCE METHODS
|
46
|
+
|
47
|
+
# We offer helpers to implement cascaded callbacks to nested objects
|
48
|
+
# i.e. `#as_update` or `#dirty?`
|
49
|
+
# @note this happens at instance level (not tracked at class level)
|
50
|
+
module CascadedCallback
|
51
|
+
def cascaded_callback(value = nil, method:, args: [], kargs: {}, path: [], &block)
|
52
|
+
method = method.to_sym
|
53
|
+
return value unless respond_to?(method, true)
|
54
|
+
|
55
|
+
value =
|
56
|
+
if path.empty?
|
57
|
+
send(method, *args, **kargs)
|
58
|
+
else
|
59
|
+
yield(value, send(method, *args, **kargs), path, self)
|
60
|
+
end
|
61
|
+
|
62
|
+
return value unless respond_to?(:_cascaded_attributes)
|
63
|
+
|
64
|
+
_cascaded_attributes.reduce(value) do |mem, (attribute, obj_k)|
|
65
|
+
next mem unless (obj = send(attribute))
|
66
|
+
next mem unless obj.respond_to(method, true)
|
67
|
+
|
68
|
+
obj_path = path.dup.push(obj_k)
|
69
|
+
|
70
|
+
unless obj.respond_to?(:cascaded_callback)
|
71
|
+
next yield(
|
72
|
+
mem,
|
73
|
+
obj.send(method, *args, **kargs),
|
74
|
+
obj_path,
|
75
|
+
obj
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
obj.cascaded_callback(
|
80
|
+
mem,
|
81
|
+
method: method,
|
82
|
+
path: obj_path,
|
83
|
+
&block
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def _cascaded_attribute!(...)
|
91
|
+
self.class._cascaded_attribute!(...)
|
92
|
+
end
|
93
|
+
|
94
|
+
def _cascaded_attributes
|
95
|
+
self.class._cascaded_attributes
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|