ecoportal-api-v2 2.0.15 → 3.1.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/ecoportal-api-v2.gemspec +1 -1
  4. data/lib/ecoportal/api/common/concerns.rb +2 -2
  5. data/lib/ecoportal/api/common/content/array_model.rb +1 -0
  6. data/lib/ecoportal/api/common/content/class_helpers.rb +42 -7
  7. data/lib/ecoportal/api/common/content/client.rb +2 -1
  8. data/lib/ecoportal/api/common/content/collection_model/doc/rooted_key.rb +8 -8
  9. data/lib/ecoportal/api/common/content/collection_model/doc.rb +2 -2
  10. data/lib/ecoportal/api/common/content/collection_model/doc_mutation.rb +3 -3
  11. data/lib/ecoportal/api/common/content/collection_model/model.rb +6 -6
  12. data/lib/ecoportal/api/common/content/collection_model/modifiers.rb +3 -3
  13. data/lib/ecoportal/api/common/content/collection_model/mutation.rb +3 -3
  14. data/lib/ecoportal/api/common/content/collection_model.rb +5 -5
  15. data/lib/ecoportal/api/common/content/double_model/attributable/nesting/cascaded_callback.rb +203 -98
  16. data/lib/ecoportal/api/common/content/double_model/attributable/nesting/embeddable.rb +49 -59
  17. data/lib/ecoportal/api/common/content/double_model/attributable/nesting/keyable.rb +102 -114
  18. data/lib/ecoportal/api/common/content/double_model/attributable/nesting.rb +8 -4
  19. data/lib/ecoportal/api/common/content/double_model/attributable.rb +5 -5
  20. data/lib/ecoportal/api/common/content/double_model/diffable/diff_service.rb +33 -0
  21. data/lib/ecoportal/api/common/content/double_model/{diffable_model.rb → diffable.rb} +12 -5
  22. data/lib/ecoportal/api/common/content/double_model/double_doc/linkable_doc.rb +9 -4
  23. data/lib/ecoportal/api/common/content/double_model/double_doc/replaceable_doc.rb +10 -2
  24. data/lib/ecoportal/api/common/content/double_model/double_doc/reset_consolidate.rb +1 -1
  25. data/lib/ecoportal/api/common/content/double_model/double_doc/rooted_key.rb +10 -3
  26. data/lib/ecoportal/api/common/content/double_model/double_doc.rb +5 -5
  27. data/lib/ecoportal/api/common/content/double_model/hash_helpers.rb +23 -4
  28. data/lib/ecoportal/api/common/content/double_model/modifiers.rb +2 -2
  29. data/lib/ecoportal/api/common/content/double_model.rb +9 -9
  30. data/lib/ecoportal/api/common/content/hash_diff_patch.rb +21 -7
  31. data/lib/ecoportal/api/common/content/model_helpers.rb +1 -1
  32. data/lib/ecoportal/api/common/content.rb +11 -11
  33. data/lib/ecoportal/api/common.v2.rb +2 -2
  34. data/lib/ecoportal/api/v2/page/component/action.rb +13 -11
  35. data/lib/ecoportal/api/v2/page/component/action_field.rb +14 -12
  36. data/lib/ecoportal/api/v2/page/component/actions_field.rb +2 -1
  37. data/lib/ecoportal/api/v2/page/component/chart_field/config.rb +1 -2
  38. data/lib/ecoportal/api/v2/page/component/chart_field.rb +48 -44
  39. data/lib/ecoportal/api/v2/page/component/chart_fr_field.rb +0 -1
  40. data/lib/ecoportal/api/v2/page/component/checklist_field.rb +9 -6
  41. data/lib/ecoportal/api/v2/page/component/checklist_item.rb +2 -3
  42. data/lib/ecoportal/api/v2/page/component/contractor_entities_field.rb +18 -17
  43. data/lib/ecoportal/api/v2/page/component/date_field.rb +19 -18
  44. data/lib/ecoportal/api/v2/page/component/file.rb +3 -3
  45. data/lib/ecoportal/api/v2/page/component/files_field.rb +9 -6
  46. data/lib/ecoportal/api/v2/page/component/gauge_field.rb +3 -2
  47. data/lib/ecoportal/api/v2/page/component/gauge_stop.rb +26 -26
  48. data/lib/ecoportal/api/v2/page/component/geo_field.rb +2 -2
  49. data/lib/ecoportal/api/v2/page/component/image.rb +6 -5
  50. data/lib/ecoportal/api/v2/page/component/images_field.rb +21 -20
  51. data/lib/ecoportal/api/v2/page/component/law.rb +3 -4
  52. data/lib/ecoportal/api/v2/page/component/law_field.rb +2 -2
  53. data/lib/ecoportal/api/v2/page/component/number_field.rb +1 -1
  54. data/lib/ecoportal/api/v2/page/component/people_field.rb +19 -19
  55. data/lib/ecoportal/api/v2/page/component/plain_text_field.rb +6 -7
  56. data/lib/ecoportal/api/v2/page/component/reference_field.rb +1 -1
  57. data/lib/ecoportal/api/v2/page/component/rich_text_field.rb +3 -3
  58. data/lib/ecoportal/api/v2/page/component/selection_field.rb +32 -35
  59. data/lib/ecoportal/api/v2/page/component/selection_options.rb +12 -7
  60. data/lib/ecoportal/api/v2/page/component/tag_field.rb +5 -4
  61. data/lib/ecoportal/api/v2/page/component.rb +21 -21
  62. data/lib/ecoportal/api/v2/page/force/bindings.rb +49 -39
  63. data/lib/ecoportal/api/v2/page/force.rb +9 -9
  64. data/lib/ecoportal/api/v2/page/forces.rb +1 -1
  65. data/lib/ecoportal/api/v2/page/permit.rb +3 -3
  66. data/lib/ecoportal/api/v2/page/sections.rb +19 -14
  67. data/lib/ecoportal/api/v2/page/stage.rb +27 -20
  68. data/lib/ecoportal/api/v2/page/stages.rb +1 -1
  69. data/lib/ecoportal/api/v2/page.rb +13 -12
  70. data/lib/ecoportal/api/v2/pages/page_stage.rb +2 -2
  71. data/lib/ecoportal/api/v2/pages.rb +2 -2
  72. data/lib/ecoportal/api/v2/registers/page_result.rb +3 -3
  73. data/lib/ecoportal/api/v2/registers/register.rb +4 -5
  74. data/lib/ecoportal/api/v2/registers/search_results.rb +1 -1
  75. data/lib/ecoportal/api/v2/registers/stages_result.rb +1 -1
  76. data/lib/ecoportal/api/v2/registers.rb +6 -6
  77. data/lib/ecoportal/api/v2/s3/files/batch_upload.rb +1 -0
  78. data/lib/ecoportal/api/v2/s3/files.rb +3 -3
  79. data/lib/ecoportal/api/v2.rb +4 -4
  80. data/lib/ecoportal/api/v2_version.rb +1 -1
  81. data/lib/ecoportal/api-v2.rb +4 -5
  82. metadata +6 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e562e79d5e559d13490b26e736d53be826122516d01ccea3a5639c39049f7ff
4
- data.tar.gz: c2f379c8afea06375065d479b5562c91d8c78dfcde07aa5bc785f67cec15844d
3
+ metadata.gz: 9f6265c8e79e49dfb8c121860260f99abf957c9f1649764ee66fcab3e7f136fd
4
+ data.tar.gz: 4de91b16581dbe57f9a23b8eb48a9fe64f365b558844f663d7471bf84a6dff75
5
5
  SHA512:
6
- metadata.gz: 4b64139336f7ab31f4a9870649fd03757036dad15d17a4c8a2ec331ec38a5afce38509242612c3adde99e45f551433c00498c9a9259dd5837fdc43550a48a2f2
7
- data.tar.gz: 0c9129094ab2faeb37c7f0e2ca50b385954e9f682016ee16234e5290b26d70838954c2806ba00b62e26c8310ed01a2f6c85226aae9ac62c57bdc5655a9733417
6
+ metadata.gz: e73d7760680b0a3966610195c674bd7aacc6a4b8e117140edc29d42de7cb47f69eda51a36900062bbe3043d9b6581a833994e34a32a738f979ae1a64497b5284
7
+ data.tar.gz: 584efdc89b150aad2ea3d0e214a11133fc56cc9b9335cb05bb0b994617dd2fc24187ccc5df46ea06bc1a450f3e212971aa8a30fc17b8ad6b025950f700a94c68
data/CHANGELOG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [2.0.16] - 2025-02-xx
5
+ ## [3.1.2] - 2025-05-xx
6
6
 
7
7
  ### Added
8
8
 
@@ -10,6 +10,35 @@ All notable changes to this project will be documented in this file.
10
10
 
11
11
  ### Fixed
12
12
 
13
+ ## [3.1.1] - 2025-05-xx
14
+
15
+ ### Added
16
+
17
+ - `Ecoportal::API::Common::Content::DoubleModel::HashHelpers`
18
+ - `Added` `#dig_path?` to check if `path` exists.
19
+
20
+ ### Changed
21
+
22
+ - upgraded `ecoportal-api` gem
23
+ - `Ecoportal::API::Common::Content::HashPatchDiff` as well as a **concern**.
24
+ - Renamed `Ecoportal::API::Common::Content::DoubleModel::DiffableModel` to
25
+ - `Ecoportal::API::Common::Content::DoubleModel::Diffable`
26
+ - **Move** `diff` to **service class**.
27
+ - `require` to `require_relative` where applicable.
28
+ - **Renamed** `#dig_set` and `#dig_delete` to `#dig_set!` and `#dig_delete!`
29
+
30
+ ### Fixed
31
+
32
+ - `inheritable_class_vars`
33
+ - Some objects should be **dupped** or even **cloned**
34
+ - `inherited` should be private
35
+
36
+ ## [2.0.16] - 2025-03-14
37
+
38
+ ### Fixed
39
+
40
+ - `passarray`: when `ready_only?`, `doc` should still be passed over on initialization.
41
+
13
42
  ## [2.0.15] - 2025-02-23
14
43
 
15
44
  ### Added
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency 'rubocop-rake', '~> 0'
34
34
  spec.add_development_dependency 'yard', '~> 0.9'
35
35
 
36
- spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.8'
36
+ spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.10'
37
37
  spec.add_dependency 'mime-types', '~> 3.5', '>= 3.5.2'
38
38
  end
39
39
 
@@ -7,5 +7,5 @@ module Ecoportal
7
7
  end
8
8
  end
9
9
 
10
- require 'ecoportal/api/common/concerns/threadable'
11
- require 'ecoportal/api/common/concerns/benchmarkable'
10
+ require_relative 'concerns/threadable'
11
+ require_relative 'concerns/benchmarkable'
@@ -33,6 +33,7 @@ module Ecoportal
33
33
  inheritable_class_vars :order_matteres, :uniq
34
34
 
35
35
  def initialize(doc = [], parent: self, key: nil, read_only: self.class.read_only?)
36
+ doc = [] unless doc.is_a?(Array)
36
37
  super
37
38
  end
38
39
 
@@ -128,7 +128,7 @@ module Ecoportal
128
128
  # Think if would be possible to join them somehow.
129
129
  def inheritable_class_vars(*vars)
130
130
  @inheritable_class_vars ||= [:inheritable_class_vars]
131
- @inheritable_class_vars += vars
131
+ @inheritable_class_vars |= vars
132
132
  end
133
133
 
134
134
  # Builds the attr_reader and attr_writer of `attrs` and registers the associated instance variable as inheritable.
@@ -140,22 +140,57 @@ module Ecoportal
140
140
  end # end
141
141
  DEF_CLSS_ATTR
142
142
  end
143
+
143
144
  inheritable_class_vars(*attrs)
144
145
  end
145
146
 
147
+ private
148
+
146
149
  # This callback method is called whenever a subclass of the current class is created.
147
- # @note
148
- # - values of the instance variables are copied as they are (no dups or clones)
149
- # - the above means: avoid methods that change the state of the mutable object on it
150
- # - mutating methods would reflect the changes on other classes as well
151
- # - therefore, `freeze` will be called on the values that are inherited.
150
+ # @note Mutating methods would reflect the changes on other classes as well, for this
151
+ # reason:
152
+ # - Immutable objects are copied as they are (i.e. `Symbol`)
153
+ # - Enumerables are deep dupped.
154
+ # - Mutable objects, such as `String` or `Date` are frozen (`freeze`). This
155
+ # might be changed later on and just dup them.
152
156
  def inherited(subclass)
153
157
  super
154
158
 
155
159
  inheritable_class_vars.each do |var|
156
160
  instance_var = instance_variable_name(var)
157
161
  value = instance_variable_get(instance_var)
158
- subclass.instance_variable_set(instance_var, value.freeze)
162
+ subclass.instance_variable_set(
163
+ instance_var,
164
+ freeze_or_dup(value)
165
+ )
166
+ end
167
+ end
168
+
169
+ def freeze_or_dup(value)
170
+ case value
171
+ when FalseClass, TrueClass, Symbol, NilClass, Numeric
172
+ value
173
+ when Date, Time, String, Range
174
+ value.freeze
175
+ when Enumerable
176
+ case value
177
+ when Array
178
+ value.map { |elem| freeze_or_dup(elem) }
179
+ when Hash
180
+ value.each.with_object({}) do |(key, val), out|
181
+ out[key] = freeze_or_dup(val)
182
+ end
183
+ else
184
+ return value.dup if respond_to?(:dup)
185
+ return value.clone if respond_to?(:clone)
186
+
187
+ value
188
+ end
189
+ else
190
+ return value.dup if respond_to?(:dup)
191
+ return value.clone if respond_to?(:clone)
192
+
193
+ value
159
194
  end
160
195
  end
161
196
  end
@@ -6,7 +6,8 @@ module Ecoportal
6
6
  class Client < Ecoportal::API::Common::Client
7
7
  attr_accessor :logger
8
8
 
9
- # @note the `api_key` will be automatically added as parameter `X-ECOPORTAL-API-KEY` in the header of the http requests.
9
+ # @note the `api_key` will be automatically added as parameter
10
+ # `X-ECOPORTAL-API-KEY` in the header of the http requests.
10
11
  def initialize(
11
12
  api_key:,
12
13
  version: 'v2',
@@ -25,9 +25,8 @@ module Ecoportal
25
25
  # - This method would have been better called `_doc_pos` :)
26
26
  # @note this method shouldn't be protected nor private. See DoubleModel#_rooted_doc_key
27
27
  def _rooted_doc_key(value)
28
- fetchable_position = value.is_a?(Hash) || value.is_a?(Content::DoubleModel)
29
28
  #print "*(#{value.class})"
30
- return super unless fetchable_position
29
+ return super unless fetchable_position?(value)
31
30
 
32
31
  if (id = get_key(value))
33
32
  # fetch position
@@ -37,9 +36,16 @@ module Ecoportal
37
36
  raise UnlinkedModel, "Can't find child: #{show_str}"
38
37
  end
39
38
  end
39
+ alias_method :_doc_pos, :_rooted_doc_key
40
40
 
41
41
  private
42
42
 
43
+ def fetchable_position?(value)
44
+ return true if value.is_a?(Hash)
45
+
46
+ value.is_a?(Content::DoubleModel)
47
+ end
48
+
43
49
  def cant_find_child_explanation(value)
44
50
  case value
45
51
  when Hash
@@ -53,12 +59,6 @@ module Ecoportal
53
59
  end
54
60
  end
55
61
  end
56
-
57
- # INSTANCE METHODS
58
-
59
- def _doc_pos(value)
60
- _rooted_doc_key(value)
61
- end
62
62
  end
63
63
  end
64
64
  end
@@ -1,5 +1,5 @@
1
- require 'ecoportal/api/common/content/collection_model/doc/items'
2
- require 'ecoportal/api/common/content/collection_model/doc/rooted_key'
1
+ require_relative 'doc/items'
2
+ require_relative 'doc/rooted_key'
3
3
 
4
4
  module Ecoportal
5
5
  module API
@@ -1,6 +1,6 @@
1
- require 'ecoportal/api/common/content/collection_model/doc_mutation/delete'
2
- require 'ecoportal/api/common/content/collection_model/doc_mutation/position'
3
- require 'ecoportal/api/common/content/collection_model/doc_mutation/upsert'
1
+ require_relative 'doc_mutation/delete'
2
+ require_relative 'doc_mutation/position'
3
+ require_relative 'doc_mutation/upsert'
4
4
 
5
5
  module Ecoportal
6
6
  module API
@@ -1,9 +1,9 @@
1
- require 'ecoportal/api/common/content/collection_model/model/items'
2
- require 'ecoportal/api/common/content/collection_model/model/iterable'
3
- require 'ecoportal/api/common/content/collection_model/model/numeric_key'
4
- require 'ecoportal/api/common/content/collection_model/model/cache'
5
- require 'ecoportal/api/common/content/collection_model/model/var_tracking'
6
- require 'ecoportal/api/common/content/collection_model/model/lookup'
1
+ require_relative 'model/items'
2
+ require_relative 'model/iterable'
3
+ require_relative 'model/numeric_key'
4
+ require_relative 'model/cache'
5
+ require_relative 'model/var_tracking'
6
+ require_relative 'model/lookup'
7
7
 
8
8
  module Ecoportal
9
9
  module API
@@ -1,6 +1,6 @@
1
- require 'ecoportal/api/common/content/collection_model/modifiers/items_key'
2
- require 'ecoportal/api/common/content/collection_model/modifiers/items_klass'
3
- require 'ecoportal/api/common/content/collection_model/modifiers/items_order'
1
+ require_relative 'modifiers/items_key'
2
+ require_relative 'modifiers/items_klass'
3
+ require_relative 'modifiers/items_order'
4
4
 
5
5
  module Ecoportal
6
6
  module API
@@ -1,6 +1,6 @@
1
- require 'ecoportal/api/common/content/collection_model/mutation/delete'
2
- require 'ecoportal/api/common/content/collection_model/mutation/upsert'
3
- require 'ecoportal/api/common/content/collection_model/mutation/clear'
1
+ require_relative 'mutation/delete'
2
+ require_relative 'mutation/upsert'
3
+ require_relative 'mutation/clear'
4
4
 
5
5
  module Ecoportal
6
6
  module API
@@ -6,11 +6,11 @@ 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
- 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'
9
+ require_relative 'collection_model/modifiers'
10
+ require_relative 'collection_model/doc'
11
+ require_relative 'collection_model/model'
12
+ require_relative 'collection_model/doc_mutation'
13
+ require_relative 'collection_model/mutation'
14
14
 
15
15
  include Modifiers
16
16
  include Doc
@@ -1,104 +1,209 @@
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
1
+ module Ecoportal::API::Common::Content::DoubleModel::Attributable::Nesting
2
+ module CascadedCallback
3
+ class << self
4
+ def included(base)
5
+ super
6
+
7
+ base.extend Ecoportal::API::Common::Content::ClassHelpers
8
+
9
+ base.extend ClassMethods
10
+ base.inheritable_class_vars :_cascaded_attributes
11
+ base.send :include, InstanceMethods
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ # @note it also tracks `doc_key` as cascaded attribute on
17
+ # child classes.
18
+ def _cascaded_attribute!(meth, doc_key = meth, inherited: false)
19
+ meth = meth.to_sym
20
+ doc_key = doc_key.to_s
21
+
22
+ dont_override = _cascaded_attributes.key?(meth) && inherited
23
+ return _cascaded_attributes[meth] if dont_override
24
+
25
+ _cascaded_attributes[meth] = doc_key
26
+
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 InstanceMethods
51
+ # Method to go through all the embedded properties recursively.
52
+ # @note **Be aware** that
53
+ # 1. It does NOT natively discard `root?` objects. You will need to
54
+ # explicitly do this in the block callback.
55
+ # 2. If `method` calls in turn to `cascaded_callback`, to prevent
56
+ # **double-recursion** you MUST use `recurs: false`.
57
+ # The **problem** with this approach is that **`key_path`**
58
+ # from the original object that called `cascaded_callback`
59
+ # **is lost**.
60
+ # For which you should probably rather use `cascaded_reduce`.
61
+ # 3. It already does a first call on `self.method`, might it respond to.
62
+ # And it will not `yield` in this first call.
63
+ # @note the `cascaded_callback` will end were:
64
+ # 1. The current object does NOT respond to `method`.
65
+ # 2. The current object does NOT have nested properties. So where
66
+ # the object does not have any `_cascaded_attributes`.
67
+ # @yield [result, value, key_path, object] block to accumulate result.
68
+ # The block is yielded for the main (root) object, as well as
69
+ # per each one of its `_cascaded_attributes` (so nested properties).
70
+ # @yieldparam result [Hash, Variant] the accumulated value of calling `method`
71
+ # across all nested models from root to this point (`object`).
72
+ # @yieldparam value [Variant] the value of calling `method` on `object`.
73
+ # @yieldparam key_path [Array<String>] the `Hash` model `path` of **keys** to get
74
+ # from root to `objecct`. It is important to **note** that an
75
+ # **empty path** means we are on the top object from where this method
76
+ # was callled.
77
+ # @yieldparam object [DoubleModel] the current object.
78
+ # @yieldreturn [Variant] the accumulated result or the current iteration.
79
+ # @param value [Variant] the intial value of the cascaded callback.
80
+ # - It can also be the accumulated value of cascaded callbacks from
81
+ # up to the model hierarchy to the current instance.
82
+ # @param method [Symbol] to be called on `self` and all nested models.
83
+ # Private methods are in scope.
84
+ # @param args [Array<>] arguments for `method`.
85
+ # @param kargs [Hash<Symbol,Variant>] named arguments for `method`.
86
+ # @param path [Array<String>] the `Hash` model `path` of **keys** to get
87
+ # from root to `self`.
88
+ # @param recurs [Boolean] whether it should go beyound the first level.
89
+ # This is specially useful to set to `false` when `method` will,
90
+ # in turn, call `cascaded_callback`, as that would already have
91
+ # a recursion. Noo need to decrease performance with a double recursion,
92
+ # in such a case.
93
+ # @return [Variant] the result of cascaded calling `method` through all the
94
+ # the nested models.
95
+ def cascaded_callback(
96
+ value = nil,
97
+ method:,
98
+ args: [],
99
+ kargs: {},
100
+ path: [],
101
+ recurs: true,
102
+ &block
103
+ )
104
+ method = method.to_sym
105
+ return value unless respond_to?(method, true)
106
+
107
+ value =
108
+ if path.empty?
109
+ # Don't yield on the first call
110
+ send(method, *args, **kargs)
111
+ else
112
+ yield(
113
+ value,
114
+ send(method, *args, **kargs),
115
+ path,
116
+ self
117
+ )
118
+ end
119
+
120
+ return value unless respond_to?(:_cascaded_attributes)
121
+
122
+ _cascaded_attributes.reduce(value) do |val, (attribute, obj_k)|
123
+ next val unless (obj = send(attribute))
124
+ next val unless obj.respond_to(method, true)
125
+
126
+ obj_path = path.dup.push(obj_k)
127
+ # next val if obj_path.empty?
128
+
129
+ unless obj.respond_to?(:cascaded_callback)
130
+ next yield(
131
+ val,
132
+ obj.send(method, *args, **kargs),
133
+ obj_path,
134
+ obj
135
+ )
136
+ end
137
+
138
+ next val unless recurs
139
+
140
+ obj.cascaded_callback(
141
+ val,
142
+ method: method,
143
+ args: args,
144
+ kargs: kargs,
145
+ path: obj_path,
146
+ recurs: recurs,
147
+ &block
148
+ )
149
+ end
150
+ end
151
+
152
+ # Trace = Struct.new(:subject, :nested_attrs, :obj_key_path)
153
+ def cascaded_reduce(init = nil, recurs: false, trace: nil, &block)
154
+ trace ||= {}
155
+ trace = _cascaded_attributes_trace(recurs: recurs) if trace.empty?
156
+
157
+ return init if trace.empty?
158
+
159
+ params = %i[subject key key_path attributes]
160
+ subject, key, key_path, attributes = trace.values_at(*params)
161
+
162
+ value = yield(init, subject, key, key_path, trace)
163
+ return value if attributes.nil? || attributes.empty?
164
+
165
+ attributes.reduce(value) do |mem, (_attr, subtrace)|
166
+ cascaded_reduce(mem, recurs: recurs, trace: subtrace, &block)
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ def _cascaded_attributes_trace(trace = nil, recurs: false, key_path: [])
173
+ {
174
+ subject: self,
175
+ attribute: nil,
176
+ key: nil,
177
+ key_path: key_path,
178
+ nested_attrs: _cascaded_attributes.keys,
179
+ attributes: {}
180
+ }.merge(trace || {}).tap do |out_trace|
181
+ _cascaded_attributes.each.with_object(out_trace) do |out, (attribute, obj_k)|
182
+ next unless (obj = send(attribute))
183
+
184
+ obj_k_path = key_path.dup.push(obj_k)
185
+
186
+ sub_trace = out[:attributes][attribute] = {
187
+ subject: obj,
188
+ attribute: attribute,
189
+ key: obj_k,
190
+ key_path: obj_k_path
191
+ }
192
+
193
+ next unless recurs
194
+
195
+ _cascaded_attributes_trace(sub_trace, recurs: true, key_path: obj_k_path)
99
196
  end
100
197
  end
101
198
  end
199
+
200
+ def _cascaded_attribute!(...)
201
+ self.class._cascaded_attribute!(...)
202
+ end
203
+
204
+ def _cascaded_attributes
205
+ self.class._cascaded_attributes
206
+ end
102
207
  end
103
208
  end
104
209
  end