diff --git a/snooty.toml b/snooty.toml index d3adf1c5..8ea40b05 100644 --- a/snooty.toml +++ b/snooty.toml @@ -31,3 +31,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" diff --git a/source/add-existing.txt b/source/add-existing.txt index ae75633a..fdd6fde2 100644 --- a/source/add-existing.txt +++ b/source/add-existing.txt @@ -130,10 +130,10 @@ for each Rails component, as shown in the following sample .. note:: Because they rely on Active Record, the `ActionText - `__, + <{+active-record-docs+}/action_text_overview.html>`__, `ActiveStorage `__, and `ActionMailbox - `__ + <{+active-record-docs+}/action_mailbox_basics.html>`__ adapters cannot be used alongside {+odm+}. Disable Active Record Adapters diff --git a/source/data-modeling.txt b/source/data-modeling.txt index b78317fb..90fa06cd 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -20,6 +20,7 @@ Model Your Data Persistence Configuration Inheritance Document Validation + Callbacks Data Associations Optimize Queries With Indexes @@ -43,6 +44,9 @@ 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. + - :ref:`mongoid-associations`: Learn how to create and manage data associations in your model classes. diff --git a/source/data-modeling/callbacks.txt b/source/data-modeling/callbacks.txt new file mode 100644 index 00000000..0eb74517 --- /dev/null +++ b/source/data-modeling/callbacks.txt @@ -0,0 +1,178 @@ +.. _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 ` 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`` +- ``around_destroy`` +- ``after_destroy`` + +To learn more about any of the preceding callback types, see the +`ActiveRecord::Callbacks +`__ +reference in the Rails API documentation. + +You can implement callbacks in both top-level and embedded document +models. + +.. 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 outside of your program's core functionality, +such as queueing up background jobs. + +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 + ``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 + 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 +alternatively use the ``set_callback`` class method syntax to register +callbacks. The following code demonstrates how to use this syntax to +create a callback that stores original values of the ``name`` field in +the ``aliases`` array: + +.. 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 to limit the number of embedded documents for a single +instance: + +.. 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 Saving <{+active-record-docs+}/active_record_callbacks.html#suppressing-saving>`__ + +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. diff --git a/source/data-modeling/validation.txt b/source/data-modeling/validation.txt index 9f96c72f..5d87a0a2 100644 --- a/source/data-modeling/validation.txt +++ b/source/data-modeling/validation.txt @@ -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 -`__ +<{+active-record-docs+}/active_record_validations.html>`__ Rails guide and `ActiveModel::Validations `__ Rails API documentation. @@ -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 -`__ +<{+active-record-docs+}/active_record_validations.html#validates-each>`__ and `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 -`__ +<{+active-record-docs+}/active_record_validations.html#performing-custom-validations>`__ in the Active Record documentation. Behavior diff --git a/source/includes/data-modeling/callbacks.rb b/source/includes/data-modeling/callbacks.rb new file mode 100644 index 00000000..c48e04d5 --- /dev/null +++ b/source/includes/data-modeling/callbacks.rb @@ -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 \ No newline at end of file diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index 8f3dc972..60de78da 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -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 `__ +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