Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions snooty.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ server-manual = "Server manual"
api-root = "https://www.mongodb.com/docs/mongoid/master/api/Mongoid"
api = "https://www.mongodb.com/docs/mongoid/master/api"
ruby-api = "https://www.mongodb.com/docs/ruby-driver/current/api/Mongo"
active-record-docs = "https://guides.rubyonrails.org"
4 changes: 2 additions & 2 deletions source/add-existing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ for each Rails component, as shown in the following sample
.. note::

Because they rely on Active Record, the `ActionText
<https://guides.rubyonrails.org/action_text_overview.html>`__,
<{+active-record-docs+}/action_text_overview.html>`__,
`ActiveStorage <https://edgeguides.rubyonrails.org/active_storage_overview.html>`__, and
`ActionMailbox
<https://guides.rubyonrails.org/action_mailbox_basics.html>`__
<{+active-record-docs+}/action_mailbox_basics.html>`__
adapters cannot be used alongside {+odm+}.

Disable Active Record Adapters
Expand Down
4 changes: 4 additions & 0 deletions source/data-modeling.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Model Your Data
Field Behaviors </data-modeling/field-behaviors>
Inheritance </data-modeling/inheritance>
Document Validation </data-modeling/validation>
Callbacks </data-modeling/callbacks>

In this section, you can learn how to model data in {+odm+}.

Expand All @@ -32,3 +33,6 @@ In this section, you can learn how to model data in {+odm+}.

- :ref:`mongoid-modeling-validation`: Learn how to create document
validation rules for your model classes.

- :ref:`mongoid-modeling-callbacks`: Learn how to implement callbacks to
customize the life cycle of your models.
173 changes: 173 additions & 0 deletions source/data-modeling/callbacks.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
.. _mongoid-modeling-callbacks:

=========
Callbacks
=========

.. facet::
:name: genre
:values: reference

.. meta::
:keywords: ruby framework, odm, code example, life cycle

.. contents:: On this page
:local:
:backlinks: none
:depth: 2
:class: singlecol

Overview
--------

In this guide, you can learn how to implement **callbacks** in your
{+odm+} models to customize the life cycle of your model instances.

Callbacks are methods that {+odm+} triggers at specified moments of
an object's life cycle. They allow you to initiate specified actions
before or after changes to an object's state.

{+odm+} implements many of the callbacks from Active Record. To learn
more, see `Callbacks
<{+active-record-docs+}/active_record_callbacks.html>`__ in the
Active Record documentation.

Supported Callbacks
-------------------

Mongoid supports the following callbacks on model classes that implement
the :ref:`Document <mongoid-modeling-documents>` module:

- ``after_initialize``
- ``after_build``
- ``before_validation``
- ``after_validation``
- ``before_create``
- ``around_create``
- ``after_create``
- ``after_find``
- ``before_update``
- ``around_update``
- ``after_update``
- ``before_upsert``
- ``around_upsert``
- ``after_upsert``
- ``before_save``
- ``around_save``
- ``after_save``
- ``before_destroy``

Check failure on line 58 in source/data-modeling/callbacks.txt

View workflow job for this annotation

GitHub Actions / TDBX Vale rules

[vale] reported by reviewdog 🐶 [MongoDB.NegativeWords] Use 'remove, delete' instead of the negative word 'destroy'. Raw Output: {"message": "[MongoDB.NegativeWords] Use 'remove, delete' instead of the negative word 'destroy'.", "location": {"path": "source/data-modeling/callbacks.txt", "range": {"start": {"line": 58, "column": 13}}}, "severity": "ERROR"}
- ``around_destroy``

Check failure on line 59 in source/data-modeling/callbacks.txt

View workflow job for this annotation

GitHub Actions / TDBX Vale rules

[vale] reported by reviewdog 🐶 [MongoDB.NegativeWords] Use 'remove, delete' instead of the negative word 'destroy'. Raw Output: {"message": "[MongoDB.NegativeWords] Use 'remove, delete' instead of the negative word 'destroy'.", "location": {"path": "source/data-modeling/callbacks.txt", "range": {"start": {"line": 59, "column": 13}}}, "severity": "ERROR"}
- ``after_destroy``

Check failure on line 60 in source/data-modeling/callbacks.txt

View workflow job for this annotation

GitHub Actions / TDBX Vale rules

[vale] reported by reviewdog 🐶 [MongoDB.NegativeWords] Use 'remove, delete' instead of the negative word 'destroy'. Raw Output: {"message": "[MongoDB.NegativeWords] Use 'remove, delete' instead of the negative word 'destroy'.", "location": {"path": "source/data-modeling/callbacks.txt", "range": {"start": {"line": 60, "column": 12}}}, "severity": "ERROR"}

To learn more about any of the preceding callback types, see the
`ActiveRecord::Callbacks
<https://api.rubyonrails.org/{+rails-8-version-docs+}/classes/ActiveRecord/Callbacks.html>`__
reference in the Rails API documentation.

You can implement a callback both top-level and embedded documents.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S:

Suggested change
You can implement a callback both top-level and embedded documents.
You can implement a callback on both top-level and embedded documents.


.. note:: Callback Invocation Behavior

For efficiency, {+odm+} invokes the callback only on the document
that you performed the persistence action on. This behavior enables
{+odm+} to support large hierarchies and handle optimized atomic
updates efficiently by not invoking callbacks throughout the document
hierarchy.

Take precautions and ensure testability when implementing callbacks for
domain logic, because these designs can lead to unexpected errors when
callbacks in the chain halt execution. We recommend using callbacks for
cross-cutting concerns, such as queueing up background jobs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: there's a couple terms in this paragraph (domain logic, cross-cutting) that I'm not familiar with - is it safe to assume readers should know these concepts, or do you think they're worth briefly explaining?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some elaboration

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Domain logic should be a phrase users are familiar with


Document Callbacks
------------------

You must implement and register callbacks on your model classes.
You can register a callback by using ordinary methods, blocks and
``Proc`` objects, or by defining custom callback objects that use
classes or modules.

This example demonstrates how to register callbacks on the ``Contact``
model class in the following ways:

- Includes the ``before_save`` class method which triggers the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S:

Suggested change
- Includes the ``before_save`` class method which triggers the
- Includes the ``before_save`` class method, which triggers the

``process_phone`` method before a ``Contact`` instance is saved to
MongoDB. The ``process_phone`` method is defined separately in the class.

- Includes the ``after_destroy`` class method and uses a block to print a

Check failure on line 97 in source/data-modeling/callbacks.txt

View workflow job for this annotation

GitHub Actions / TDBX Vale rules

[vale] reported by reviewdog 🐶 [MongoDB.NegativeWords] Use 'remove, delete' instead of the negative word 'destroy'. Raw Output: {"message": "[MongoDB.NegativeWords] Use 'remove, delete' instead of the negative word 'destroy'.", "location": {"path": "source/data-modeling/callbacks.txt", "range": {"start": {"line": 97, "column": 24}}}, "severity": "ERROR"}
message when a ``Contact`` instance is deleted.

.. literalinclude:: /includes/data-modeling/callbacks.rb
:start-after: start-doc-callback
:end-before: end-doc-callback
:language: ruby
:emphasize-lines: 8, 11-13, 16-18
:dedent:

The following code performs data operations that demonstrate the
callback actions:

.. literalinclude:: /includes/data-modeling/callbacks.rb
:start-after: start-doc-ops
:end-before: end-doc-ops
:language: ruby
:dedent:

Because callback functionality comes from Active Support, you can use
the ``set_callback`` class method syntax to register callbacks, as shown
in the following code:

.. literalinclude:: /includes/data-modeling/callbacks.rb
:start-after: start-doc-set-syntax
:end-before: end-doc-set-syntax
:language: ruby
:emphasize-lines: 8-12
:dedent:

Association Callbacks
---------------------

{+odm+} provides the following association callbacks:

- ``after_add``
- ``after_remove``
- ``before_add``
- ``before_remove``

If you register an association callback on your model class, it is
invoked whenever you add or remove a document from any of the following
associations:

- ``embeds_many``
- ``has_many``
- ``has_and_belongs_to_many``

Specify association callbacks as options on the respective association.
You must pass the added or removed document as the parameter to the
specified callback.

The following code demonstrates how to register an association callback
on a ``User`` model class that embeds multiple ``SavedArticle``
instances:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S: might be helpful to describe this code more in-depth, similar to the description for the "Document Callbacks" example. Right now it's not super clear how it works


.. literalinclude:: /includes/data-modeling/callbacks.rb
:start-after: start-association-callback
:end-before: end-association-callback
:language: ruby
:emphasize-lines: 6, 10-15
:dedent:

Additional Information
----------------------

To learn how to prevent {+odm+} from running callbacks, see the
following references in the Active Record documentation:

- `Skipping Callbacks <{+active-record-docs+}/active_record_callbacks.html#skipping-callbacks>`__
- `Suppressing Callbacks <{+active-record-docs+}/active_record_callbacks.html#suppressing-callbacks>`__
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I: Looks like there isn't a "suppressing callbacks" section on this page, did you mean "suppressing saving"?

Suggested change
- `Suppressing Callbacks <{+active-record-docs+}/active_record_callbacks.html#suppressing-callbacks>`__
- `Suppressing Saving <{+active-record-docs+}/active_record_callbacks.html#suppressing-saving>`__

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops!


To learn about how {+odm+} manages callbacks in transactions, see the
:ref:`mongoid-data-txn` guide.

To learn how to access and change your MongoDB data, see the
:ref:`mongoid-interact-data` guides.
8 changes: 4 additions & 4 deletions source/data-modeling/validation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ of document fields in your collections.
{+odm+} includes ``ActiveModel::Validations`` from Active Record to
provide validation functionality, including an associated and uniqueness
validator. To learn more, see the `Active Record Validations
<https://guides.rubyonrails.org/active_record_validations.html>`__
<{+active-record-docs+}/active_record_validations.html>`__
Rails guide and `ActiveModel::Validations
<https://api.rubyonrails.org/classes/ActiveModel/Validations.html>`__
Rails API documentation.
Expand Down Expand Up @@ -284,14 +284,14 @@ Custom Validation Rules
You can use the ``validates_each`` and ``validates_with`` helpers to
create custom validators. To learn more about these helpers and view
examples, see the `validates_each
<https://guides.rubyonrails.org/active_record_validations.html#validates-each>`__
<{+active-record-docs+}/active_record_validations.html#validates-each>`__
and `validates_with
<https://guides.rubyonrails.org/active_record_validations.html#validates-with>`__
<{+active-record-docs+}/active_record_validations.html#validates-with>`__
references in the Active Record documentation.

To learn more about custom validators, see `Performing Custom
Validations
<https://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations>`__
<{+active-record-docs+}/active_record_validations.html#performing-custom-validations>`__
in the Active Record documentation.

Behavior
Expand Down
78 changes: 78 additions & 0 deletions source/includes/data-modeling/callbacks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# start-doc-callback
class Contact
include Mongoid::Document

field :name, type: String
field :phone, type: String

# Creates a callback to clean phone numbers before saving
before_save :process_phone

protected
def process_phone
self.phone = phone.gsub(/[^0-9]/, "") if attribute_present?("phone")
end

# Creates a callback to send a message about object deletion
after_destroy do
p "deleted the contact for #{name}"
end
end
# end-doc-callback

# start-doc-ops
Contact.create(name: 'Serena Atherton', phone: '999 555-3030')
# => `phone` field saved as '9995553030'
Contact.create(name: 'Zayba Haq', phone: '999 123?5050')
# => `phone` field saved as '9991235050'

Contact.first.destroy
# => Console message: "deleted the contact for Serena Atherton"
# end-doc-ops

# start-doc-set-syntax
class Contact
include Mongoid::Document

field :name, type: String
field :phone, type: String
field :aliases, type: Array, default: []

set_callback(:update, :before) do |document|
if document.name_changed?
document.push(aliases: document.name_was)
end
end
end

Contact.create(name: 'Xavier Bloom', phone: '4447779999')
Contact.first.update(name: 'Xav - coworker')
# Saved document in MongoDB:
# {"aliases":["Xavier Bloom"],"name":"Xav - coworker","phone":"4447779999"}
# end-doc-set-syntax

# start-association-callback
class User
include Mongoid::Document

field :username, type: String
# Registers the callback in the association statement
embeds_many :saved_articles, before_add: :send_message

protected
# Passes the association document as a parameter to the callback
def send_message(saved_article)
if saved_articles.count >= 10
p "you can't save more than 10 articles at a time"
throw(:abort)
end
end
end

class SavedArticle
include Mongoid::Document
embedded_in :user

field :url, type: String
end
# end-association-callback
2 changes: 1 addition & 1 deletion source/quick-start-rails.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ modeled and displayed. {+odm+} replaces the default Active Record
adapter for data modeling in Rails.

To learn more about Ruby on Rails, see the `Getting Started
with Rails <https://guides.rubyonrails.org/getting_started.html>`__
with Rails <{+active-record-docs+}/getting_started.html>`__
guide in the Rails documentation.

MongoDB Atlas is a fully managed cloud database service that hosts your
Expand Down
Loading