From 002026b004f3a419587102033d4c5c7f4798bf06 Mon Sep 17 00:00:00 2001 From: Jordan Smith Date: Thu, 9 Jan 2025 13:17:28 -0800 Subject: [PATCH 1/4] Add second section to associations page --- source/data-modeling/associations.txt | 406 ++++++++++++++++++ .../data-modeling/association-behaviors.rb | 226 ++++++++++ 2 files changed, 632 insertions(+) create mode 100644 source/includes/data-modeling/association-behaviors.rb diff --git a/source/data-modeling/associations.txt b/source/data-modeling/associations.txt index f441f598..6684af99 100644 --- a/source/data-modeling/associations.txt +++ b/source/data-modeling/associations.txt @@ -515,3 +515,409 @@ method to remove all embedded ``Album`` documents from the ``Band`` class: :language: ruby :start-after: # start-embedded-destroy-all :end-before: # end-embedded-destroy-all + +Association Behaviors +--------------------- + +When working with associations in {+odm+}, there are various behaviors you can +use to customize how associations are handled in your application. The following +sections describe common association behaviors. + +Extensions +~~~~~~~~~~ + +Extensions allow you to add specific functionality to an association. You can +define an extension on an association by specifying a block to the association +definition, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-extensions + :end-before: # end-extensions + :emphasize-lines: 4-6 + +Custom Association Names +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``class_name`` macro to specify a custom class name for an +association. This is useful when you want to use a different name for an +association than the name of the class. The following example uses the +``class_name`` macro to specify that an embedded association called ``records`` +represents the ``Album`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-name + :end-before: # end-custom-name + +Custom Keys +~~~~~~~~~~~ + +By default, {+odm+} uses the ``_id`` field of the parent class when looking up +associations. You can specify a different field to use by specifying the +``primary_key`` and ``foreign_key`` macros. The following example specifies a new +primary and foreign key for the ``Band`` and ``Album`` classes: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-keys + :end-before: # end-custom-keys + +If you are specifying a ``has_and_belongs_to_many`` association, you can also +set the ``inverse_primary_key`` and ``inverse_foreign_key`` macros. The +``inverse_primary_key`` macro specifies the field on the local model that the +remote model uses to look up the local model documents, and +the ``inverse_foreign_key`` macro specifies the field on the remote model +storing the values in ``inverse_primary_key``. + +The following example specifies a new primary and foreign key for the +``Band`` and ``Members`` classes in a ``has_and_belongs_to_many`` association: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-inverse-keys + :end-before: # end-custom-inverse-keys + +Custom Scopes +~~~~~~~~~~~~~ + +You can set a specific scope on an association by using the ``scope`` parameter. +The ``scope`` parameter determines which documents {+odm+} considers to be part +of an association. A scoped association returns only documents that match the +scope conditions when queried. You can set the ``scope`` to either a ``Proc`` with an arity +of zero, or a ``Symbol`` that references a named scope on the associated model. +The following example sets custom scopes on associations in a ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-scope + :end-before: # end-custom-scope + +.. note:: + + You can add documents that do not match the scope conditions to an + association. {+odm+} saves the documents to the database and they will appear + in associated memory, however, the documents are not present when the + association is queried in the future. + +Validations +~~~~~~~~~~~ + +By default, {+odm+} validates that the children of any associations are present +when it loads the association into memory by using the ``validates_associated`` +macro. This applies to the following association types: + +- ``embeds_many`` +- ``embeds_one`` +- ``has_many`` +- ``has_one`` +- ``has_and_belongs_to_many`` + +You can turn off this validation behavior by setting the ``validate`` macro to +``false`` when defining the association, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-validation-false + :end-before: # end-validation-false + +Polymorphism +~~~~~~~~~~~~ + +{+odm+} supports polymorphism on the child class in one-to-one and one-to-many associations. +Polymorphic associations allows a single association to contain objects of different class +types. You can define a polymorphic association by setting the ``polymorphic`` +option to ``true`` in a child association and adding the ``as`` option to the +parent association. The following example creates a polymorphic association in a +``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-polymorphic + :end-before: # end-polymorphic + +In the preceding example, the ``:featured`` association in the ``Band`` class can contain either a +``Label`` or ``Album`` document. + +.. important:: + + {+odm+} supports polymorphism only from child to parent. You cannot specify + a parent ``has_one`` or ``has_many`` association as polymorphic. + +``has_and_belongs_to_many`` associations do not support polymorphism. + +Custom Polymorphic Types +```````````````````````` + +Starting in version 9.0.2, {+odm+} adds support for custom polymorphic types through +a global registry. You can specify alternative keys to represent different +classes, decoupling your code from the data. The following example specifies +the string ``"artist"`` as an alternate key for the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-polymorphic + :end-before: # end-custom-polymorphic + +In the preceding example, the ``identify_as`` directive instructs {+odm+} +to store the ``Band`` class in the database as the string ``"artist"``. + +You can also specify multiple aliases, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-multiple-alias + :end-before: # end-multiple-alias + +In the preceding example, ``artist`` is the default name and the others are used +only for looking up records. This allows you to refactor your +code without breaking the associations in your data. + +Polymorphic type aliases are global. The keys you specify must be unique across your +entire code base. However, it is possible to register alternative resolvers, which +may be used for different subsets of your models. In this case, the keys must +only be unique for each resolver. The following example shows how to register +alternate resolvers: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-polymorphic-resolvers + :end-before: # end-polymorphic-resolvers + +Both ``Music::Band`` and ``Tools::Band`` are aliased as +``"bnd"``, but use their own resolver to avoid conflicts. + +Dependent Behavior +~~~~~~~~~~~~~~~~~~ + +You can provide ``dependent`` options to referenced associations to specify how +{+odm+} handles associated documents when a document is deleted. You can specify +the following options: + +- ``delete_all``: Deletes all child documents without running any model + callbacks. +- ``destroy``: Deletes the child documents and runs all model callbacks. +- ``nullify``: Sets the foreign key of the child documents to ``nil``. The child + document might become orphaned if it is only referenced by the parent. +- ``restrict_with_exception``: Raises an exception if the child document is not + empty. +- ``restrict_with_error``: Cancels the operation and returns ``false`` if the + child document is not empty. + +If you don't specify any ``dependent`` options, {+odm+} leaves the child +document unchanged when the parent document is deleted. The child document +continues to reference the deleted parent document. If the child document is +only referenced through the parent, the child document becomes orphaned. + +The following example specifies ``dependent`` options on the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-dependent + :end-before: # end-dependent + +Autosave Referenced Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, {+odm+} does not automatically save associated documents for +non-embedded associations when saving the parent document. Because of this, it's +possible to create dangling references to documents that don't exist through +associations. You can use the ``autosave`` option on a referenced association to +automatically save associated documents when saving the parent document. The +following example creates a ``Band`` class with an associated ``Album`` class +and specifies the ``autosave`` option on the ``Album`` association: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-autosave + :end-before: # end-autosave + :emphasize-lines: 10 + +.. note:: + + {+odm+} automatically adds autosave functionality to an association that uses + the ``accepts_nested_attributes_for`` option. + +You do not need to specify the ``autosave`` option for embedded associations +because {+odm+} saves embedded documents in the parent document. + +Autobuild +~~~~~~~~~ + +You can add the ``autobuild`` option to one-to-one associations, such as +``has_one`` and ``embeds_one``, to automatically instantiate a new document when +accessing a ``nil`` association. The following example adds the ``autobuild`` +option to an association on the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-autobuild + :end-before: # end-autobuild + +Touch +~~~~~ + +You can add the ``touch`` option to any ``belongs_to`` association to ensure +that {+odm+} touches the parent document any time the child document is updated. +When {+odm+} touches the parent document, it updates the document's +``updated_at`` field. The following example adds the ``touch`` option to an +association on the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-touch + :end-before: # end-touch + +You can also specify a string or a symbol in the ``touch`` option to specify +another field to touch on the parent association. When doing so, {+odm+} updates +the specified field and the ``updated_at`` field in the parent association. The +following example instructs {+odm+} to touch the ``bands_updated_at`` field: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-touch-specific + :end-before: # end-touch-specific + +.. note:: + + In embedded associations, when an embedded document is touched, {+odm+} + touches its parents recursively. Because of this, adding a ``touch`` + attribute to ``embedded_in`` association is unnecessary. {+odm+} does not + support specifying additional fields to touch in ``embedded_in`` + associations. + +Counter Cache +~~~~~~~~~~~~~ + +You can use the ``counter_cache`` option to store the number of objects +that belong to an associated field. When specifying this option, {+odm+} stores +an extra attribute on the associated models to store the count. Because of this, +you must specify the ``Mongoid::Attributes::Dynamic`` module in associated +classes. The following example adds the ``counter_cache`` option to a ``Band`` +class and specifies the ``Mongoid::Attributes::Dynamic`` in a ``Label`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-counter-cache + :end-before: # end-counter-cache + :emphasize-lines: 4, 9 + +Association Metadata +-------------------- + +When you define an association, {+odm+} stores metadata about that association. +You can access the metadata by calling the ``reflect_on_association`` method on +a model class or document, or by directly accessing the metadata on a specific +document. The following example shows how to access metadata by using the +``reflect_on_association`` method and by direct access: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-access-metadata + :end-before: # end-access-metadata + +.. note:: + + Replace ```` in the preceding example with the name of your + association. + +Attributes +~~~~~~~~~~ + +All associations contain attributes that store information about the associated +document. Associations contain the following attributes: + +- ``_target``: Contains the proxied document or documents +- ``_base``: Contains the document that the association is defined on +- ``_association``: Contains information about the association + +The following example accesses each of the preceding attributes: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-attributes + :end-before: # end-attributes + +The following table shows the information stored in the ``_association`` +attribute: + +.. list-table:: + :header-rows: 1 + :widths: 30 60 + + * - Method + - Description + * - ``Association#as`` + - Returns the name of the parent to a polymorphic child. + * - ``Association#as?`` + - Returns whether an ``as`` option exists. + * - ``Association#autobuilding?`` + - Returns whether the association is autobuilding. + * - ``Association#autosaving?`` + - Returns whether the association is autosaving. + * - ``Association#cascading_callbacks?`` + - Returns whether the association has callbacks cascaded down from the parent. + * - ``Association#class_name`` + - Returns the class name of the proxied document. + * - ``Association#cyclic?`` + - Returns whether the association is a cyclic association. + * - ``Association#dependent`` + - Returns the association's dependent option. + * - ``Association#destructive?`` + - Returns ``true`` if the association has a dependent delete or destroy method. + * - ``Association#embedded?`` + - Returns whether the association is embedded in another document. + * - ``Association#forced_nil_inverse?`` + - Returns whether the association has a ``nil`` inverse defined. + * - ``Association#foreign_key`` + - Returns the name of the foreign-key field. + * - ``Association#foreign_key_check`` + - Returns the name of the foreign-key field's dirty-check method. + * - ``Association#foreign_key_setter`` + - Returns the name of the foreign-key field's setter. + * - ``Association#indexed?`` + - Returns whether the foreign key is auto indexed. + * - ``Association#inverses`` + - Returns the names of all inverse associations. + * - ``Association#inverse`` + - Returns the name of a single inverse association. + * - ``Association#inverse_class_name`` + - Returns the class name of the association on the inverse side. + * - ``Association#inverse_foreign_key`` + - Returns the name of the foreign-key field on the inverse side. + * - ``Association#inverse_klass`` + - Returns the class of the association on the inverse side. + * - ``Association#inverse_association`` + - Returns the metadata of the association on the inverse side. + * - ``Association#inverse_of`` + - Returns the explicitly defined name of the inverse association. + * - ``Association#inverse_setter`` + - Returns the name of the method used to set the inverse. + * - ``Association#inverse_type`` + - Returns the name of the polymorphic-type field of the inverse. + * - ``Association#inverse_type_setter`` + - Returns the name of the polymorphic-type field's setter of the inverse. + * - ``Association#key`` + - Returns the name of the field in the attribute's hash used to get the association. + * - ``Association#klass`` + - Returns the class of the proxied documents in the association. + * - ``Association#name`` + - Returns the association name. + * - ``Association#options`` + - Returns self, for API compatibility with ActiveRecord. + * - ``Association#order`` + - Returns the custom sorting options on the association. + * - ``Association#polymorphic?`` + - Returns whether the association is polymorphic. + * - ``Association#setter`` + - Returns the name of the field to set the association. + * - ``Association#store_as`` + - Returns the name of the attribute in which to store an embedded association. + * - ``Association#touchable?`` + - Returns whether the association has a touch option. + * - ``Association#type`` + - Returns the name of the field to get the polymorphic type. + * - ``Association#type_setter`` + - Returns the name of the field to set the polymorphic type. + * - ``Association#validate?`` + - Returns whether the association has an associated validation. diff --git a/source/includes/data-modeling/association-behaviors.rb b/source/includes/data-modeling/association-behaviors.rb new file mode 100644 index 00000000..cf3a0bb3 --- /dev/null +++ b/source/includes/data-modeling/association-behaviors.rb @@ -0,0 +1,226 @@ +# start-extensions +class Band + include Mongoid::Document + embeds_many :albums do + def find_by_name(name) + where(name: name).first + end + end +end + +band.albums.find_by_name("Omega") # returns album "Omega" +# end-extensions + +# start-custom-name +class Band + include Mongoid::Document + embeds_many :records, class_name: "Album" +end +# end-custom-name + +# start-custom-keys +class Band + include Mongoid::Document + + field :band_id, type: String + has_many :albums, primary_key: 'band_id', foreign_key: 'band_id_ref' +end + +class Album + include Mongoid::Document + + field :band_id_ref, type: String + belongs_to :band, primary_key: 'band_id', foreign_key: 'band_id_ref' +end +# end-custom-keys + +# start-custom-inverse-keys +class Band + include Mongoid::Document + + field :band_id, type: String + field :member_ids, type: Array + + has_many :members, + primary_key: 'member_id', foreign_key: 'member_ids', + inverse_primary_key: 'band_id', inverse_foreign_key: 'band_ids' +end + +class Member + include Mongoid::Document + + field :member_id, type: String + field :band_ids, type: Array + + has_many :bands, + primary_key: 'band_id', foreign_key: 'band_ids', + inverse_primary_key: 'member_id', inverse_foreign_key: 'member_ids' +end +# end-custom-inverse-keys + +# start-custom-scope +class Band + include Mongoid::Document + + # Only return published albums + has_many :albums, scope: -> { where(published: true) } + + # Only return upcoming tours + has_many :tours, scope: :upcoming +end +# end-custom-scope + +# start-validation-false +class Band + include Mongoid::Document + + embeds_many :albums, validate: false +end +# end-validation-false + +# start-polymorphic +class Tour + include Mongoid::Document + + has_one :band, as: :featured +end + +class Label + include Mongoid::Document + + has_one :band, as: :featured +end + +class Band + include Mongoid::Document + + belongs_to :featured, polymorphic: true +end +# end-polymorphic + +# start-custom-polymorphic +class Band + include Mongoid::Document + + identify_as 'artist' + has_many :albums, as: :record +end +# end-custom-polymorphic + +# start-multiple-alias +class Band + include Mongoid::Document + + identify_as 'artist', 'group', 'troupe' + has_many :albums, as: :record +end +# end-multiple-alias + +# start-polymorphic-resolvers +Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :mus +Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :tool + +module Music + class Band + include Mongoid::Document + + identify_as 'bnd', resolver: :mus + end +end + +module Tools + class Band + include Mongoid::Document + + identify_as 'bnd', resolver: :tool + end +end +# end-polymorphic-resolvers + +# start-dependent +class Band + include Mongoid::Document + + has_many :albums, dependent: :delete_all + belongs_to :label, dependent: :nullify +end +# end-dependent + +# start-autosave +class Band + include Mongoid::Document + + has_many :albums +end + +class Album + include Mongoid::Document + + belongs_to :band, autosave: true +end + +band = Band.new +album = Album.create!(band: band) +# The band is persisted at this point. +# end-autosave + +# start-autobuild +class Band + include Mongoid::Document + + embeds_one :label, autobuild: true + has_one :producer, autobuild: true +end +# end-autobuild + +# start-touch +class Band + include Mongoid::Document + + field :name + belongs_to :label, touch: true +end +# end-touch + +# start-touch-specific +class Band + include Mongoid::Document + belongs_to :label, touch: :bands_updated_at +end +# end-touch-specific + +# start-counter-cache +class Band + include Mongoid::Document + + belongs_to :label, counter_cache: true +end + +class Label + include Mongoid::Document + include Mongoid::Attributes::Dynamic + + has_many :bands +end +# end-counter-cache + +# start-access-metadata +# Get the metadata for a named association from the class or document +Model.reflect_on_association(:) + +# Directly access metadata on a document +model.associations[:] +# end-access-metadata + +# start-attributes +class Band + include Mongoid::Document + + embeds_many :songs +end + +Band.songs = [ song ] +Band.songs._target # returns [ song ] +Band.songs._base # returns band +Band.songs._association # returns the association metadata +# end-attributes \ No newline at end of file From 8f6a5f144d9bd9265cba7696c450a83f67361df4 Mon Sep 17 00:00:00 2001 From: Jordan Smith Date: Fri, 10 Jan 2025 07:48:13 -0800 Subject: [PATCH 2/4] edits --- source/data-modeling/associations.txt | 39 ++++++++++--------- .../data-modeling/association-behaviors.rb | 10 +++-- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/source/data-modeling/associations.txt b/source/data-modeling/associations.txt index 6684af99..917781b5 100644 --- a/source/data-modeling/associations.txt +++ b/source/data-modeling/associations.txt @@ -519,15 +519,15 @@ method to remove all embedded ``Album`` documents from the ``Band`` class: Association Behaviors --------------------- -When working with associations in {+odm+}, there are various behaviors you can -use to customize how associations are handled in your application. The following +When working with associations in {+odm+}, there are various behaviors that +allow you to customize how associations are handled in your application. The following sections describe common association behaviors. Extensions ~~~~~~~~~~ Extensions allow you to add specific functionality to an association. You can -define an extension on an association by specifying a block to the association +define an extension on an association by specifying a block in the association definition, as shown in the following example: .. literalinclude:: /includes/data-modeling/association-behaviors.rb @@ -556,7 +556,7 @@ Custom Keys By default, {+odm+} uses the ``_id`` field of the parent class when looking up associations. You can specify a different field to use by specifying the ``primary_key`` and ``foreign_key`` macros. The following example specifies a new -primary and foreign key for the ``Band`` and ``Album`` classes: +primary and foreign key for the ``albums`` association on a ``Band`` class: .. literalinclude:: /includes/data-modeling/association-behaviors.rb :language: ruby @@ -566,9 +566,9 @@ primary and foreign key for the ``Band`` and ``Album`` classes: If you are specifying a ``has_and_belongs_to_many`` association, you can also set the ``inverse_primary_key`` and ``inverse_foreign_key`` macros. The ``inverse_primary_key`` macro specifies the field on the local model that the -remote model uses to look up the local model documents, and -the ``inverse_foreign_key`` macro specifies the field on the remote model -storing the values in ``inverse_primary_key``. +remote model uses to look up the documents. +The ``inverse_foreign_key`` macro specifies the field on the remote model +that stores the values found in ``inverse_primary_key``. The following example specifies a new primary and foreign key for the ``Band`` and ``Members`` classes in a ``has_and_belongs_to_many`` association: @@ -577,6 +577,7 @@ The following example specifies a new primary and foreign key for the :language: ruby :start-after: # start-custom-inverse-keys :end-before: # end-custom-inverse-keys + :emphasize-lines: 9, 20 Custom Scopes ~~~~~~~~~~~~~ @@ -585,7 +586,7 @@ You can set a specific scope on an association by using the ``scope`` parameter. The ``scope`` parameter determines which documents {+odm+} considers to be part of an association. A scoped association returns only documents that match the scope conditions when queried. You can set the ``scope`` to either a ``Proc`` with an arity -of zero, or a ``Symbol`` that references a named scope on the associated model. +of zero or a ``Symbol`` that references a named scope on the associated model. The following example sets custom scopes on associations in a ``Band`` class: .. literalinclude:: /includes/data-modeling/association-behaviors.rb @@ -624,7 +625,7 @@ You can turn off this validation behavior by setting the ``validate`` macro to Polymorphism ~~~~~~~~~~~~ -{+odm+} supports polymorphism on the child class in one-to-one and one-to-many associations. +{+odm+} supports polymorphism on the child classes of one-to-one and one-to-many associations. Polymorphic associations allows a single association to contain objects of different class types. You can define a polymorphic association by setting the ``polymorphic`` option to ``true`` in a child association and adding the ``as`` option to the @@ -674,8 +675,8 @@ only for looking up records. This allows you to refactor your code without breaking the associations in your data. Polymorphic type aliases are global. The keys you specify must be unique across your -entire code base. However, it is possible to register alternative resolvers, which -may be used for different subsets of your models. In this case, the keys must +entire code base. However, it is possible to register alternative resolvers that +can be used for different subsets of your models. In this case, the keys must only be unique for each resolver. The following example shows how to register alternate resolvers: @@ -698,7 +699,7 @@ the following options: callbacks. - ``destroy``: Deletes the child documents and runs all model callbacks. - ``nullify``: Sets the foreign key of the child documents to ``nil``. The child - document might become orphaned if it is only referenced by the parent. + document might become orphaned if it is referenced by only the parent. - ``restrict_with_exception``: Raises an exception if the child document is not empty. - ``restrict_with_error``: Cancels the operation and returns ``false`` if the @@ -707,7 +708,7 @@ the following options: If you don't specify any ``dependent`` options, {+odm+} leaves the child document unchanged when the parent document is deleted. The child document continues to reference the deleted parent document. If the child document is -only referenced through the parent, the child document becomes orphaned. +referenced through only the parent, the child document becomes orphaned. The following example specifies ``dependent`` options on the ``Band`` class: @@ -719,13 +720,13 @@ The following example specifies ``dependent`` options on the ``Band`` class: Autosave Referenced Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -By default, {+odm+} does not automatically save associated documents for +By default, {+odm+} does not automatically save associated documents from non-embedded associations when saving the parent document. Because of this, it's possible to create dangling references to documents that don't exist through associations. You can use the ``autosave`` option on a referenced association to automatically save associated documents when saving the parent document. The following example creates a ``Band`` class with an associated ``Album`` class -and specifies the ``autosave`` option on the ``Album`` association: +and specifies the ``autosave`` option: .. literalinclude:: /includes/data-modeling/association-behaviors.rb :language: ruby @@ -782,7 +783,7 @@ following example instructs {+odm+} to touch the ``bands_updated_at`` field: In embedded associations, when an embedded document is touched, {+odm+} touches its parents recursively. Because of this, adding a ``touch`` - attribute to ``embedded_in`` association is unnecessary. {+odm+} does not + attribute to an ``embedded_in`` association is unnecessary. {+odm+} does not support specifying additional fields to touch in ``embedded_in`` associations. @@ -792,7 +793,7 @@ Counter Cache You can use the ``counter_cache`` option to store the number of objects that belong to an associated field. When specifying this option, {+odm+} stores an extra attribute on the associated models to store the count. Because of this, -you must specify the ``Mongoid::Attributes::Dynamic`` module in associated +you must specify the ``Mongoid::Attributes::Dynamic`` module in the associated classes. The following example adds the ``counter_cache`` option to a ``Band`` class and specifies the ``Mongoid::Attributes::Dynamic`` in a ``Label`` class: @@ -818,7 +819,7 @@ document. The following example shows how to access metadata by using the .. note:: - Replace ```` in the preceding example with the name of your + Replace ```` in the preceding example with the name of your association. Attributes @@ -828,7 +829,7 @@ All associations contain attributes that store information about the associated document. Associations contain the following attributes: - ``_target``: Contains the proxied document or documents -- ``_base``: Contains the document that the association is defined on +- ``_base``: Contains the document on which the association is defined - ``_association``: Contains information about the association The following example accesses each of the preceding attributes: diff --git a/source/includes/data-modeling/association-behaviors.rb b/source/includes/data-modeling/association-behaviors.rb index cf3a0bb3..a74d0739 100644 --- a/source/includes/data-modeling/association-behaviors.rb +++ b/source/includes/data-modeling/association-behaviors.rb @@ -1,6 +1,7 @@ # start-extensions class Band include Mongoid::Document + embeds_many :albums do def find_by_name(name) where(name: name).first @@ -14,6 +15,7 @@ def find_by_name(name) # start-custom-name class Band include Mongoid::Document + embeds_many :records, class_name: "Album" end # end-custom-name @@ -62,10 +64,9 @@ class Member class Band include Mongoid::Document - # Only return published albums has_many :albums, scope: -> { where(published: true) } - # Only return upcoming tours + # Uses a scope called "upcoming" on the Tour model has_many :tours, scope: :upcoming end # end-custom-scope @@ -184,8 +185,9 @@ class Band # start-touch-specific class Band - include Mongoid::Document - belongs_to :label, touch: :bands_updated_at + include Mongoid::Document + + belongs_to :label, touch: :bands_updated_at end # end-touch-specific From 7de011e447b20102bb2d0e3aa8c65f4014574d3e Mon Sep 17 00:00:00 2001 From: Jordan Smith Date: Mon, 13 Jan 2025 07:54:25 -0800 Subject: [PATCH 3/4] feedback --- source/data-modeling/associations.txt | 141 +++++++++++++------------- 1 file changed, 73 insertions(+), 68 deletions(-) diff --git a/source/data-modeling/associations.txt b/source/data-modeling/associations.txt index 917781b5..0cb8c2b1 100644 --- a/source/data-modeling/associations.txt +++ b/source/data-modeling/associations.txt @@ -25,7 +25,7 @@ this guide, you can learn about the different types of associations that {+odm+} supports and how to use them in your application. Referenced Associations ------------------------- +----------------------- Referenced associations allow you to create a relationship between two models where one model references the other. {+odm+} supports the following referenced @@ -516,17 +516,16 @@ method to remove all embedded ``Album`` documents from the ``Band`` class: :start-after: # start-embedded-destroy-all :end-before: # end-embedded-destroy-all -Association Behaviors ---------------------- +Customize Association Behavior +------------------------------ -When working with associations in {+odm+}, there are various behaviors that -allow you to customize how associations are handled in your application. The following -sections describe common association behaviors. +You can use {+odm+} to customize how associations behave in your application. +The following sections describe ways to customize association behaviors. Extensions ~~~~~~~~~~ -Extensions allow you to add specific functionality to an association. You can +Extensions allow you to add custom functionality to an association. You can define an extension on an association by specifying a block in the association definition, as shown in the following example: @@ -540,8 +539,8 @@ Custom Association Names ~~~~~~~~~~~~~~~~~~~~~~~~ You can use the ``class_name`` macro to specify a custom class name for an -association. This is useful when you want to use a different name for an -association than the name of the class. The following example uses the +association. This is useful when you want to name the association something other +than the name of the class. The following example uses the ``class_name`` macro to specify that an embedded association called ``records`` represents the ``Album`` class: @@ -554,7 +553,7 @@ Custom Keys ~~~~~~~~~~~ By default, {+odm+} uses the ``_id`` field of the parent class when looking up -associations. You can specify a different field to use by specifying the +associations. You can specify different fields to use by using the ``primary_key`` and ``foreign_key`` macros. The following example specifies a new primary and foreign key for the ``albums`` association on a ``Band`` class: @@ -564,7 +563,7 @@ primary and foreign key for the ``albums`` association on a ``Band`` class: :end-before: # end-custom-keys If you are specifying a ``has_and_belongs_to_many`` association, you can also -set the ``inverse_primary_key`` and ``inverse_foreign_key`` macros. The +use the ``inverse_primary_key`` and ``inverse_foreign_key`` macros. The ``inverse_primary_key`` macro specifies the field on the local model that the remote model uses to look up the documents. The ``inverse_foreign_key`` macro specifies the field on the remote model @@ -582,8 +581,8 @@ The following example specifies a new primary and foreign key for the Custom Scopes ~~~~~~~~~~~~~ -You can set a specific scope on an association by using the ``scope`` parameter. -The ``scope`` parameter determines which documents {+odm+} considers to be part +You can specify the scope of an association by using the ``scope`` parameter. +The ``scope`` parameter determines the documents that {+odm+} considers part of an association. A scoped association returns only documents that match the scope conditions when queried. You can set the ``scope`` to either a ``Proc`` with an arity of zero or a ``Symbol`` that references a named scope on the associated model. @@ -598,15 +597,15 @@ The following example sets custom scopes on associations in a ``Band`` class: You can add documents that do not match the scope conditions to an association. {+odm+} saves the documents to the database and they will appear - in associated memory, however, the documents are not present when the - association is queried in the future. + in associated memory, however, you won't see the documents when querying the + association. Validations ~~~~~~~~~~~ -By default, {+odm+} validates that the children of any associations are present -when it loads the association into memory by using the ``validates_associated`` -macro. This applies to the following association types: +When {+odm+} loads an association into memory, by default, it uses the +``validates_associated`` macro to validate that any children are also present. +{+odm+} validates children for the following association types: - ``embeds_many`` - ``embeds_one`` @@ -650,7 +649,7 @@ In the preceding example, the ``:featured`` association in the ``Band`` class ca Custom Polymorphic Types ```````````````````````` -Starting in version 9.0.2, {+odm+} adds support for custom polymorphic types through +Starting in version 9.0.2, {+odm+} supports custom polymorphic types through a global registry. You can specify alternative keys to represent different classes, decoupling your code from the data. The following example specifies the string ``"artist"`` as an alternate key for the ``Band`` class: @@ -675,9 +674,9 @@ only for looking up records. This allows you to refactor your code without breaking the associations in your data. Polymorphic type aliases are global. The keys you specify must be unique across your -entire code base. However, it is possible to register alternative resolvers that +entire code base. However, you can register alternative resolvers that can be used for different subsets of your models. In this case, the keys must -only be unique for each resolver. The following example shows how to register +be unique only for each resolver. The following example shows how to register alternate resolvers: .. literalinclude:: /includes/data-modeling/association-behaviors.rb @@ -686,7 +685,7 @@ alternate resolvers: :end-before: # end-polymorphic-resolvers Both ``Music::Band`` and ``Tools::Band`` are aliased as -``"bnd"``, but use their own resolver to avoid conflicts. +``"bnd"``, but each model uses its own resolver to avoid conflicts. Dependent Behavior ~~~~~~~~~~~~~~~~~~ @@ -707,7 +706,7 @@ the following options: If you don't specify any ``dependent`` options, {+odm+} leaves the child document unchanged when the parent document is deleted. The child document -continues to reference the deleted parent document. If the child document is +continues to reference the deleted parent document, and if it is referenced through only the parent, the child document becomes orphaned. The following example specifies ``dependent`` options on the ``Band`` class: @@ -721,9 +720,10 @@ Autosave Referenced Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, {+odm+} does not automatically save associated documents from -non-embedded associations when saving the parent document. Because of this, it's -possible to create dangling references to documents that don't exist through -associations. You can use the ``autosave`` option on a referenced association to +non-embedded associations when saving the parent document. This can +result in dangling references to documents that don't exist. + +You can use the ``autosave`` option on a referenced association to automatically save associated documents when saving the parent document. The following example creates a ``Band`` class with an associated ``Album`` class and specifies the ``autosave`` option: @@ -758,21 +758,23 @@ option to an association on the ``Band`` class: Touch ~~~~~ -You can add the ``touch`` option to any ``belongs_to`` association to ensure -that {+odm+} touches the parent document any time the child document is updated. -When {+odm+} touches the parent document, it updates the document's -``updated_at`` field. The following example adds the ``touch`` option to an -association on the ``Band`` class: +When {+odm+} *touches* a document, it updates the document's +``updated_at`` field to the current date and time. You can add the ``touch`` +option to any ``belongs_to`` association to ensure that {+odm+} touches the +parent document whenever the child document is updated. The following example +adds the ``touch`` option to an association on the ``Band`` class: .. literalinclude:: /includes/data-modeling/association-behaviors.rb :language: ruby :start-after: # start-touch :end-before: # end-touch -You can also specify a string or a symbol in the ``touch`` option to specify -another field to touch on the parent association. When doing so, {+odm+} updates -the specified field and the ``updated_at`` field in the parent association. The -following example instructs {+odm+} to touch the ``bands_updated_at`` field: +You can also use the ``touch`` option to specify another field on the parent +association, as a string or a symbol. When {+odm+} touches the parent +association, it sets both the ``updated_at`` field and the specified field +to the current date and time. + +The following example instructs {+odm+} to touch the ``bands_updated_at`` field: .. literalinclude:: /includes/data-modeling/association-behaviors.rb :language: ruby @@ -783,18 +785,21 @@ following example instructs {+odm+} to touch the ``bands_updated_at`` field: In embedded associations, when an embedded document is touched, {+odm+} touches its parents recursively. Because of this, adding a ``touch`` - attribute to an ``embedded_in`` association is unnecessary. {+odm+} does not - support specifying additional fields to touch in ``embedded_in`` - associations. + attribute to an ``embedded_in`` association is unnecessary. + + {+odm+} does not support specifying additional fields to touch in + ``embedded_in`` associations. Counter Cache ~~~~~~~~~~~~~ You can use the ``counter_cache`` option to store the number of objects -that belong to an associated field. When specifying this option, {+odm+} stores +that belong to an associated field. When you specify this option, {+odm+} stores an extra attribute on the associated models to store the count. Because of this, you must specify the ``Mongoid::Attributes::Dynamic`` module in the associated -classes. The following example adds the ``counter_cache`` option to a ``Band`` +classes. + +The following example adds the ``counter_cache`` option to a ``Band`` class and specifies the ``Mongoid::Attributes::Dynamic`` in a ``Label`` class: .. literalinclude:: /includes/data-modeling/association-behaviors.rb @@ -828,9 +833,9 @@ Attributes All associations contain attributes that store information about the associated document. Associations contain the following attributes: -- ``_target``: Contains the proxied document or documents -- ``_base``: Contains the document on which the association is defined -- ``_association``: Contains information about the association +- ``_target``: The proxied document or documents +- ``_base``: The document on which the association is defined +- ``_association``: Information about the association The following example accesses each of the preceding attributes: @@ -849,7 +854,7 @@ attribute: * - Method - Description * - ``Association#as`` - - Returns the name of the parent to a polymorphic child. + - The name of the parent to a polymorphic child. * - ``Association#as?`` - Returns whether an ``as`` option exists. * - ``Association#autobuilding?`` @@ -859,11 +864,11 @@ attribute: * - ``Association#cascading_callbacks?`` - Returns whether the association has callbacks cascaded down from the parent. * - ``Association#class_name`` - - Returns the class name of the proxied document. + - The class name of the proxied document. * - ``Association#cyclic?`` - Returns whether the association is a cyclic association. * - ``Association#dependent`` - - Returns the association's dependent option. + - The association's dependent option. * - ``Association#destructive?`` - Returns ``true`` if the association has a dependent delete or destroy method. * - ``Association#embedded?`` @@ -871,54 +876,54 @@ attribute: * - ``Association#forced_nil_inverse?`` - Returns whether the association has a ``nil`` inverse defined. * - ``Association#foreign_key`` - - Returns the name of the foreign-key field. + - The name of the foreign-key field. * - ``Association#foreign_key_check`` - - Returns the name of the foreign-key field's dirty-check method. + - The name of the foreign-key field's dirty-check method. * - ``Association#foreign_key_setter`` - - Returns the name of the foreign-key field's setter. + - The name of the foreign-key field's setter. * - ``Association#indexed?`` - Returns whether the foreign key is auto indexed. * - ``Association#inverses`` - - Returns the names of all inverse associations. + - The names of all inverse associations. * - ``Association#inverse`` - - Returns the name of a single inverse association. + - The name of a single inverse association. * - ``Association#inverse_class_name`` - - Returns the class name of the association on the inverse side. + - The class name of the association on the inverse side. * - ``Association#inverse_foreign_key`` - - Returns the name of the foreign-key field on the inverse side. + - The name of the foreign-key field on the inverse side. * - ``Association#inverse_klass`` - - Returns the class of the association on the inverse side. + - The class of the association on the inverse side. * - ``Association#inverse_association`` - - Returns the metadata of the association on the inverse side. + - The metadata of the association on the inverse side. * - ``Association#inverse_of`` - - Returns the explicitly defined name of the inverse association. + - The explicitly defined name of the inverse association. * - ``Association#inverse_setter`` - - Returns the name of the method used to set the inverse. + - The name of the method used to set the inverse. * - ``Association#inverse_type`` - - Returns the name of the polymorphic-type field of the inverse. + - The name of the polymorphic-type field of the inverse. * - ``Association#inverse_type_setter`` - - Returns the name of the polymorphic-type field's setter of the inverse. + - The name of the polymorphic-type field's setter of the inverse. * - ``Association#key`` - - Returns the name of the field in the attribute's hash used to get the association. + - The name of the field in the attribute's hash that is used to get the association. * - ``Association#klass`` - - Returns the class of the proxied documents in the association. + - The class of the proxied documents in the association. * - ``Association#name`` - - Returns the association name. + - The association name. * - ``Association#options`` - - Returns self, for API compatibility with ActiveRecord. + - Returns ``self``, for API compatibility with ActiveRecord. * - ``Association#order`` - - Returns the custom sorting options on the association. + - The custom sorting options on the association. * - ``Association#polymorphic?`` - Returns whether the association is polymorphic. * - ``Association#setter`` - - Returns the name of the field to set the association. + - The name of the field to set the association. * - ``Association#store_as`` - - Returns the name of the attribute in which to store an embedded association. + - The name of the attribute in which to store an embedded association. * - ``Association#touchable?`` - Returns whether the association has a touch option. * - ``Association#type`` - - Returns the name of the field to get the polymorphic type. + - The name of the field to get the polymorphic type. * - ``Association#type_setter`` - - Returns the name of the field to set the polymorphic type. + - The name of the field to set the polymorphic type. * - ``Association#validate?`` - Returns whether the association has an associated validation. From 2f749b6a7745b27a3a44eaf6a2cdb3661fea4ad1 Mon Sep 17 00:00:00 2001 From: Jordan Smith Date: Mon, 13 Jan 2025 11:03:03 -0800 Subject: [PATCH 4/4] feedback --- source/data-modeling/associations.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/data-modeling/associations.txt b/source/data-modeling/associations.txt index 0cb8c2b1..88d65315 100644 --- a/source/data-modeling/associations.txt +++ b/source/data-modeling/associations.txt @@ -597,7 +597,7 @@ The following example sets custom scopes on associations in a ``Band`` class: You can add documents that do not match the scope conditions to an association. {+odm+} saves the documents to the database and they will appear - in associated memory, however, you won't see the documents when querying the + in associated memory. However, you won't see the documents when querying the association. Validations