module Mongoid::Interceptable

This module contains all the callback hooks for Mongoid.

@since 4.0.0

Constants

CALLBACKS

Attributes

before_callback_halted[RW]

Public Instance Methods

callback_executable?(kind) click to toggle source

Is the provided type of callback executable by this document?

@example Is the callback executable?

document.callback_executable?(:save)

@param [ Symbol ] kind The type of callback.

@return [ true, false ] If the callback can be executed.

@since 3.0.6

# File lib/mongoid/interceptable.rb, line 56
def callback_executable?(kind)
  respond_to?("_#{kind}_callbacks")
end
in_callback_state?(kind) click to toggle source

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

@example Is the document in a callback state?

document.in_callback_state?(:update)

@param [ Symbol ] kind The callback kind.

@return [ true, false ] If the document is in a callback state.

@since 3.1.0

# File lib/mongoid/interceptable.rb, line 71
def in_callback_state?(kind)
  [ :create, :destroy ].include?(kind) || new_record? || flagged_for_destroy? || changed?
end
run_after_callbacks(*kinds) click to toggle source

Run only the after callbacks for the specific event.

@note ActiveSupport does not allow this type of behavior by default, so

Mongoid has to get around it and implement itself.

@example Run only the after save callbacks.

model.run_after_callbacks(:save)

@param [ Array<Symbol> ] kinds The events that are occurring.

@return [ Object ] The result of the chain executing.

@since 3.0.0

# File lib/mongoid/interceptable.rb, line 88
def run_after_callbacks(*kinds)
  kinds.each do |kind|
    run_targeted_callbacks(:after, kind)
  end
end
run_before_callbacks(*kinds) click to toggle source

Run only the before callbacks for the specific event.

@note ActiveSupport does not allow this type of behavior by default, so

Mongoid has to get around it and implement itself.

@example Run only the before save callbacks.

model.run_before_callbacks(:save, :create)

@param [ Array<Symbol> ] kinds The events that are occurring.

@return [ Object ] The result of the chain executing.

@since 3.0.0

# File lib/mongoid/interceptable.rb, line 107
def run_before_callbacks(*kinds)
  kinds.each do |kind|
    run_targeted_callbacks(:before, kind)
  end
end
run_callbacks(kind, *args, &block) click to toggle source
Calls superclass method
# File lib/mongoid/interceptable.rb, line 128
               def run_callbacks(kind, *args, &block)
  cascadable_children(kind).each do |child|
    if child.run_callbacks(child_callback_type(kind, child), *args) == false
      return false
    end
  end
  if callback_executable?(kind)
    super(kind, *args, &block)
  else
    true
  end
end

Private Instance Methods

before_callback_halted?() click to toggle source

We need to hook into this for autosave, since we don’t want it firing if the before callbacks were halted.

@api private

@example Was a before callback halted?

document.before_callback_halted?

@return [ true, false ] If a before callback was halted.

@since 3.0.3

# File lib/mongoid/interceptable.rb, line 154
def before_callback_halted?
  !!@before_callback_halted
end
cascadable_child?(kind, child, association) click to toggle source

Determine if the child should fire the callback.

@example Should the child fire the callback?

document.cascadable_child?(:update, doc)

@param [ Symbol ] kind The type of callback. @param [ Document ] child The child document.

@return [ true, false ] If the child should fire the callback.

@since 2.3.0

# File lib/mongoid/interceptable.rb, line 198
def cascadable_child?(kind, child, association)
  return false if kind == :initialize || kind == :find || kind == :touch
  return false if kind == :validate && association.validate?
  child.callback_executable?(kind) ? child.in_callback_state?(kind) : false
end
cascadable_children(kind, children = Set.new) click to toggle source

Get all the child embedded documents that are flagged as cascadable.

@example Get all the cascading children.

document.cascadable_children(:update)

@param [ Symbol ] kind The type of callback.

@return [ Array<Document> ] The children.

@since 2.3.0

# File lib/mongoid/interceptable.rb, line 168
def cascadable_children(kind, children = Set.new)
  embedded_relations.each_pair do |name, association|
    next unless association.cascading_callbacks?
    without_autobuild do
      delayed_pulls = delayed_atomic_pulls[name]
      delayed_unsets = delayed_atomic_unsets[name]
      children.merge(delayed_pulls) if delayed_pulls
      children.merge(delayed_unsets) if delayed_unsets
      relation = send(name)
      Array.wrap(relation).each do |child|
        next if children.include?(child)
        children.add(child) if cascadable_child?(kind, child, association)
        child.send(:cascadable_children, kind, children)
      end
    end
  end
  children.to_a
end
child_callback_type(kind, child) click to toggle source

Get the name of the callback that the child should fire. This changes depending on whether or not the child is new. A persisted parent with a new child would fire :update from the parent, but needs to fire :create on the child.

@example Get the callback type.

document.child_callback_type(:update, doc)

@param [ Symbol ] kind The type of callback. @param [ Document ] child The child document

@return [ Symbol ] The name of the callback.

@since 2.3.0

# File lib/mongoid/interceptable.rb, line 218
def child_callback_type(kind, child)
  if kind == :update
    return :create if child.new_record?
    return :destroy if child.flagged_for_destroy?
    kind
  else
    kind
  end
end
halted_callback_hook(filter, name = nil) click to toggle source

We need to hook into this for autosave, since we don’t want it firing if the before callbacks were halted.

@api private

@example Hook into the halt.

document.halted_callback_hook(filter)

@param [ Symbol ] filter The callback that halted. @param [ Symbol ] name The name of the callback that was halted

(requires Rails 6.1+)

@since 3.0.3

# File lib/mongoid/interceptable.rb, line 241
def halted_callback_hook(filter, name = nil)
  @before_callback_halted = true
end
run_targeted_callbacks(place, kind) click to toggle source

Run only the callbacks for the target location (before, after, around) and kind (save, update, create).

@example Run the targeted callbacks.

model.run_targeted_callbacks(:before, :save)

@param [ Symbol ] place The time to run, :before, :after, :around. @param [ Symbol ] kind The type of callback, :save, :create, :update.

@return [ Object ] The result of the chain execution.

@since 3.0.0

# File lib/mongoid/interceptable.rb, line 257
def run_targeted_callbacks(place, kind)
  name = "_run__#{place}__#{kind}__callbacks"
  unless respond_to?(name)
    chain = ActiveSupport::Callbacks::CallbackChain.new(name, {})
    send("_#{kind}_callbacks").each do |callback|
      chain.append(callback) if callback.kind == place
    end
    self.class.send :define_method, name do
      env = ActiveSupport::Callbacks::Filters::Environment.new(self, false, nil)
      sequence = chain.compile
      sequence.invoke_before(env)
      env.value = !env.halted
      sequence.invoke_after(env)
      env.value
    end
    self.class.send :protected, name
  end
  send(name)
end