CRUD Operations
On this page
- Saving Documents
- Standard
- Atomic
- Reloading
- Reloading Unsaved Documents
- Accessing Field Values
- Getters & Setters
- Custom Getters & Setters
read_attribute
&write_attribute
- Hash Access
- Bulk Attribute Writes
- Dirty Tracking
- Viewing Changes
- Resetting Changes
- Persistence
- Viewing Previous Changes
- Updating Container Fields
- Readonly Documents
- Overriding
readonly?
Saving Documents
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 | |||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Returns the document's attributes as a ``Hash`` with string keys, and its values in Mongoized form (i.e. the way they are stored in the db). The attributes hash also contains the attributes of all embedded documents, as well as their embedded documents, etc. If an embedded association is empty, its key will not show up in the returned hash. |
| |||||||||||||||||||||||
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 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. |
| |||||||||||||||||||||||
Instantiate a document or multiple documents and, if validations pass, insert them into the database.
If any validation errors are encountered, the respective document
is not inserted but is returned along with documents that were inserted.
Use |
| |||||||||||||||||||||||
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. |
| |||||||||||||||||||||||
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 Pass |
| |||||||||||||||||||||||
Update the document attributes in the database. Will return true if validation passed, false if not. |
| |||||||||||||||||||||||
Update the document attributes in the database and raise an error if validation failed. |
| |||||||||||||||||||||||
Update a single attribute, bypassing validations. |
| |||||||||||||||||||||||
Performs a MongoDB replace with upsert on the document. If the document
exists in the database and the |
| |||||||||||||||||||||||
Update the document's updated_at timestamp, optionally with one extra
provided time field. This will cascade the touch to all
Attempting to touch a destroyed document will raise |
| |||||||||||||||||||||||
Deletes the document from the database without running callbacks. If the document is not persisted, Mongoid will attempt to delete from
the database any document with the same |
| |||||||||||||||||||||||
Deletes the document from the database while running destroy callbacks. If the document is not persisted, Mongoid will attempt to delete from
the database any document with the same |
| |||||||||||||||||||||||
Deletes all documents from the database without running any callbacks. |
| |||||||||||||||||||||||
Deletes all documents from the database while running callbacks. This is a potentially expensive operation since all documents will be loaded into memory. |
|
Mongoid provides the following persistence-related attributes:
Attribute | Example | |||||||
---|---|---|---|---|---|---|---|---|
Returns |
| |||||||
Returns |
|
Atomic
Mongoid exposes MongoDB update operators as methods on Mongoid documents. When these methods are used, callbacks are not invoked and validations are not performed. The supported update operators are:
Operation | Example | |||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Performs an atomic $addToSet on the field. |
| |||||||||||||||||||||||||||||||||||||||||||
Performs an atomic $bit on the field. |
| |||||||||||||||||||||||||||||||||||||||||||
Performs an atomic $inc on the field. |
| |||||||||||||||||||||||||||||||||||||||||||
Performs an atomic $pop on the field. |
| |||||||||||||||||||||||||||||||||||||||||||
Performs an atomic $pull on the field. |
| |||||||||||||||||||||||||||||||||||||||||||
Performs an atomic $pullAll on the field. |
| |||||||||||||||||||||||||||||||||||||||||||
Performs an atomic $push on the field. |
| |||||||||||||||||||||||||||||||||||||||||||
Performs an atomic $rename on the field. |
| |||||||||||||||||||||||||||||||||||||||||||
Updates an attribute on the model instance and, if the instance is already persisted, performs an atomic $set on the field, bypassing validations.
|
| |||||||||||||||||||||||||||||||||||||||||||
Performs an atomic $unset on the field. |
|
Note that, because these methods skip validations, it is possible to both
save invalid documents into the database and end up with invalid documents
in the application (which would subsequently fail to save via a save
call due to the failing validations).
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.
Reloading
Use the reload
method to fetch the most recent version of a document from
the database. Any unsaved modifications to the document's attributes are lost:
band = Band.create!(name: 'foo') # => #<Band _id: 6206d06de1b8324561f179c9, name: "foo", description: nil, likes: nil> band.name = 'bar' band # => #<Band _id: 6206d06de1b8324561f179c9, name: "bar", description: nil, likes: nil> band.reload # => #<Band _id: 6206d06de1b8324561f179c9, name: "foo", description: nil, likes: nil>
When a document is reloaded, all of its embedded associations are also reloaded in the same query (since embedded documents are stored in the parent document on the server). If a document has referenced associations, the loaded associations' are not reloaded but their values are cleared, such that these associations would be loaded from the database at the next access.
Note
Some operations on associations, for example assignment, persists the new document. In these cases there may not be any unsaved modifications to revert by reloading. In the following example, the assignment of the empty array to the association is immediately persisted and reloading does not make any changes to the document:
# Assuming band has many tours, which could be referenced: band = Band.create!(tours: [Tour.create!]) # ... or embedded: band = Band.create!(tours: [Tour.new]) # This writes the empty tour list into the database. band.tours = [] # There are no unsaved modifications in band at this point to be reverted. band.reload # Returns the empty array since this is what is in the database. band.tours # => []
If the model has a shard key defined, the shard key value is included in the reloading query.
If the database does not contain a matching document, Mongoid normally raises
Mongoid::Errors::DocumentNotFound
. However, if the configuration option
raise_not_found_error
is set to false
, and the database does not
contain a matching document, Mongoid replaces the current document with a newly
created document whose attributes are set to default values. Importantly, this
generally causes the _id
of the document to change, as the following
example demonstrates:
band = Band.create! # => #<Band _id: 6206d00de1b8324561f179c7, name: "foo", description: nil, likes: nil> Mongoid.raise_not_found_error = false band.destroy band.reload # => #<Band _id: 6206d031e1b8324561f179c8, name: nil, description: nil, likes: nil>
For this reason, it is not recommended to use reload
when
raise_not_found_error
is set to false
.
Reloading Unsaved Documents
reload
can be called when the document has not yet been persisted.
In this case reload
performs a find
query using the id
value
specified in the document (and the shard key value, if a shard key is defined):
existing = Band.create!(name: 'Photek') # Unsaved document band = Band.new(id: existing.id) band.reload band.name # => "Photek"
Accessing Field Values
Mongoid provides several ways of accessing field values.
Note
All of the access methods described below raise
Mongoid::Errors::AttributeNotLoaded
when the field being accessed is
projected out, either by virtue of not being included in
only or by virtue of being included in
without. This applies to both reads and writes.
Getters & Setters
The recommended way is to use the getter and setter methods generated for each declared field:
class Person include Mongoid::Document field :first_name end person = Person.new person.first_name = "Artem" person.first_name # => "Artem"
To use this mechanism, each field must be explicitly declared, or the model class must enable dynamic fields.
Custom Getters & Setters
It is possible to explicitly define the getter and setter methods to provide
custom behavior when reading or writing fields, for example value
transformations or storing values under different field names. In this case
read_attribute
and write_attribute
methods can be used to read and
write the values directly into the attributes hash:
class Person include Mongoid::Document def first_name read_attribute(:fn) end def first_name=(value) write_attribute(:fn, value) end end person = Person.new person.first_name = "Artem" person.first_name # => "Artem" person.attributes # => {"_id"=>BSON::ObjectId('606477dc2c97a628cf47075b'), "fn"=>"Artem"}
Note
The custom setters are called during the assignment of nested attributes, however they are called before the associations are set up. Because of this, associations may not always be available during these methods, and it is encouraged to include checks for their presence whenever referring to them. Callbacks can also be used to perform operations on certain events, and associations will have already been setup and are available during their execution.
read_attribute
& write_attribute
The read_attribute
and write_attribute
methods can be used explicitly
as well. Note that if a field specifies its storage field name, both read_attribute
and write_attribute
accept either the declared field name or the storage field name for operations:
class Person include Mongoid::Document field :first_name, as: :fn field :last_name, as: :ln end person = Person.new(first_name: "Artem") # => #<Person _id: 60647a522c97a6292c195b4b, first_name(fn): "Artem", last_name(ln): nil> person.read_attribute(:first_name) # => "Artem" person.read_attribute(:fn) # => "Artem" person.write_attribute(:last_name, "Pushkin") person # => #<Person _id: 60647a522c97a6292c195b4b, first_name(fn): "Artem", last_name(ln): "Pushkin"> person.write_attribute(:ln, "Medvedev") person # => #<Person _id: 60647a522c97a6292c195b4b, first_name(fn): "Artem", last_name(ln): "Medvedev">
read_attribute
and write_attribute
do not require that a field with
the used name is defined, but writing field values with write_attribute
does not cause the respective field to be defined either:
person.write_attribute(:undefined, "Hello") person # => #<Person _id: 60647b212c97a6292c195b4c, first_name(fn): "Artem", last_name(ln): "Medvedev"> person.attributes # => {"_id"=>BSON::ObjectId('60647b212c97a6292c195b4c'), "first_name"=>"Artem", "last_name"=>"Medvedev", "undefined"=>"Hello"} person.read_attribute(:undefined) # => "Hello" person.undefined # raises NoMethodError
When read_attribute
is used to access a missing field, it returns nil
.
Hash Access
Mongoid model instances define the []
and []=
methods to provide
Hash
style access to the attributes. []
is an alias for
read_attribute
and []=
is an alias for write_attribute
; see
the section on read_attribute and write_attribute
for the detailed description of their behavior.
class Person include Mongoid::Document field :first_name, as: :fn field :last_name, as: :ln end person = Person.new(first_name: "Artem") person["fn"] # => "Artem" person[:first_name] # => "Artem" person[:ln] = "Medvedev" person # => #<Person _id: 606483742c97a629bdde5cfc, first_name(fn): "Artem", last_name(ln): "Medvedev"> person["last_name"] = "Pushkin" person # => #<Person _id: 606483742c97a629bdde5cfc, first_name(fn): "Artem", last_name(ln): "Pushkin">
Bulk Attribute Writes
In cases where you want to set multiple field values at once, there are a few different ways of accomplishing this as well.
# Get the field values as a hash. person.attributes # Set the field values in the document. Person.new(first_name: "Jean-Baptiste", middle_name: "Emmanuel") person.attributes = { first_name: "Jean-Baptiste", middle_name: "Emmanuel" } person.write_attributes( first_name: "Jean-Baptiste", middle_name: "Emmanuel", )
Dirty Tracking
Mongoid supports tracking of changed or "dirty" fields with an API that mirrors that of Active Model. If a defined field has been modified in a model the model will be marked as dirty and some additional behavior comes into play.
Viewing Changes
There are various ways to view what has been altered on a model. Changes are recorded from the time a document is instantiated, either as a new document or via loading from the database up to the time it is saved. Any persistence operation clears the changes.
class Person include Mongoid::Document field :name, type: String end person = Person.first person.name = "Alan Garner" # Check to see if the document has changed. person.changed? # true # Get an array of the names of the changed fields. person.changed # [ :name ] # Get a hash of the old and changed values for each field. person.changes # { "name" => [ "Alan Parsons", "Alan Garner" ] } # Check if a specific field has changed. person.name_changed? # true # Get the changes for a specific field. person.name_change # [ "Alan Parsons", "Alan Garner" ] # Get the previous value for a field. person.name_was # "Alan Parsons"
Note
Setting the associations on a document does not cause the changes
or
changed_attributes
hashes to be modified. This is true for all associations
whether referenced or embedded. Note that changing the _id(s) field on
referenced associations does cause the changes to show up in the changes
and the changed_attributes
hashes.
Resetting Changes
You can reset changes of a field to its previous value by calling the reset method.
person = Person.first person.name = "Alan Garner" # Reset the changed name back to the original person.reset_name! person.name # "Alan Parsons"
Persistence
Mongoid uses dirty tracking as the core of its persistence operations. It looks at the
changes on a document and atomically updates only what has changed, unlike other frameworks
that write the entire document on each save. If no changes have been made, Mongoid will
not hit the database on a call to Model#save
.
Viewing Previous Changes
After a document has been persisted, you can see what the changes were previously by
calling Model#previous_changes
.
person = Person.first person.name = "Alan Garner" person.save # Clears out current changes. # View the previous changes. person.previous_changes # { "name" => [ "Alan Parsons", "Alan Garner" ] }
Updating Container Fields
Be aware that, until MONGOID-2951 is resolved, all fields including container ones must be assigned to for their values to be persisted to the database.
For example, adding to a set like this does not work:
class Band include Mongoid::Document field :tours, type: Set end band = Band.new band.tours # => #<Set: {}> band.tours << 'London' # => #<Set: {"London"}> band.tours # => #<Set: {}>
Instead, the field value must be modified outside of the model and assigned back to the model as follows:
class Band include Mongoid::Document field :tours, type: Set end band = Band.new tours = band.tours # => #<Set: {}> tours << 'London' # => #<Set: {"London"}> band.tours = tours # => #<Set: {"London"}> band.tours # => #<Set: {"London"}>
Readonly Documents
Documents can be marked read-only in two ways, depending on the value of the
Mongoid.legacy_readonly
feature flag:
If this flag is turned off, a document is marked read-only when the #readonly!
method is called on that documnet. A read-only document, with this flag turned off,
will raise a ReadonlyDocument error on attempting to perform any persistence
operation, including (but not limited to) saving, updating, deleting and
destroying. Note that reloading does not reset the read-only state.
band = Band.first band.readonly? # => false band.readonly! band.readonly? # => true band.name = "The Rolling Stones" band.save # => raises ReadonlyDocument error band.reload.readonly? # => true
If this flag is turned on, a document is marked read-only when that document
has been projected (i.e. using #only
or #without
). A read-only document,
with this flag turned on, will not be deletable or destroyable (a
ReadonlyDocument
error will be raised), but will be saveable and updatable.
The read-only status is reset on reloading the document.
class Band include Mongoid::Document field :name, type: String field :genre, type: String end band = Band.only(:name).first band.readonly? # => true band.destroy # => raises ReadonlyDocument error band.reload.readonly? # => false
Overriding readonly?
Another way to make a document read-only is by overriding the readonly? method:
class Band include Mongoid::Document field :name, type: String field :genre, type: String def readonly? true end end band = Band.first band.readonly? # => true band.destroy # => raises ReadonlyDocument error