mongodb/mongoid

View on GitHub
docs/reference/crud.txt

Summary

Maintainability
Test Coverage
***************
CRUD Operations
***************

.. default-domain:: mongodb

.. contents:: On this page
   :local:
   :backlinks: none
   :depth: 2
   :class: singlecol


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.

.. list-table::
   :header-rows: 1
   :widths: 30 60

   * - Operation
     - Example

   * - ``Model#attributes``

       *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.*

     -
        .. code-block:: ruby

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

          person.attributes
          # => { "_id" => BSON::ObjectId('633467d03282a43784c2d56e'), "first_name" => "Heinrich", "last_name" => "Heine" }

   * - ``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.*
     -
        .. code-block:: ruby

          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.*
     -
        .. code-block:: ruby

          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.*
     -
        .. code-block:: ruby

          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.*

       *Pass* ``touch: false`` *option to ignore the updates to the updated_at
       field. If the document being save has not been previously persisted,
       this option is ignored and the created_at and updated_at fields will be
       updated with the current time.*
     -
        .. code-block:: ruby

          person = Person.new(
            first_name: "Heinrich",
            last_name: "Heine"
          )
          person.save
          person.save(validate: false)
          person.save(touch: 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.*
     -
        .. code-block:: ruby

          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.*
     -
        .. code-block:: ruby

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

   * - ``Model#update_attribute``

       *Update a single attribute, bypassing validations.*
     -
        .. code-block:: ruby

          person.update_attribute(:first_name, "Jean")

   * - ``Model#upsert``

       *Performs a MongoDB replace with upsert on the document. If the document
       exists in the database and the* ``:replace`` *option is set to true, it
       will get overwritten with the current document in the application (any
       attributes present in the database but not in the application's document
       instance will be lost). If the* ``:replace`` *option is false (default),
       the document will be updated, and any attributes not in the application's
       document will be maintained.
       If the document does not exist in the database, it will be inserted.
       Note that this only runs the* ``{before|after|around}_upsert`` *callbacks.*
     -
        .. code-block:: ruby

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

   * - ``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``,
       *same as if attempting to update an attribute on a destroyed
       document.*
     -
        .. code-block:: ruby

          person.touch
          person.touch(:audited_at)

   * - ``Model#delete``

       *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* ``_id``.
     -
        .. code-block:: ruby

          person.delete

          person = Person.create!(...)
          unsaved_person = Person.new(id: person.id)
          unsaved_person.delete
          person.reload
          # raises Mongoid::Errors::DocumentNotFound because the person was deleted

   * - ``Model#destroy``

       *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* ``_id``.
     -
        .. code-block:: ruby

          person.destroy

          person = Person.create!(...)
          unsaved_person = Person.new(id: person.id)
          unsaved_person.destroy
          person.reload
          # raises Mongoid::Errors::DocumentNotFound because the person was deleted

   * - ``Model.delete_all``

       *Deletes all documents from the database without running any callbacks.*
     -
        .. code-block:: ruby

          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.*
     -
        .. code-block:: ruby

          Person.destroy_all

Mongoid provides the following persistence-related attributes:

.. list-table::
   :header-rows: 1
   :widths: 30 60

   * - Attribute
     - Example


   * - ``Model#new_record?``

       *Returns* ``true`` *if the model instance has not yet been saved
       to the database. Opposite of* ``persisted?``
     -
        .. code-block:: ruby

          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?``
     -
        .. code-block:: ruby

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


Atomic
------

Mongoid exposes :manual:`MongoDB update operators </reference/operator/update/>`
as methods on Mongoid documents. When these methods are used, callbacks are
not invoked and validations are not performed. The supported update operators
are:

.. list-table::
   :header-rows: 1
   :widths: 30 60

   * - Operation
     - Example

   * - ``Model#add_to_set``

       *Performs an atomic $addToSet on the field.*
     -
        .. code-block:: ruby

          person.add_to_set(aliases: "Bond")

   * - ``Model#bit``

       *Performs an atomic $bit on the field.*
     -
        .. code-block:: ruby

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

   * - ``Model#inc``

       *Performs an atomic $inc on the field.*
     -
        .. code-block:: ruby

          person.inc(age: 1)

   * - ``Model#pop``

       *Performs an atomic $pop on the field.*
     -
        .. code-block:: ruby

          person.pop(aliases: 1)

   * - ``Model#pull``

       *Performs an atomic $pull on the field.*
     -
        .. code-block:: ruby

          person.pull(aliases: "Bond")

   * - ``Model#pull_all``

       *Performs an atomic $pullAll on the field.*
     -
        .. code-block:: ruby

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

   * - ``Model#push``

       *Performs an atomic $push on the field.*
     -
        .. code-block:: ruby

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

   * - ``Model#rename``

       *Performs an atomic $rename on the field.*
     -
        .. code-block:: ruby

          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.*

     -
        .. code-block:: ruby

          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.*
     -
        .. code-block:: ruby

          person.unset(:name)

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 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:

.. code-block:: ruby

  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:

.. code-block:: ruby

  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``
:ref:`configuration option <configuration-options>` 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:

.. code-block:: ruby

  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:

.. code-block:: ruby

  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 :ref:`multi-document transactions <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:

.. code-block:: ruby

  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:

  .. code-block:: ruby

    # 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 :ref:`shard key <shard-keys>` 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:

.. code-block:: ruby

  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):

.. code-block:: ruby

  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
  :ref:`projected out <projection>`, either by virtue of not being included in
  :ref:`only <only>` or by virtue of being included in
  :ref:`without <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:

.. code-block:: ruby

  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 :ref:`dynamic fields <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:

.. code-block:: ruby

  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
  :ref:`nested attributes <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. :ref:`Callbacks <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-write-attribute:

``read_attribute`` & ``write_attribute``
----------------------------------------

The ``read_attribute`` and ``write_attribute`` methods can be used explicitly
as well. Note that if a field specifies its :ref:`storage field name
<storage-field-names>`, both ``read_attribute`` and ``write_attribute``
accept either the declared field name or the storage field name for operations:

.. code-block:: ruby

  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:

.. code-block:: ruby

  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 :ref:`read_attribute and write_attribute <read-write-attribute>`
for the detailed description of their behavior.

.. code-block:: ruby

  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.

.. code-block:: ruby

   # 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.

.. code-block:: ruby

   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.

.. code-block:: ruby

   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``.

.. code-block:: ruby

   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 <https://jira.mongodb.org/browse/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:

.. code-block:: ruby

  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:

.. code-block:: ruby

  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:

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.

.. code:: ruby

  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.

.. code:: ruby

  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:

.. code:: ruby

  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