Module: Mongoid::Interceptable
Relationships & Source Files | |
Extension / Inclusion / Inheritance Descendants | |
Included In:
| |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
ActiveSupport::Concern
|
|
Defined in: | lib/mongoid/interceptable.rb |
Overview
This module contains all the callback hooks for ::Mongoid
.
Constant Summary
-
CALLBACKS =
# File 'lib/mongoid/interceptable.rb', line 10[ :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 Attribute Summary
-
#pending_callbacks ⇒ Array<Symbol>
rw
Internal use only
Internal use only
Returns the stored callbacks to be executed later.
-
#pending_callbacks=(value) ⇒ Array<Symbol>
rw
Internal use only
Internal use only
Stores callbacks to be executed later.
-
#before_callback_halted? ⇒ true | false
readonly
private
Internal use only
Internal use only
We need to hook into this for autosave, since we don’t want it firing if the before callbacks were halted.
Instance Method Summary
-
#_mongoid_run_child_after_callbacks(callback_list: [])
Execute the after callbacks.
-
#_mongoid_run_child_before_callbacks(kind, children: [], callback_list: [])
Internal use only
Internal use only
Execute the before callbacks of given kind for embedded documents.
-
#_mongoid_run_child_callbacks(kind, children: nil, &block)
Internal use only
Internal use only
Run the callbacks for embedded documents.
-
#_mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
Execute the callbacks of given kind for embedded documents including around callbacks.
-
#_mongoid_run_child_callbacks_without_around(kind, children: nil, &block)
Internal use only
Internal use only
Execute the callbacks of given kind for embedded documents without around callbacks.
-
#callback_executable?(kind) ⇒ true | false
Is the provided type of callback executable by this document?
-
#in_callback_state?(kind) ⇒ true | false
Is the document currently in a state that could potentially require callbacks to be executed?
-
#run_after_callbacks(*kinds) ⇒ Object
Run only the after callbacks for the specific event.
-
#run_before_callbacks(*kinds) ⇒ Object
Run only the before callbacks for the specific event.
-
#run_callbacks(kind, with_children: true, skip_if: nil, &block)
Run the callbacks for the document.
-
#run_pending_callbacks
Internal use only
Internal use only
Run the pending callbacks.
-
#cascadable_child?(kind, child, association) ⇒ true | false
private
Determine if the child should fire the callback.
-
#cascadable_children(kind, children = Set.new) ⇒ Array<Document>
private
Get all the child embedded documents that are flagged as cascadable.
-
#child_callback_type(kind, child) ⇒ Symbol
private
Get the name of the callback that the child should fire.
-
#compile_callbacks(chain, type = nil) ⇒ ActiveSupport::Callbacks::CallbackSequence
private
Compile the callback chain.
-
#halted_callback_hook(filter, name = nil)
private
Internal use only
Internal use only
We need to hook into this for autosave, since we don’t want it firing if the before callbacks were halted.
-
#run_targeted_callbacks(place, kind) ⇒ Object
private
Run only the callbacks for the target location (before, after, around) and kind (save, update, create).
DSL Calls
included
[ GitHub ]34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
# File 'lib/mongoid/interceptable.rb', line 34
included do extend ActiveModel::Callbacks include ActiveModel::Validations::Callbacks define_model_callbacks :build, :find, :initialize, :touch, only: :after define_model_callbacks :create, :destroy, :save, :update, :upsert # This callback is used internally by Mongoid to save association # targets for referenced associations after the parent model is persisted. # # @api private define_model_callbacks :persist_parent define_callbacks :commit, :rollback, only: :after, scope: [:kind, :name] attr_accessor :before_callback_halted end
Instance Attribute Details
#before_callback_halted? ⇒ true
| false
(readonly, private)
We need to hook into this for autosave, since we don’t want it firing if the before callbacks were halted.
# File 'lib/mongoid/interceptable.rb', line 306
def before_callback_halted? !!@before_callback_halted end
#pending_callbacks ⇒ Array<Symbol> (rw)
Returns the stored callbacks to be executed later.
# File 'lib/mongoid/interceptable.rb', line 261
def pending_callbacks @pending_callbacks ||= [].to_set end
#pending_callbacks=(value) ⇒ Array<Symbol> (rw)
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.
# File 'lib/mongoid/interceptable.rb', line 275
def pending_callbacks=(value) @pending_callbacks = value end
Instance Method Details
#_mongoid_run_child_after_callbacks(callback_list: [])
Execute the after callbacks.
# File 'lib/mongoid/interceptable.rb', line 249
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: [])
Execute the before callbacks of given kind for embedded documents.
# File 'lib/mongoid/interceptable.rb', line 225
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)
Run the callbacks for embedded documents.
# File 'lib/mongoid/interceptable.rb', line 153
def _mongoid_run_child_callbacks(kind, children: nil, &block) if Mongoid::Config. _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)
Execute the callbacks of given kind for embedded documents including around callbacks.
# File 'lib/mongoid/interceptable.rb', line 170
def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block) children = (children || cascadable_children(kind)) with_children = !Mongoid::Config. return block&.call if children.empty? fibers = children.map do |child| Fiber.new do child.run_callbacks(child_callback_type(kind, child), with_children: with_children) do Fiber.yield end end end fibers.each do |fiber| fiber.resume raise Mongoid::Errors::InvalidAroundCallback unless fiber.alive? end block&.call fibers.reverse.each(&:resume) end
#_mongoid_run_child_callbacks_without_around(kind, children: nil, &block)
Execute the callbacks of given kind for embedded documents without around callbacks.
# File 'lib/mongoid/interceptable.rb', line 203
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?
# File 'lib/mongoid/interceptable.rb', line 62
def callback_executable?(kind) respond_to?("_#{kind}_callbacks") end
#cascadable_child?(kind, child, association) ⇒ true
| false
(private)
Determine if the child should fire the callback.
# File 'lib/mongoid/interceptable.rb', line 346
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) ⇒ Array<Document> (private)
Get all the child embedded documents that are flagged as cascadable.
# File 'lib/mongoid/interceptable.rb', line 318
def cascadable_children(kind, children = Set.new) .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) ⇒ Symbol (private)
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.
# File 'lib/mongoid/interceptable.rb', line 364
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
#compile_callbacks(chain, type = nil) ⇒ ActiveSupport::Callbacks::CallbackSequence
(private)
Compile the callback chain.
This method hides the differences between ::ActiveSupport
implementations before and after 7.1.
# File 'lib/mongoid/interceptable.rb', line 428
def compile_callbacks(chain, type = nil) if chain.method(:compile).arity == 0 # ActiveSupport < 7.1 chain.compile else # ActiveSupport >= 7.1 chain.compile(type) end end
#halted_callback_hook(filter, name = nil) (private)
We need to hook into this for autosave, since we don’t want it firing if the before callbacks were halted.
# File 'lib/mongoid/interceptable.rb', line 385
def halted_callback_hook(filter, name = nil) @before_callback_halted = true end
#in_callback_state?(kind) ⇒ true
| false
Is the document currently in a state that could potentially require callbacks to be executed?
# File 'lib/mongoid/interceptable.rb', line 75
def in_callback_state?(kind) [ :create, :destroy ].include?(kind) || new_record? || flagged_for_destroy? || changed? end
#run_after_callbacks(*kinds) ⇒ Object
::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.
# File 'lib/mongoid/interceptable.rb', line 90
def run_after_callbacks(*kinds) kinds.each do |kind| run_targeted_callbacks(:after, kind) end end
#run_before_callbacks(*kinds) ⇒ Object
::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.
# 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, with_children: true, skip_if: nil, &block)
Run the callbacks for the document. This overrides active support’s functionality to cascade callbacks to embedded documents that have been flagged as such.
# File 'lib/mongoid/interceptable.rb', line 127
def run_callbacks(kind, with_children: true, skip_if: nil, &block) if skip_if&.call return block&.call end 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_callbacks
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.
# File 'lib/mongoid/interceptable.rb', line 284
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
#run_targeted_callbacks(place, kind) ⇒ Object
(private)
Run only the callbacks for the target location (before, after, around) and kind (save, update, create).
# File 'lib/mongoid/interceptable.rb', line 399
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 = compile_callbacks(chain) sequence.invoke_before(env) env.value = !env.halted sequence.invoke_after(env) env.value end self.class.send :protected, name end send(name) end