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
Returns the stored callbacks to be executed later.
-
#pending_callbacks=(value) ⇒ Array<Symbol>
rw
Internal use only
Stores callbacks to be executed later.
-
#before_callback_halted? ⇒ true | false
readonly
private
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
Execute the before callbacks of given kind for embedded documents.
-
#_mongoid_run_child_callbacks(kind, children: nil, &block)
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
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
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
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