ecoportal-api-v2 2.0.12 → 2.0.15

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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/CHANGELOG.md +29 -3
  4. data/ecoportal-api-v2.gemspec +1 -1
  5. data/lib/ecoportal/api/common/content/array_model.rb +20 -3
  6. data/lib/ecoportal/api/common/content/class_helpers.rb +4 -2
  7. data/lib/ecoportal/api/common/content/client.rb +12 -6
  8. data/lib/ecoportal/api/common/content/collection_model/doc/items.rb +33 -0
  9. data/lib/ecoportal/api/common/content/collection_model/doc/rooted_key.rb +68 -0
  10. data/lib/ecoportal/api/common/content/collection_model/doc.rb +25 -0
  11. data/lib/ecoportal/api/common/content/collection_model/doc_mutation/delete.rb +33 -0
  12. data/lib/ecoportal/api/common/content/collection_model/doc_mutation/position.rb +42 -0
  13. data/lib/ecoportal/api/common/content/collection_model/doc_mutation/upsert.rb +45 -0
  14. data/lib/ecoportal/api/common/content/collection_model/doc_mutation.rb +27 -0
  15. data/lib/ecoportal/api/common/content/collection_model/model/cache.rb +44 -0
  16. data/lib/ecoportal/api/common/content/collection_model/model/items.rb +43 -0
  17. data/lib/ecoportal/api/common/content/collection_model/model/iterable.rb +44 -0
  18. data/lib/ecoportal/api/common/content/collection_model/model/lookup.rb +45 -0
  19. data/lib/ecoportal/api/common/content/collection_model/model/numeric_key.rb +40 -0
  20. data/lib/ecoportal/api/common/content/collection_model/model/var_tracking.rb +50 -0
  21. data/lib/ecoportal/api/common/content/collection_model/model.rb +33 -0
  22. data/lib/ecoportal/api/common/content/collection_model/modifiers/items_key.rb +57 -0
  23. data/lib/ecoportal/api/common/content/collection_model/modifiers/items_klass.rb +144 -0
  24. data/lib/ecoportal/api/common/content/collection_model/modifiers/items_order.rb +38 -0
  25. data/lib/ecoportal/api/common/content/collection_model/modifiers.rb +27 -0
  26. data/lib/ecoportal/api/common/content/collection_model/mutation/clear.rb +30 -0
  27. data/lib/ecoportal/api/common/content/collection_model/mutation/delete.rb +42 -0
  28. data/lib/ecoportal/api/common/content/collection_model/mutation/upsert.rb +56 -0
  29. data/lib/ecoportal/api/common/content/collection_model/mutation.rb +27 -0
  30. data/lib/ecoportal/api/common/content/collection_model.rb +19 -351
  31. data/lib/ecoportal/api/common/content/doc_helpers.rb +18 -16
  32. data/lib/ecoportal/api/common/content/double_model/attributable/base.rb +23 -0
  33. data/lib/ecoportal/api/common/content/double_model/attributable/enforce.rb +62 -0
  34. data/lib/ecoportal/api/common/content/double_model/attributable/nesting/cascaded_callback.rb +104 -0
  35. data/lib/ecoportal/api/common/content/double_model/attributable/nesting/embeddable.rb +76 -0
  36. data/lib/ecoportal/api/common/content/double_model/attributable/nesting/keyable.rb +119 -0
  37. data/lib/ecoportal/api/common/content/double_model/attributable/nesting.rb +136 -0
  38. data/lib/ecoportal/api/common/content/double_model/attributable/passthrough.rb +70 -0
  39. data/lib/ecoportal/api/common/content/double_model/attributable/simple.rb +48 -0
  40. data/lib/ecoportal/api/common/content/double_model/attributable.rb +30 -0
  41. data/lib/ecoportal/api/common/content/double_model/base.rb +29 -0
  42. data/lib/ecoportal/api/common/content/double_model/diffable_model.rb +43 -0
  43. data/lib/ecoportal/api/common/content/double_model/double_doc/base.rb +23 -0
  44. data/lib/ecoportal/api/common/content/double_model/double_doc/linkable_doc.rb +87 -0
  45. data/lib/ecoportal/api/common/content/double_model/double_doc/replaceable_doc.rb +61 -0
  46. data/lib/ecoportal/api/common/content/double_model/double_doc/reset_consolidate.rb +54 -0
  47. data/lib/ecoportal/api/common/content/double_model/double_doc/rooted_key.rb +49 -0
  48. data/lib/ecoportal/api/common/content/double_model/double_doc.rb +31 -0
  49. data/lib/ecoportal/api/common/content/double_model/hash_helpers.rb +40 -0
  50. data/lib/ecoportal/api/common/content/double_model/modifiers/read_only_able.rb +73 -0
  51. data/lib/ecoportal/api/common/content/double_model/modifiers/rootable.rb +64 -0
  52. data/lib/ecoportal/api/common/content/double_model/modifiers.rb +24 -0
  53. data/lib/ecoportal/api/common/content/double_model/parented.rb +22 -0
  54. data/lib/ecoportal/api/common/content/double_model/var_tracking.rb +45 -0
  55. data/lib/ecoportal/api/common/content/double_model.rb +28 -486
  56. data/lib/ecoportal/api/common/content/hash_diff_patch.rb +2 -1
  57. data/lib/ecoportal/api/common/content/includer.rb +16 -0
  58. data/lib/ecoportal/api/common/content/model_helpers.rb +14 -16
  59. data/lib/ecoportal/api/common/content.rb +1 -0
  60. data/lib/ecoportal/api/v2/page/component.rb +46 -46
  61. data/lib/ecoportal/api/v2/page/components.rb +2 -2
  62. data/lib/ecoportal/api/v2/page.rb +14 -14
  63. data/lib/ecoportal/api/v2/pages/page_stage/task.rb +2 -2
  64. data/lib/ecoportal/api/v2/pages/page_stage/tasks.rb +3 -3
  65. data/lib/ecoportal/api/v2/pages/page_stage.rb +8 -8
  66. data/lib/ecoportal/api/v2/pages/stages.rb +2 -2
  67. data/lib/ecoportal/api/v2/pages.rb +15 -15
  68. data/lib/ecoportal/api/v2/registers.rb +20 -19
  69. data/lib/ecoportal/api/v2/s3/files/batch_upload.rb +1 -0
  70. data/lib/ecoportal/api/v2/s3/files/poll.rb +5 -5
  71. data/lib/ecoportal/api/v2/s3/files/poll_status.rb +3 -3
  72. data/lib/ecoportal/api/v2/s3/files.rb +6 -6
  73. data/lib/ecoportal/api/v2/s3.rb +5 -5
  74. data/lib/ecoportal/api/v2.rb +18 -8
  75. data/lib/ecoportal/api/v2_version.rb +1 -1
  76. metadata +50 -4
@@ -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
- class << self
10
- attr_writer :klass
11
- attr_accessor :order_matters, :order_key
12
-
13
- # The attr that has been defined as `passkey`
14
- # in the item class
15
- def items_key
16
- @items_key ||= "id"
17
- end
18
-
19
- def items_key=(value)
20
- @items_key = value && value.to_s.freeze
21
- end
22
-
23
- # Resolves to the nuclear `Class` of the elements
24
- # @note
25
- # - use block to define `klass` callback
26
- # @note When `klass` is resolved, if the items are of type
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: "page")
9
+ def get_body(doc, level: 'page')
11
10
  {}.tap do |body|
12
- body["#{level}"] = case
13
- when doc.respond_to?(:as_update)
11
+ body[level.to_s] =
12
+ if doc.respond_to?(:as_update)
14
13
  doc.as_update
15
- when doc.respond_to?(:as_json)
14
+ elsif doc.respond_to?(:as_json)
16
15
  HashDiffPatch.patch_diff(doc.as_json, nil)
17
- when doc.is_a?(Hash)
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 = nil
31
- id ||= doc.id if doc.respond_to?(:id)
32
- id ||= doc["id"] if doc.is_a?(Hash)
33
- id ||= doc if doc.is_a?(String)
34
- raise "No ID has been given!" unless id || !exception
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
- if idx = array_id_index(arr, id)
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