Module: Mongoid::Interceptable

Extended by:
ActiveSupport::Concern
Included in:
Composable
Defined in:
lib/mongoid/interceptable.rb

Overview

This module contains all the callback hooks for Mongoid.

Constant Summary collapse

CALLBACKS =
[
  :after_build,
  :after_create,
  :after_destroy,
  :after_find,
  :after_initialize,
  :after_save,
  :after_touch,
  :after_update,
  :after_upsert,
  :after_validation,
  :around_create,
  :around_destroy,
  :around_save,
  :around_update,
  :around_upsert,
  :before_create,
  :before_destroy,
  :before_save,
  :before_update,
  :before_upsert,
  :before_validation,
].freeze

Instance Method Summary collapse

Instance Method Details

#_mongoid_run_child_after_callbacks(callback_list: []) ⇒ Object

Execute the after callbacks.

Parameters:

  • callback_list (Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment>) (defaults to: [])

    List of pairs of callback sequence and environment.



233
234
235
236
237
238
# File 'lib/mongoid/interceptable.rb', line 233

def _mongoid_run_child_after_callbacks(callback_list: [])
  callback_list.reverse_each do |next_sequence, env|
    next_sequence.invoke_after(env)
    return false if env.halted
  end
end

#_mongoid_run_child_before_callbacks(kind, children: [], callback_list: []) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Execute the before callbacks of given kind for embedded documents.

Parameters:

  • kind (Symbol)

    The type of callback to execute.

  • children (Array<Document>) (defaults to: [])

    Children to execute callbacks on.

  • callback_list (Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment>) (defaults to: [])

    List of pairs of callback sequence and environment. This list will be later used to execute after callbacks in reverse order.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/mongoid/interceptable.rb', line 209

def _mongoid_run_child_before_callbacks(kind, children: [], callback_list: [])
  children.each do |child|
    chain = child.__callbacks[child_callback_type(kind, child)]
    env = ActiveSupport::Callbacks::Filters::Environment.new(child, false, nil)
    next_sequence = compile_callbacks(chain)
    unless next_sequence.final?
      Mongoid.logger.warn("Around callbacks are disabled for embedded documents. Skipping around callbacks for #{child.class.name}.")
      Mongoid.logger.warn("To enable around callbacks for embedded documents, set Mongoid::Config.around_callbacks_for_embeds to true.")
    end
    next_sequence.invoke_before(env)
    return false if env.halted
    env.value = !env.halted
    callback_list << [next_sequence, env]
    if (grandchildren = child.send(:cascadable_children, kind))
      _mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
    end
  end
  callback_list
end

#_mongoid_run_child_callbacks(kind, children: nil, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Run the callbacks for embedded documents.

Parameters:

  • kind (Symbol)

    The type of callback to execute.

  • children (Array<Document>) (defaults to: nil)

    Children to execute callbacks on. If nil, callbacks will be executed on all cascadable children of the document.



142
143
144
145
146
147
148
# File 'lib/mongoid/interceptable.rb', line 142

def _mongoid_run_child_callbacks(kind, children: nil, &block)
  if Mongoid::Config.around_callbacks_for_embeds
    _mongoid_run_child_callbacks_with_around(kind, children: children, &block)
  else
    _mongoid_run_child_callbacks_without_around(kind, children: children, &block)
  end
end

#_mongoid_run_child_callbacks_with_around(kind, children: nil, &block) ⇒ Object

Note:

This method is prone to stack overflow errors if the document has a large number of embedded documents. It is recommended to avoid using around callbacks for embedded documents until a proper solution is implemented.

Execute the callbacks of given kind for embedded documents including around callbacks.

Parameters:

  • kind (Symbol)

    The type of callback to execute.

  • children (Array<Document>) (defaults to: nil)

    Children to execute callbacks on. If nil, callbacks will be executed on all cascadable children of the document.

    @api private



164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/mongoid/interceptable.rb', line 164

def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
  child, *tail = (children || cascadable_children(kind))
  with_children = !Mongoid::Config.prevent_multiple_calls_of_embedded_callbacks
  if child.nil?
    block&.call
  elsif tail.empty?
    child.run_callbacks(child_callback_type(kind, child), with_children: with_children, &block)
  else
    child.run_callbacks(child_callback_type(kind, child), with_children: with_children) do
      _mongoid_run_child_callbacks_with_around(kind, children: tail, &block)
    end
  end
end

#_mongoid_run_child_callbacks_without_around(kind, children: nil, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Execute the callbacks of given kind for embedded documents without around callbacks.

Parameters:

  • kind (Symbol)

    The type of callback to execute.

  • children (Array<Document>) (defaults to: nil)

    Children to execute callbacks on. If nil, callbacks will be executed on all cascadable children of the document.



187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/mongoid/interceptable.rb', line 187

def _mongoid_run_child_callbacks_without_around(kind, children: nil, &block)
  children = (children || cascadable_children(kind))
  callback_list = _mongoid_run_child_before_callbacks(kind, children: children)
  return false if callback_list == false
  value = block&.call
  callback_list.each do |_next_sequence, env|
    env.value &&= value
  end
  return false if _mongoid_run_child_after_callbacks(callback_list: callback_list) == false

  value
end

#callback_executable?(kind) ⇒ true | false

Is the provided type of callback executable by this document?

Examples:

Is the callback executable?

document.callback_executable?(:save)

Parameters:

  • kind (Symbol)

    The type of callback.

Returns:

  • (true | false)

    If the callback can be executed.



57
58
59
# File 'lib/mongoid/interceptable.rb', line 57

def callback_executable?(kind)
  respond_to?("_#{kind}_callbacks")
end

#in_callback_state?(kind) ⇒ true | false

Is the document currently in a state that could potentially require callbacks to be executed?

Examples:

Is the document in a callback state?

document.in_callback_state?(:update)

Parameters:

  • kind (Symbol)

    The callback kind.

Returns:

  • (true | false)

    If the document is in a callback state.



70
71
72
# File 'lib/mongoid/interceptable.rb', line 70

def in_callback_state?(kind)
  [ :create, :destroy ].include?(kind) || new_record? || flagged_for_destroy? || changed?
end

#pending_callbacksArray<Symbol>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the stored callbacks to be executed later.

Returns:

  • (Array<Symbol>)

    Method symbols of the stored pending callbacks.



245
246
247
# File 'lib/mongoid/interceptable.rb', line 245

def pending_callbacks
  @pending_callbacks ||= [].to_set
end

#pending_callbacks=(value) ⇒ Array<Symbol>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Stores callbacks to be executed later. A good use case for this is delaying the after_find and after_initialize callbacks until the associations are set on the document. This can also be used to delay applying the defaults on a document.

Parameters:

  • value (Array<Symbol>)

    Method symbols of the pending callbacks to store.

Returns:

  • (Array<Symbol>)

    Method symbols of the stored pending callbacks.



259
260
261
# File 'lib/mongoid/interceptable.rb', line 259

def pending_callbacks=(value)
  @pending_callbacks = value
end

#run_after_callbacks(*kinds) ⇒ Object

Note:

ActiveSupport does not allow this type of behavior by default, so Mongoid has to get around it and implement itself.

Run only the after callbacks for the specific event.

Examples:

Run only the after save callbacks.

model.run_after_callbacks(:save)

Parameters:

  • *kinds (Symbol...)

    The events that are occurring.

Returns:

  • (Object)

    The result of the chain executing.



85
86
87
88
89
# File 'lib/mongoid/interceptable.rb', line 85

def run_after_callbacks(*kinds)
  kinds.each do |kind|
    run_targeted_callbacks(:after, kind)
  end
end

#run_before_callbacks(*kinds) ⇒ Object

Note:

ActiveSupport does not allow this type of behavior by default, so Mongoid has to get around it and implement itself.

Run only the before callbacks for the specific event.

Examples:

Run only the before save callbacks.

model.run_before_callbacks(:save, :create)

Parameters:

  • *kinds (Symbol...)

    The events that are occurring.

Returns:

  • (Object)

    The result of the chain executing.



102
103
104
105
106
# File 'lib/mongoid/interceptable.rb', line 102

def run_before_callbacks(*kinds)
  kinds.each do |kind|
    run_targeted_callbacks(:before, kind)
  end
end

#run_callbacks(kind, with_children: true, &block) ⇒ Object

Run the callbacks for the document. This overrides active support’s functionality to cascade callbacks to embedded documents that have been flagged as such.

Examples:

Run the callbacks.

run_callbacks :save do
  save!
end

Parameters:

  • kind (Symbol)

    The type of callback to execute.

  • with_children (true | false) (defaults to: true)

    Flag specifies whether callbacks of embedded document should be run.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/mongoid/interceptable.rb', line 119

def run_callbacks(kind, with_children: true, &block)
  if with_children
    cascadable_children(kind).each do |child|
      if child.run_callbacks(child_callback_type(kind, child), with_children: with_children) == false
        return false
      end
    end
  end
  if callback_executable?(kind)
    super(kind, &block)
  else
    true
  end
end

#run_pending_callbacksObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Run the pending callbacks. If the callback is :apply_defaults, we will apply the defaults for this document. Otherwise, the callback is passed to the run_callbacks function.



268
269
270
271
272
273
274
275
276
277
# File 'lib/mongoid/interceptable.rb', line 268

def run_pending_callbacks
  pending_callbacks.each do |cb|
    if [:apply_defaults, :apply_post_processed_defaults].include?(cb)
      send(cb)
    else
      self.run_callbacks(cb, with_children: false)
    end
  end
  pending_callbacks.clear
end