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 8%i[ 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 ]32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
# File 'lib/mongoid/interceptable.rb', line 32
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: %i[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 304
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 259
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 273
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 247
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 ] end callback_list end
#_mongoid_run_child_callbacks(kind, children: nil, &block)
Run the callbacks for embedded documents.
# File 'lib/mongoid/interceptable.rb', line 148
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 169
def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block) 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 202
def _mongoid_run_child_callbacks_without_around(kind, children: nil, &block) 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 60
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 %i[initialize find touch].include?(kind) 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 316
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 365
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 430
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 387
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 73
def in_callback_state?(kind) %i[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 88
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 105
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 125
def run_callbacks(kind, with_children: true, skip_if: nil, &block) return block&.call if skip_if&.call if with_children cascadable_children(kind).each do |child| return false if child.run_callbacks(child_callback_type(kind, child), with_children: with_children) == false 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 282
def run_pending_callbacks pending_callbacks.each do |cb| if %i[apply_defaults apply_post_processed_defaults].include?(cb) send(cb) else 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 401
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