Navigation

Associations

Referenced Associations

Mongoid supports the has_one, has_many, belongs_to and has_and_belongs_to_many associations familiar to ActiveRecord users.

Has One

Use the has_one macro to declare that the parent has a child stored in a separate collection. The child is optional by default:

class Band
  include Mongoid::Document

  has_one :studio
end

When using has_one, the child model must use belongs_to to declare the association with the parent:

class Studio
  include Mongoid::Document

  belongs_to :band
end

Given the above definitions, every child document contains a reference to its respective parent document:

band = Band.create!(studio: Studio.new)
# => #<Band _id: 600114fa48966848ad5bd392, >

band.studio
# => #<Studio _id: 600114fa48966848ad5bd391, band_id: BSON::ObjectId('600114fa48966848ad5bd392')>

Use validations to require that the child is present:

class Band
  include Mongoid::Document

  has_one :studio

  validates_presence_of :studio
end

Has Many

Use the has_many association to declare that the parent has zero or more children stored in a separate collection:

class Band
  include Mongoid::Document

  has_many :members
end

Like with has_one, the child model must use belongs_to to declare the association with the parent:

class Member
  include Mongoid::Document

  belongs_to :band
end

Also as with has_one, the child documents contain references to their respective parents:

band = Band.create!(members: [Member.new])
# => #<Band _id: 6001166d4896684910b8d1c5, >

band.members
# => [#<Member _id: 6001166d4896684910b8d1c6, band_id: BSON::ObjectId('6001166d4896684910b8d1c5')>]

Use validations to require that at least one child is present:

class Band
  include Mongoid::Document

  has_many :members

  validates_presence_of :members
end

Queries

any?

Use the any? method on the association to efficiently determine whether the association contains any documents, without retrieving the entire set of documents from the database:

band = Band.first
band.members.any?

any? also implements the Enumerable#any? API, allowing filtering with a block:

band = Band.first
band.members.any? { |member| member.instrument == 'piano' }

… or by a class name which can be useful for polymorphic associations:

class Drummer < Member
end

band = Band.first
band.members.any?(Drummer)

If the association is already loaded, any? inspects the loaded documents and does not query the database:

band = Band.first
# Queries the database
band.members.any?

band.members.to_a

# Does not query the database
band.members.any?

Note that simply calling any? would not load the association (since any? only retrieves the _id field of the first matching document).

exists?

The exists? method on the association determines whether there are any persisted documents in the association. Unlike the any? method:

  • exists? always queries the database, even if the association is already loaded.
  • exists? does not consider non-persisted documents.
  • exists? does not allow filtering in the application like any? does, and does not take any arguments.

The following example illustrates the difference between exists? and any?:

band = Band.create!
# Member is not persisted.
band.members.build

band.members.any?
# => true
band.members.exists?
# => false

# Persist the member.
band.members.map(&:save!)

band.members.any?
# => true
band.members.exists?
# => true

Belongs To

Use the belongs_to macro to associate a child with a parent stored in a separate collection. The _id of the parent (if a parent is associated) is stored in the child.

By default, if a belongs_to association is defined on a model, it must be provided a value for a model instance to be saved. Use the optional: true` option to make the instances persistable without specifying the parent:

class Band
  include Mongoid::Document

  has_one :studio
end

class Studio
  include Mongoid::Document

  belongs_to :band, optional: true
end

studio = Studio.create!
# => #<Studio _id: 600118184896684987aa884f, band_id: nil>

To change the default behavior of belongs_to associations to not require their respective parents globally, set the belongs_to_required_by_default configuration option to false.

Although has_one and has_many associations require the corresponding belongs_to association to be defined on the child, belongs_to may also be used without a corresponding has_one or has_many macro. In this case the child is not accessible from the parent but the parent is accessible from the child:

class Band
  include Mongoid::Document
end

class Studio
  include Mongoid::Document

  belongs_to :band
end

For clarity it is possible to add the inverse_of: nil option in cases when the parent does not define the association:

class Band
  include Mongoid::Document
end

class Studio
  include Mongoid::Document

  belongs_to :band, inverse_of: nil
end

Has And Belongs To Many

Use the has_and_belongs_to_many macro to declare a many-to-many association:

class Band
  include Mongoid::Document

  has_and_belongs_to_many :tags
end

class Tag
  include Mongoid::Document

  has_and_belongs_to_many :bands
end

Both model instances store a list of ids of the associated models, if any:

band = Band.create!(tags: [Tag.create!])
# => #<Band _id: 60011d554896684b8b910a2a, tag_ids: [BSON::ObjectId('60011d554896684b8b910a29')]>

band.tags
# => [#<Tag _id: 60011d554896684b8b910a29, band_ids: [BSON::ObjectId('60011d554896684b8b910a2a')]>]

You can create a one-sided has_and_belongs_to_many association to store the ids only in one document using the inverse_of: nil option:

class Band
  include Mongoid::Document

  has_and_belongs_to_many :tags, inverse_of: nil
end

class Tag
  include Mongoid::Document
end

band = Band.create!(tags: [Tag.create!])
# => #<Band _id: 60011dbc4896684bbbaa9255, tag_ids: [BSON::ObjectId('60011dbc4896684bbbaa9254')]>

band.tags
# => [#<Tag _id: 60011dbc4896684bbbaa9254, >]

A one-sided has_and_belongs_to_many association is, naturally, only usable from the model where it is defined.

Note

Given two models, A and B where A has_and_belongs_to_many B, when adding a document of type B to the HABTM association on a document of type A, Mongoid will not update the updated_at field for the document of type A, but will update the updated_at field for the document of type B.

Querying Referenced Associations

In most cases, efficient queries across referenced associations (and in general involving data or conditions or multiple collections) are performed using the aggregation pipeline. Mongoid helpers for constructing aggregation pipeline queries are described in the aggregation pipeline section.

For simple queries, the use of aggregation pipeline may be avoided and associations may be queried directly. When querying associations directly, all conditions must be on that association’s collection only (which typically means association in question and any associations embedded in it).

For example, given the following models:

class Band
  include Mongoid::Document

  has_many :tours
  has_many :awards

  field :name, type: String
end

class Tour
  include Mongoid::Document

  belongs_to :band

  field :year, type: Integer
end

class Award
  include Mongoid::Document

  belongs_to :band

  field :name, type: String
end

One could retrieve all bands that have toured since 2000 as follows:

band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id)
bands = Band.find(band_ids)

The conditions on Tour can be arbitrarily complex, but they must all be on the same Tour document (or documents embedded in Tour).

To find awards for bands that have toured since 2000:

band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id)
awards = Award.where(band_id: {'$in' => band_ids})

Embedded Associations

Thanks to MongoDB’s document model, Mongoid also offers embedded associations which allow documents of different types to be stored hierarchically in the same collection. Embedded associations are defined using embeds_one, embeds_many and embedded_in macros, plus recursively_embeds_one and recursively_embeds_many for recursive embedding.

Embeds One

One to one associations where the children are embedded in the parent document are defined using Mongoid’s embeds_one and embedded_in macros.

Defining

The parent document of the association should use the embeds_one macro to indicate is has one embedded child, where the document that is embedded uses embedded_in. Definitions are required on both sides to the association in order for it to work properly.

class Band
  include Mongoid::Document
  embeds_one :label
end

class Label
  include Mongoid::Document
  field :name, type: String
  embedded_in :band
end

Storage

Documents that are embedded using the embeds_one macro are stored as a hash inside the parent in the parent’s database collection.

{
  "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
  "label" : {
    "_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
    "name" : "Mute",
  }
}

You can optionally tell Mongoid to store the embedded document in a different attribute other than the name, by providing the :store_as option.

class Band
  include Mongoid::Document
  embeds_one :label, store_as: "lab"
end

Embeds Many

One to many relationships where the children are embedded in the parent document are defined using Mongoid’s embeds_many and embedded_in macros.

Defining

The parent document of the association should use the embeds_many macro to indicate it has many embedded children, where the document that is embedded uses embedded_in. Definitions are required on both sides of the association in order for it to work properly.

class Band
  include Mongoid::Document
  embeds_many :albums
end

class Album
  include Mongoid::Document
  field :name, type: String
  embedded_in :band
end

Storage

Documents that are embedded using the embeds_many macro are stored as an array of hashes inside the parent in the parent’s database collection.

{
  "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
  "albums" : [
    {
      "_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
      "name" : "Violator",
    }
  ]
}

You can optionally tell Mongoid to store the embedded document in a different attribute other than the name, by providing the :store_as option.

class Band
  include Mongoid::Document
  embeds_many :albums, store_as: "albs"
end

Recursive Embedding

A document can recursively embed itself using recursively_embeds_one or recursively_embeds_many, which provides accessors for the parent and children via parent_ and child_ methods.

class Tag
  include Mongoid::Document
  field :name, type: String
  recursively_embeds_many
end

root = Tag.new(name: "programming")
child_one = root.child_tags.build
child_two = root.child_tags.build

root.child_tags # [ child_one, child_two ]
child_one.parent_tag # [ root ]
child_two.parent_tag # [ root ]

class Node
  include Mongoid::Document
  recursively_embeds_one
end

root = Node.new
child = Node.new
root.child_node = child

root.child_node # child
child.parent_node # root

Referencing Vs Embedding

While a complete discussion of referencing vs embedding is beyond the scope of this tutorial, here are some high level considerations for choosing one over the other.

When an association is embedded, both parent and child documents are stored in the same collection. This permits efficient persistence and retrieval when both are used/needed. For example, if the navigation bar on a web site shows attributes of a user that are stored in documents themselves, it is often a good idea to use embedded associations.

Using embedded associations allows using MongoDB tools like the aggregation pipeline to query these documents in a powerful way.

Because embedded documents are stored as part of their parent top-level documents, it is not possible to persist an embedded document by itself, nor is it possible to retrieve embedded documents directly. However, embedded documents can still be efficiently queried and retrieved with the help of MongoDB projection operation:

class Band
  include Mongoid::Document
  field :started_on, type: Date
  embeds_one :label
end

class Label
  include Mongoid::Document
  field :name, type: String
  embedded_in :band
end

# Retrieve labels for bands started in the last year.
#
# Sends a find query like this:
# {"find"=>"bands",
#  "filter"=>{"started_on"=>{"$gt"=>2018-07-01 00:00:00 UTC}},
#  "projection"=>{"_id"=>1, "label"=>1}}
Band.where(started_on: {'$gt' => Time.now - 1.year}).only(:label).map(&:label).compact.uniq

Setting Stale Values on Referenced Associations

Setting a stale value to a referenced association can sometimes result in a nil value being persisted to the database. Take the following case:

class Post
  include Mongoid::Document

  has_one :comment, inverse_of: :post
end

class Comment
  include Mongoid::Document

  belongs_to :post, inverse_of: :comment, optional: true
end

post.comment = comment1
post.reload

At this point, post.comment is set to comment1, however since a reload happened, post.comment does not refer to the same object as comment1. Meaning, updating one object does not implicitly update the other. This matters for the next operation:

post.comment = comment2
post.reload

Now, post.comment is set to comment2, and the post_id of the old comment is set to nil. However, the value that was assigned to post.comment did not refer to the same object as comment1, therefore, while the old value of post.comment was updated to have a nil post_id, comment1 still has the post_id set.

post.comment = comment1
post.reload

Finally, this last assignment attempts to set the post_id on comment1, which should be nil at this point, but is set to the old post_id. During this operation, the post_id is cleared from comment2, and the new post_id is set on comment1. However, since the post_id was already set on comment1, nothing is persisted, and we end up with both comments having a nil post_id. At this point, running post.comment returns nil.

Querying Embedded Associations

When querying top-level documents, conditions can be specified on documents in embedded associations using the dot notation. For example, given the following models:

class Band
  include Mongoid::Document
  embeds_many :tours
  embeds_many :awards
  field :name, type: String
end

class Tour
  include Mongoid::Document
  embedded_in :band
  field :year, type: Integer
end

class Award
  include Mongoid::Document
  embedded_in :band
  field :name, type: String
end

To retrieve bands based on tour attributes, use the dot notation as follows:

# Get all bands that have toured since 2000
Band.where('tours.year' => {'$gte' => 2000})

To retrieve only documents of embedded associations, without retrieving top-level documents, use the pluck projection method:

# Get awards for bands that have toured since 2000
Band.where('tours.year' => {'$gte' => 2000}).pluck(:awards)

Querying Loaded Associations

Mongoid query methods can be used on embedded associations of documents which are already loaded in the application. This mechanism is sometimes called “embedded matching” or “embedded document matching” and it is implemented entirely in Mongoid - the queries are NOT sent to the server.

Embedded matching is supported for most general-purpose query operators. It is not implemented for text search, geospatial query operators, operators that execute JavaScript code ($where) and operators that are implemented via other server functionality such as $expr and $jsonSchema.

The following operators are supported:

For example, using the model definitions just given, we could query tours on a loaded band:

band = Band.where(name: 'Astral Projection').first
tours = band.tours.where(year: {'$gte' => 2000})

Embedded Matching Vs Server Behavior

Mongoid aims to provide the same semantics when performing embedded matching as those of MongoDB server. This means, for example, when the server only accepts arguments of particular types for a particular operator, Mongoid would also only accept arguments of the corresponding types.

The following deviations are known:

  • Mongoid embedded matchers, because they are implemented on the client side, behave the same regardless of the server version that backs the application. As such, it is possible for Mongoid to deviate from server behavior if the server itself behaves differently in different versions. All operators are implemented in Mongoid regardless of backing deployment’s server version.

    As of this writing, the known cases of such deviation are:

    • 3.2 and earlier servers not validating $size arguments as strictly as newer versions do.
    • 4.0 and earlier servers not validating $type arguments as strictly as newer versions do (allowing invalid arguments like 0, for example).
    • 3.2 and earlier servers not supporting Decimal128 for $type, as well as allowing invalid arguments such as negative numbers (smaller than -1) and numbers that are greater than 19 (not including 127, the argument for the MaxKey type).
    • 3.4 and earlier servers not supporting arrays for $type.
    • 3.0 and earlier servers not supporting bitwise operators.
  • Mongoid DSL expands Range arguments to hashes with $gte and $lte conditions. In some cases this creates bogus queries. Embedded matchers raise the InvalidQuery exception in these cases. The operators that are known to be affected are $elemMatch, $eq, $gt, $gte, $lt, $lte and $ne.

  • When performing embedded matching with $regex, it is not currently possible to specify a regular expression object as the pattern and also provide options.

In general, Mongoid adopts the behavior of current server versions and validates more strictly.

Omitting _id Fields

By default, Mongoid adds an _id field to each embedded document. This permits easy referencing of and operations on the embedded documents.

These _id fields may be omitted to save storage space. To do so, override the _id field definition in the child documents and remove the default value:

class Order
  include Mongoid::Document

  embeds_many :line_items
end

class LineItem
  include Mongoid::Document

  embedded_in :order

  field :_id, type: Object
end

In the current version of Mongoid the field definition is required, but without a default value specified no value will be stored in the database. A future version of Mongoid may allow removing previously defined fields.

Note

Removing the _id field means that embedded documents must be identified by their content attribute values during queries, updates and deletes.

Deleting

Mongoid provides three methods for deleting children from embeds_many associations: clear, destroy_all and delete_all.

clear

The clear method uses the $unset operator to remove the entire association from the host document. It does not run destroy callbacks on the documents being removed, acting like delete_all in this regard:

band = Band.find(...)
band.tours.clear

If clear is called on an association in an unsaved host document, it will still try to remove the association from the database based on the host document’s _id:

band = Band.find(...)
band.tours << Tour.new(...)

unsaved_band = Band.new(id: band.id, tours: [Tour.new])
# Removes all tours from the persisted band due to _id match.
unsaved_band.tours.clear

band.tours
# => []

delete_all

The delete_all method removes the documents that are in the association using the $pullAll operator. Unlike clear, delete_all:

  • Loads the association, if it wasn’t yet loaded;
  • Only removes the documents that exist in the application.

delete_all does not run destroy callbacks on the documents being removed.

Example:

band = Band.find(...)
band.tours.delete_all

destroy_all

The delete_all method removes the documents that are in the association using the $pullAll operator while running the destroy callbacks. Like delete_all, destroy_all loads the entire association if it wasn’t yet loaded and it only removes documents that exist in the application:

band = Band.find(...)
band.tours.destroy_all

Hash Assignment

Embedded associations allow the user to assign a Hash instead of a document to an association. On assignment, this hash is coerced into a document of the class of the association that it’s being assigned to. Take the following example:

class Band
  include Mongoid::Document
  embeds_many :albums
end

class Album
  include Mongoid::Document
  field :name, type: String
  embedded_in :band
end

band = Band.create!
band.albums = [ { name: "Narrow Stairs" }, { name: "Transatlanticism" } ]
p band.albums
# => [ #<Album _id: 633c71e93282a4357bb608e5, name: "Narrow Stairs">, #<Album _id: 633c71e93282a4357bb608e6, name: "Transatlanticism"> ]

This works for embeds_one, embeds_many, and embedded_in associations. Note that you cannot assign hashes to referenced associations.

Common Behavior

Extensions

All associations can have extensions, which provides a way to add application specific functionality to the association. They are defined by providing a block to the association definition.

class Person
  include Mongoid::Document
  embeds_many :addresses do
    def find_by_country(country)
      where(country: country).first
    end
    def chinese
      _target.select { |address| address.country == "China" }
    end
  end
end

person.addresses.find_by_country("Mongolia") # returns address
person.addresses.chinese # returns [ address ]

Custom Association Names

You can name your associations whatever you like, but if the class cannot be inferred by Mongoid from the name, and neither can the opposite side you’ll want to provide the macro with some additional options to tell Mongoid how to hook them up.

class Car
  include Mongoid::Document
  embeds_one :engine, class_name: "Motor", inverse_of: :machine
end

class Motor
  include Mongoid::Document
  embedded_in :machine, class_name: "Car", inverse_of: :engine
end

Custom Primary & Foreign Keys

The fields used when looking up associations can be explicitly specified. The default is to use id on the “parent” association and #{association_name}_id on the “child” association, for example with a has_many/belongs_to:

class Company
  include Mongoid::Document
  has_many :emails
end

class Email
  include Mongoid::Document
  belongs_to :company
end

company = Company.find(id)
# looks up emails where emails.company_id == company.id
company.emails

Specify a different primary_key to change the field name on the “parent” association and foreign_key to change the field name on the “child” association:

class Company
  include Mongoid::Document
  field :c, type: String
  has_many :emails, foreign_key: 'c_ref', primary_key: 'c'
end

class Email
  include Mongoid::Document
  # This definition of c_ref is automatically generated by Mongoid:
  # field :c_ref, type: Object
  # But the type can also be specified:
  field :c_ref, type: String
  belongs_to :company, foreign_key: 'c_ref', primary_key: 'c'
end

company = Company.find(id)
# looks up emails where emails.c_ref == company.c
company.emails

With a has_and_belongs_to_many association, since the data is stored on both sides of the association, there are 4 fields configurable when the association is defined:

  • :primary_key is the field on the remote model that contains the value by which the remote model is looked up.
  • :foreign_key is the field on the local model which stores the :primary_key values.
  • :inverse_primary_key is the field on the local model that the remote model uses to look up the local model documents.
  • :inverse_foreign_key is the field on the remote model storing the values in :inverse_primary_key.

An example might make this more clear:

class Company
  include Mongoid::Document

  field :c_id, type: Integer
  field :e_ids, type: Array

  has_and_belongs_to_many :employees,
    primary_key: :e_id, foreign_key: :e_ids,
    inverse_primary_key: :c_id, inverse_foreign_key: :c_ids
end

class Employee
  include Mongoid::Document

  field :e_id, type: Integer
  field :c_ids, type: Array

  has_and_belongs_to_many :companies,
    primary_key: :c_id, foreign_key: :c_ids,
    inverse_primary_key: :e_id, inverse_foreign_key: :e_ids
end

company = Company.create!(c_id: 123)
# => #<Company _id: 5c565ece026d7c461d8a9d4e, c_id: 123, e_ids: nil>

employee = Employee.create!(e_id: 456)
# => #<Employee _id: 5c565ee8026d7c461d8a9d4f, e_id: 456, c_ids: nil>

company.employees << employee

company
# => #<Company _id: 5c565ece026d7c461d8a9d4e, c_id: 123, e_ids: [456]>

employee
# => #<Employee _id: 5c5883ce026d7c4b9e244c0c, e_id: 456, c_ids: [123]>

Note that just like with the default #{association_name}_id field, Mongoid automatically adds a field for the custom foreign key c_ref to the model. However, since Mongoid doesn’t know what type of data should be allowed in the field, the field is created with a type of Object. It is a good idea to explicitly define the field with the appropriate type.

Custom Scopes

You may set a specific scope on an association using the :scope parameter. The scope is an additional filter that restricts which objects are considered to be a part of the association - a scoped association will return only documents which satisfy the scope condition.. The scope may be either:

  • a Proc with arity zero, or
  • a Symbol which references a named scope on the associated model.
class Trainer
  has_many :pets, scope: -> { where(species: 'dog') }
  has_many :toys, scope: :rubber
end

class Pet
  belongs_to :trainer
end

class Toy
  scope :rubber, where(material: 'rubber')
  belongs_to :trainer
end

Note

It is possible to add documents that do not satisfy an association’s scope to that association. In this case, such documents will appear associated in memory, and will be saved to the database, but will not be present when the association is queried in the future. For example:

trainer = Trainer.create!
dog = Pet.create!(trainer: trainer, species: 'dog')
cat = Pet.create!(trainer: trainer, species: 'cat')

trainer.pets #=> [dog, cat]

trainer.reload.pets #=> [dog]

Note

Mongoid’s syntax for scoped association differs from that of ActiveRecord. Mongoid uses the :scope keyword argument for consistency with other association options, whereas in ActiveRecord the scope is a positional argument.

Validations

It is important to note that by default, Mongoid will validate the children of any association that are loaded into memory via a validates_associated. The associations that this applies to are:

  • embeds_many
  • embeds_one
  • has_many
  • has_one
  • has_and_belongs_to_many

If you do not want this behavior, you may turn it off when defining the association.

class Person
  include Mongoid::Document

  embeds_many :addresses, validate: false
  has_many :posts, validate: false
end

Polymorphism

One to one and one to many associations support polymorphism, which is having a single association potentially contain objects of different classes. For example, we could model an organization in which departments and teams have managers as follows:

class Department
  include Mongoid::Document

  has_one :manager, as: :unit
end

class Team
  include Mongoid::Document

  has_one :manager, as: :unit
end

class Manager
  include Mongoid::Document

  belongs_to :unit, polymorphic: true
end

dept = Department.create!
team = Team.create!

alice = Manager.create!(unit: dept)
alice.unit == dept
# => true
dept.manager == alice
# => true

To provide another example, suppose we want to track price history for products and bundles. This can be achieved via an embedded one to many polymorphic association:

class Product
  include Mongoid::Document

  field :name, type: String
  has_and_belongs_to_many :bundles

  embeds_many :prices, as: :item
end

class Bundle
  include Mongoid::Document

  field :name, type: String
  has_and_belongs_to_many :products

  embeds_many :prices, as: :item
end

class Price
  include Mongoid::Document

  embedded_in :item, polymorphic: true
end

pants = Product.create!(name: 'Pants',
  prices: [Price.new, Price.new])
costume = Bundle.create!(name: 'Costume', products: [pants],
  prices: [Price.new, Price.new])

To define a polymorphic association, specify the polymorphic: true option on the child association and add the as: :association_name option to the parent association.

Note that Mongoid currently supports polymorphism only in one direction - from the child to the parent. For example, polymorphism cannot be used to specify that a bundle may contain other bundles or products:

class Bundle
  include Mongoid::Document

  # Does not work:
  has_many :items, polymorphic: true
end

has_and_belongs_to_many associations do not support polymorphism.

Cascading Callbacks

If you want the embedded document callbacks to fire when calling a persistence operation on its parent, you will need to provide the cascade callbacks option to the association.

 class Band
   include Mongoid::Document
   embeds_many :albums, cascade_callbacks: true
   embeds_one :label, cascade_callbacks: true
 end

band.save # Fires all save callbacks on the band, albums, and label.

Dependent Behavior

You can provide dependent options to referenced associations to instruct Mongoid how to handle situations where one side of the association is deleted, or is attempted to be deleted. The options are as follows:

  • :delete_all: Delete the child document(s) without running any of the model callbacks.
  • :destroy: Destroy the child document(s) and run all of the model callbacks.
  • :nullify: Set the foreign key field of the child document to nil. The child may become orphaned if it is ordinarily only referenced via the parent.
  • :restrict_with_exception: raise an error if the child is not empty.
  • :restrict_with_error: Cancel operation and return false if the child is not empty.

If no :dependent option is provided, deleting the parent document leaves the child document unmodified (in other words, the child document continues to reference the now deleted parent document via the foreign key field). The child may become orphaned if it is ordinarily only referenced via the parent.

class Band
  include Mongoid::Document
  has_many :albums, dependent: :delete_all
  belongs_to :label, dependent: :nullify
end

class Album
  include Mongoid::Document
  belongs_to :band
end

class Label
  include Mongoid::Document
  has_many :bands, dependent: :restrict_with_exception
end

label = Label.first
label.bands.push(Band.first)
label.delete # Raises an error since bands is not empty.

Band.first.delete # Will delete all associated albums.

Autosaving

One core difference between Mongoid and ActiveRecord is that Mongoid does not automatically save associated documents for referenced (i.e., non-embedded) associations when the parent is saved, for performance reasons.

If autosaving is not used, it is possible to create dangling references to non-existent documents via associations:

class Band
  include Mongoid::Document

  has_many :albums
end

class Album
  include Mongoid::Document

  belongs_to :band
end

band = Band.new
album = Album.create!(band: band)

# The band is not persisted at this point.

album.reload

album.band_id
# => BSON::ObjectId('6257699753aefe153121a3d5')

# Band does not exist.
album.band
# => nil

To make referenced associations save automatically when the parent is saved, add the :autosave option to the association:

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.

album.reload

album.band_id
# => BSON::ObjectId('62576b4b53aefe178b65b8e3')

album.band
# => #<Band _id: 62576b4b53aefe178b65b8e3, >

The autosaving functionality is automatically added to an association when using accepts_nested_attributes_for, so that the application does not need to track which associations were modified when processing a form submission.

Embedded associations always autosave, because they are stored as part of the parent document.

Some operations on associations always save the parent and the child documents as part of the operation, regardless of whether autosaving is enabled. A non-exhaustive list of these operations is as follows:

  • Assignment to the association:

    # Saves the band and the album.
    band.albums = [Album.new]
    
  • push, <<:

    band.albums << Album.new
    band.albums.push(Album.new)
    

Existence Predicates

All associations have existence predicates on them in the form of name? and has_name? to check if the association is blank.

class Band
  include Mongoid::Document
  embeds_one :label
  embeds_many :albums
end

band.label?
band.has_label?
band.albums?
band.has_albums?

Autobuilding

One to one associations (embeds_one, has_one) have an autobuild option which tells Mongoid to instantiate a new document when the association is accessed and it is nil.

class Band
  include Mongoid::Document
  embeds_one :label, autobuild: true
  has_one :producer, autobuild: true
end

band = Band.new
band.label # Returns a new empty label.
band.producer # Returns a new empty producer.

Touching

Any belongs_to association can take an optional :touch option which will cause the parent document to be touched whenever the child document is updated:

class Band
  include Mongoid::Document
  field :name
  belongs_to :label, touch: true
end

band = Band.first
band.name = "The Rolling Stones"
band.save! # Calls touch on the parent label.
band.touch # Calls touch on the parent label.

:touch can also take a string or symbol argument specifying a field to be touched on the parent association in addition to updated_at:

class Label
  include Mongoid::Document
  include Mongoid::Timestamps
  field :bands_updated_at, type: Time
  has_many :bands
end

class Band
  include Mongoid::Document
  belongs_to :label, touch: :bands_updated_at
end

label = Label.create!
band = Band.create!(label: label)

band.touch # Updates updated_at and bands_updated_at on the label.

When an embedded document is touched, its parents are recursively touched through the composition root (because all of the parents are necessarily saved when the embedded document is saved). The :touch attribute therefore is unnecessary on embedded_in associations.

Mongoid currently does not support specifying an additional field to be touched on an embedded_in association.

:touch should not be set to false on an embedded_in association, since composition hierarchy is always updated upon a touch of an embedded document. This is currently not enforced but enforcement is intended in the future.

The counter_cache Option

As with ActiveRecord, the :counter_cache option can be used on an association to make finding the number of belonging objects more efficient. Also similar to ActiveRecord, you must take into account that there will be an extra attribute on the associated model. This means that with Mongoid, you need to include Mongoid::Attributes::Dynamic on the associated model. For example:

class Order
  include Mongoid::Document
  belongs_to :customer, counter_cache: true
end

class Customer
  include Mongoid::Document
  include Mongoid::Attributes::Dynamic
  has_many :orders
end

Association Proxies

Associations employ transparent proxies to the target objects. This can cause surprising behavior in some situations.

The method visibility may be lost when methods on association targets are accessed, depending on the association:

class Order
  include Mongoid::Document
  belongs_to :customer

  private

  def internal_status
    'new'
  end
end

class Customer
  include Mongoid::Document
  has_many :orders

  private

  def internal_id
    42
  end
end

order = Order.new
customer = Customer.create!(orders: [order])

# has_many does not permit calling private methods on the target
customer.orders.first.internal_status
# NoMethodError (private method `internal_status' called for #<Order:0x000055af2ec46c50>)

# belongs_to permits calling private methods on the target
order.customer.internal_id
# => 42

Association Metadata

All associations in Mongoid contain metadata that holds information about the association in question, and is a valuable tool for third party developers to use to extend Mongoid.

You can access the association metadata of the association in a few different ways.

# Get the metadata for a named association from the class or document.
Model.reflect_on_association(:association_name)
model.reflect_on_association(:association_name)

# Get the metadata with a specific association itself on a specific
# document.
model.associations[:association_name]

Attributes

All associations contain a _target, which is the proxied document or documents, a _base which is the document the association hangs off, and _association which provides information about the association.

class Person
  include Mongoid::Document
  embeds_many :addresses
end

person.addresses = [ address ]
person.addresses._target # returns [ address ]
person.addresses._base # returns person
person.addresses._association # returns the association metadata

The Association Object

The association object itself contains more information than one might know what to do with, and is useful for developers of extensions to Mongoid.

Method Description
Association#as Returns the name of the parent to a polymorphic child.
Association#as? Returns whether or not an as option exists.
Association#autobuilding? Returns whether or not the association is autobuilding.
Association#autosaving? Returns whether or not 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.
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 dirty check method.
Association#foreign_key_setter Returns the name of the foreign key field setter.
Association#indexed? Returns whether the foreign key is auto indexed.
Association#inverses Returns the names of all inverse association.
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 for the polymorphic type field of the inverse.
Association#inverse_type_setter Returns the name for the polymorphic type field setter of the inverse.
Association#key Returns the name of the field in the attributes hash to use 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 to store an embedded association in.
Association#touchable? Returns whether or not 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.
←   Inheritance Validation  →