Navigation

Persistence

Mongoid supports all expected CRUD operations for those familiar with other Ruby mappers like Active Record or Data Mapper. What distinguishes Mongoid from other mappers for MongoDB is that the general persistence operations perform atomic updates on only the fields that have changed instead of writing the entire document to the database each time.

The persistence sections will provide examples on what database operation is performed when executing the documented command.

Standard

Mongoid’s standard persistence methods come in the form of common methods you would find in other mapping frameworks. The following table shows all standard operations with examples.

Operation Example

Model.create!

Insert a document or multiple documents into the database, raising an error if a validation or server error occurs.

Pass a hash of attributes to create one document with the specified attributes, or an array of hashes to create multiple documents. If a single hash is passed, the corresponding document is returned. If an array of hashes is passed, an array of documents corresponding to the hashes is returned.

If a block is given to create! , it will be invoked with each document as the argument in turn prior to attempting to save that document.

If there is a problem saving any of the documents, such as a validation error or a server error, an exception is raised and, consequently, none of the documents are returned. However, if an array of hashes was passed and previous documents were successfully saved, those documents will remain in the database.

Person.create!(
  first_name: "Heinrich",
  last_name: "Heine"
) # => Person instance

Person.create!([
  { first_name: "Heinrich", last_name: "Heine" },
  { first_name: "Willy", last_name: "Brandt" }
]) # => Array of two Person instances

Person.create!(first_name: "Heinrich") do |doc|
  doc.last_name = "Heine"
end # => Person instance

Model.create

Instantiate a document or multiple documents and, if validations pass, insert them into the database.

create is similar to create! but does not raise exceptions on validation errors. It still raises errors on server errors, such as trying to insert a document with an _id that already exists in the collection.

If any validation errors are encountered, the respective document is not inserted but is returned along with documents that were inserted. Use persisted? , new_record? or errors methods to check which of the returned documents were inserted into the database.

Person.create(
  first_name: "Heinrich",
  last_name: "Heine"
) # => Person instance

Person.create([
  { first_name: "Heinrich", last_name: "Heine" },
  { first_name: "Willy", last_name: "Brandt" }
]) # => Array of two Person instances

Person.create(first_name: "Heinrich") do |doc|
  doc.last_name = "Heine"
end # => Person instance

class Post
  include Mongoid::Document

  validates_uniqueness_of :title
end

posts = Post.create([{title: "test"}, {title: "test"}])
# => array of two Post instances
posts.map { |post| post.persisted? } # => [true, false]

Model#save!

Save the changed attributes to the database atomically, or insert the document if new. Raises an exception if validations fail or there is a server error.

Returns true if the changed attributes were saved, raises an exception otherwise.

person = Person.new(
  first_name: "Heinrich",
  last_name: "Heine"
)
person.save!

person.first_name = "Christian Johan"
person.save!

Model#save

Save the changed attributes to the database atomically, or insert the document if new.

Returns true if the changed attributes were saved. Returns false if there were any validation errors. Raises an exception if the document passed validation but there was a server error during the save.

Pass validate: false option to bypass validations.

person = Person.new(
  first_name: "Heinrich",
  last_name: "Heine"
)
person.save
person.save(validate: false)

person.first_name = "Christian Johan"
person.save

Model#update_attributes

Update the document attributes in the database. Will return true if validation passed, false if not.

person.update_attributes(
  first_name: "Jean",
  last_name: "Zorg"
)

Model#update_attributes!

Update the document attributes in the database and raise an error if validation failed.

person.update_attributes!(
  first_name: "Leo",
  last_name: "Tolstoy"
)

Model#update_attribute

Update a single attribute, bypassing validations.

person.update_attribute(:first_name, "Jean")

Model#upsert

Performs a MongoDB upsert on the document. If the document exists in the database, it will get overwritten with the current attributes of the document in memory. If the document does not exist in the database, it will be inserted. Note that this only runs the {before|after|around}_upsert callbacks.

person = Person.new(
  first_name: "Heinrich",
  last_name: "Heine"
)
person.upsert

Model#touch

Update the document’s updated_at timestamp, optionally with one extra provided time field. This will cascade the touch to all belongs_to associations of the document with the option set. This operation skips validations and callbacks.

Attempting to touch a destroyed document will raise FrozenError * (as of Ruby 2.5,* RuntimeError on previous Ruby versions), same as if attempting to update an attribute on a destroyed document.

person.touch
person.touch(:audited_at)

Model#delete

Deletes the document from the database without running callbacks.

person.delete

Model#destroy

Deletes the document from the database while running destroy callbacks.

person.destroy

Model.delete_all

Deletes all documents from the database without running any callbacks.

Person.delete_all

Model.destroy_all

Deletes all documents from the database while running callbacks. This is a potentially expensive operation since all documents will be loaded into memory.

Person.destroy_all

Mongoid provides the following persistence-related attributes:

Attribute Example

Model#new_record?

Returns true if the model instance has not yet been saved to the database. Opposite of persisted?

person = Person.new(
  first_name: "Heinrich",
  last_name: "Heine"
)
person.new_record? # => true
person.save!
person.new_record? # => false

Model#persisted?

Returns true if the model instance has been saved to the database. Opposite of new_record?

person = Person.new(
  first_name: "Heinrich",
  last_name: "Heine"
)
person.persisted? # => false
person.save!
person.persisted? # => true

Atomic

Although Mongoid performs atomic operations under the covers by default, there may be cases where you want to do this explicitly without persisting other fields. Mongoid provides support for all of these operations as well. When executing atomic operations via these methods, callbacks and validations are not invoked.

Operation Example

Model#add_to_set

Performs an atomic $addToSet on the field.

person.add_to_set(aliases: "Bond")

Model#bit

Performs an atomic $bit on the field.

person.bit(age: { and: 10, or: 12 })

Model#inc

Performs an atomic $inc on the field.

person.inc(age: 1)

Model#pop

Performs an atomic $pop on the field.

person.pop(aliases: 1)

Model#pull

Performs an atomic $pull on the field.

person.pull(aliases: "Bond")

Model#pull_all

Performs an atomic $pullAll on the field.

person.pull_all(aliases: [ "Bond", "James" ])

Model#push

Performs an atomic $push on the field.

person.push(aliases: ["007","008"])

Model#rename

Performs an atomic $rename on the field.

person.rename(bday: :dob)

Model#set

Updates an attribute on the model instance and, if the instance is already persisted, performs an atomic $set on the field, bypassing validations.

set can also deeply set values on Hash fields.

set can also deeply set values on embeds_one associations. If such an association’s document is nil, one will be created prior to the update.

set should not be used with has_one associations, as it does not correctly work in such cases.

person = Person.create!(name: "Ricky Bobby")
person.set(name: "Tyler Durden") # updates name in the database


person = Person.new
person.set(name: "Tyler Durden") # does not write to database
person.name # => "Tyler Durden"
person.persisted? # => true


class Post
  include Mongoid::Document

  field :metadata, type: Hash
end

post = Post.create!
post.set('metadata.published_at' => Time.now)
post.metadata['published_at'] # => Time instance

post.set('metadata.approved.today' => true)
post.metadata['approved'] # => {'today' => true}


class Flight
  include Mongoid::Document

  embeds_one :plan
end

class Plan
  include Mongoid::Document

  embedded_in :flight

  field :route, type: String
end

flight = Flight.create!
flight.plan # => nil
flight.set('plan.route', 'test route')
flight.plan # => Plan instance
flight.plan.route # => "test route"

Model#unset

Performs an atomic $unset on the field.

person.unset(:name)

Atomic Operation Grouping

Atomic operations may be grouped together using the #atomically method on a document. All operations inside the block given to #atomically are sent to the cluster in a single atomic command. For example:

person.atomically do
  person.inc(age: 1)
  person.set(name: 'Jake')
end

#atomically blocks may be nested. The default behavior is to write changes performed by each block as soon as the block ends:

person.atomically do
  person.atomically do
    person.inc(age: 1)
    person.set(name: 'Jake')
  end
  raise 'An exception'
  # name and age changes are still persisted
end

This behavior can be changed by specifying the join_context: true option to #atomically, or globally by setting the join_contexts configuration option to true. When context joining is enabled, nested #atomically blocks are joined with the outer blocks, and only the outermost block (or the first block where join_contexts is false) actually writes changes to the cluster. For example:

person.atomically do
  person.atomically(join_context: true) do
    person.inc(age: 1)
    person.set(name: 'Jake')
  end
  raise 'An exception'
  # name and age changes are not persisted
end

The context joining behavior can be enabled globally by default by setting join_context option in Mongoid configuration. In this case specifying join_context: false on an #atomically block can be used to obtain the independent persistence context behavior.

If an exception is raised in an #atomically block which has not yet persisted its changes to the cluster, any pending attribute changes on Mongoid models are reverted. For example:

person = Person.new(name: 'Tom')
begin
  person.atomically do
    person.inc(age: 1)
    person.set(name: 'Jake')
    person.name # => 'Jake'
    raise 'An exception'
  end
rescue Exception
  person.name # => 'Tom'
end

Atomic operations described in this section apply to one document at a time, therefore nesting #atomically blocks invoked on multiple documents does not make changes to the different documents be persisted atomically together. However, MongoDB offers multi-document transactions as of server version 4.0 which provide atomic persistence across multiple documents.

Persistence Context Attributes

Mongoid provides client_name, database_name and collection_name methods on model classes to determine the client, database and collection names used for persistence:

Band.client_name
# => :default

Band.database_name
# => "mongoid"

Band.collection_name
# => :bands

Custom

There may be cases where you want to persist documents to different sources from their defaults, or with different options from the default. Mongoid provides run-time support for this as well as support on a per-model basis.

Model-Level Persistence Options

On a per-model basis, you can tell it to store in a custom collection name, a different database, or a different client. The following example would store the Band class by default into a collection named “artists” in the database named “music”, with the client “analytics”.

Note that the value supplied to the client option must be configured under clients in your mongoid.yml.

class Band
  include Mongoid::Document
  store_in collection: "artists", database: "music", client: "analytics"
end

If no store_in macro would have been provided, Mongoid would store the model in a collection named “bands” in the default database in the default client.

Runtime Persistence Options

It is possible to change the client, database and collection, as well as any of the MongoDB client options, used for persistence for a group of operations by using the with method on a model class or instance:

Band.with(database: "music-non-stop") do |klass|
  klass.create(...)

  band = Band.first

  Band.create(...)
end

Band.with(collection: "artists") do |klass|
  klass.delete_all

  Band.delete_all
end

band.with(client: :tertiary) do |band_object|
  band_object.save!

  band.save!
end

The with method creates a temporary persistence context and a MongoDB client to be used for operations in the context. For the duration of the block, the persistence context on the model class or instance that with was called on is changed to the temporary persistence context. For convenience, the model class or instance that with was called on is yielded to the block.

The temporary persistence context applies to both queries and writes.

Care should be taken when performing persistence operations across different persistence contexts. For example, if a document is saved in a temporary persistence context, it may not exist in the default persistence context, failing subsequent updates:

band = Band.new(name: "Scuba")
band.with(collection: "artists") do |band_object|
  band_object.save!
end

# This will not save - updates the collection "bands" which does not have
# the Scuba band
band.update_attribute(likes: 1000)

# This will update the document.
band.with(collection: "artists") do |band_object|
  band_object.update_attribute(likes: 1000)
end

As of Mongoid 6.0, the with method must always be called with a block, and the temporary persistence context exists only for the duration of the block. This is because a new client is created under the covers with the options passed to with. To ensure that this client is closed and its associated resources are freed, the scope when this client could be used must be well-defined.

Global Override

If you want to switch the persistence context for all operations at runtime, but don’t want to be using with all over your code, Mongoid provides the ability to do this as the client and database level globally. The methods for this are Mongoid.override_client and Mongoid.override_database. A useful case for this are internationalized applications that store information for different locales in different databases or clients, but the schema in each remains the same.

class BandsController < ApplicationController
  before_filter :switch_database
  after_filter :reset_database

  private

  def switch_database
    I18n.locale = params[:locale] || I18n.default_locale
    Mongoid.override_database("my_db_name_#{I18n.locale}")
  end

  def reset_database
    Mongoid.override_database(nil)
  end
end

In the above example, all persistence operations would be stored in the alternative database for all remaining operations on this thread. This is why the after request set the override back to nil - it ensures subsequent requests with no local params use the default option.

Persistence context applies to both read and write operations. For example, secondary reads can be performed as follows:

Band.with(read: {mode: :secondary}) do
  Band.count
end

Client and Collection Access

If you want to drop down to the driver level to perform operations, you can grab the Mongo client or collection from the model or document instance:

Band.mongo_client
band.mongo_client
Band.collection
band.collection

From here you also have the same runtime persistence options using the client’s #with:

client = Band.mongo_client.with(write: { w: 0 }, database: "musik")
client[:artists].find(...)

You can also override the :read or :write options on the collection using the collections #with:

collection_w_0 = Band.collection.with(write: { w: 0 })
collection_w_0[:artists].find(...)

Capped Collections

Mongoid does not provide a mechanism for creating capped collections on the fly - you will need to create these yourself one time up front either with the driver or via the Mongo console.

To create a capped collection with the driver:

client["name", :capped => true, :size => 1024].create

To create a capped collection from the Mongo console:

db.createCollection("name", { capped: true, size: 1024 });

Set a Default Collation on a Collection

Mongoid does not provide a mechanism for creating a collection with a default collation. Like capped collections, you will need to create the collection yourself one time, up-front, either with the driver or via the Mongo console.

To create a collection with a default collation with the driver:

client["name", :collation => { :locale => 'fr'}].create

To create a collection with a default collation from the Mongo console:

db.createCollection("name", { collation: { locale: 'fr' } });
←   Documents Queries  →