Module: Ecoportal::API::Common::Content::DoubleModel::Attributable::Nesting::CascadedCallback::InstanceMethods

Defined in:
lib/ecoportal/api/common/content/double_model/attributable/nesting/cascaded_callback.rb

Overview

Note:

this happens at instance level (not tracked at class level)

We offer helpers to implement cascaded callbacks to nested objects i.e. #as_update or #dirty?

Instance Method Summary collapse

Instance Method Details

#cascaded_callback(value = nil, method:, args: [], kargs: {}, path: [], recurs: true) {|result, value, key_path, object| ... } ⇒ Variant

Note:

Be aware that

  1. It does NOT natively discard root? objects. You will need to explicitly do this in the block callback.
  2. If method calls in turn to cascaded_callback, to prevent double-recursion you MUST use recurs: false. The problem with this approach is that key_path from the original object that called cascaded_callback is lost. For which you should probably rather use cascaded_reduce.
  3. It already does a first call on self.method, might it respond to. And it will not yield in this first call.
Note:

the cascaded_callback will end were:

  1. The current object does NOT respond to method.
  2. The current object does NOT have nested properties. So where the object does not have any _cascaded_attributes.

Method to go through all the embedded properties recursively.

Parameters:

  • value (Variant) (defaults to: nil)

    the intial value of the cascaded callback.

    • It can also be the accumulated value of cascaded callbacks from up to the model hierarchy to the current instance.
  • method (Symbol)

    to be called on self and all nested models. Private methods are in scope.

  • args (Array<>) (defaults to: [])

    arguments for method.

  • kargs (Hash<Symbol,Variant>) (defaults to: {})

    named arguments for method.

  • path (Array<String>) (defaults to: [])

    the Hash model path of keys to get from root to self.

  • recurs (Boolean) (defaults to: true)

    whether it should go beyound the first level. This is specially useful to set to false when method will, in turn, call cascaded_callback, as that would already have a recursion. Noo need to decrease performance with a double recursion, in such a case.

Yields:

  • (result, value, key_path, object)

    block to accumulate result. The block is yielded for the main (root) object, as well as per each one of its _cascaded_attributes (so nested properties).

Yield Parameters:

  • result (Hash, Variant)

    the accumulated value of calling method across all nested models from root to this point (object).

  • value (Variant)

    the value of calling method on object.

  • key_path (Array<String>)

    the Hash model path of keys to get from root to objecct. It is important to note that an empty path means we are on the top object from where this method was callled.

  • object (DoubleModel)

    the current object.

Yield Returns:

  • (Variant)

    the accumulated result or the current iteration.

Returns:

  • (Variant)

    the result of cascaded calling method through all the the nested models.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/ecoportal/api/common/content/double_model/attributable/nesting/cascaded_callback.rb', line 95

def cascaded_callback(
  value = nil,
  method:,
  args:   [],
  kargs:  {},
  path:   [],
  recurs: true,
  &block
)
  method = method.to_sym
  return value unless respond_to?(method, true)

  value =
    if path.empty?
      # Don't yield on the first call
      send(method, *args, **kargs)
    else
      yield(
        value,
        send(method, *args, **kargs),
        path,
        self
      )
    end

  return value unless respond_to?(:_cascaded_attributes)

  _cascaded_attributes.reduce(value) do |val, (attribute, obj_k)|
    next val unless (obj = send(attribute))
    next val unless obj.respond_to(method, true)

    obj_path = path.dup.push(obj_k)
    # next val if obj_path.empty?

    unless obj.respond_to?(:cascaded_callback)
      next yield(
        val,
        obj.send(method, *args, **kargs),
        obj_path,
        obj
      )
    end

    next val unless recurs

    obj.cascaded_callback(
      val,
      method: method,
      args:   args,
      kargs:  kargs,
      path:   obj_path,
      recurs: recurs,
      &block
    )
  end
end

#cascaded_reduce(init = nil, recurs: false, trace: nil, &block) ⇒ Object

Trace = Struct.new(:subject, :nested_attrs, :obj_key_path)



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ecoportal/api/common/content/double_model/attributable/nesting/cascaded_callback.rb', line 153

def cascaded_reduce(init = nil, recurs: false, trace: nil, &block)
  trace ||= {}
  trace   = _cascaded_attributes_trace(recurs: recurs) if trace.empty?

  return init if trace.empty?

  params = %i[subject key key_path attributes]
  subject, key, key_path, attributes = trace.values_at(*params)

  value = yield(init, subject, key, key_path, trace)
  return value if attributes.nil? || attributes.empty?

  attributes.reduce(value) do |mem, (_attr, subtrace)|
    cascaded_reduce(mem, recurs: recurs, trace: subtrace, &block)
  end
end