.. _fields: **************** Field Definition **************** .. default-domain:: mongodb .. contents:: On this page :local: :backlinks: none :depth: 2 :class: singlecol .. _field-types: Field Types =========== MongoDB stores underlying document data using `BSON types `_, and Mongoid converts BSON types to Ruby types at runtime in your application. For example, a field defined with `type: :float` will use the Ruby ``Float`` class in-memory and will persist in the database as the the BSON ``double`` type. Field type definitions determine how Mongoid behaves when constructing queries and retrieving/writing fields from/to the database. Specifically: 1. When assigning values to fields at runtime, the values are converted to the specified type. 2. When persisting data to MongoDB, the data is sent in an appropriate type, permitting richer data manipulation within MongoDB or by other tools. 3. When querying documents, query parameters are converted to the specified type before being sent to MongoDB. 4. When retrieving documents from the database, field values are converted to the specified type. Changing the field definitions in a model class does not alter data already stored in MongoDB. To update type or contents of fields of existing documents, the field must be re-saved to the database. Note that, due to Mongoid tracking which attributes on a model change and only saving the changed ones, it may be necessary to explicitly write a field value when changing the type of an existing field without changing the stored values. Consider a simple class for modeling a person in an application. A person may have a name, date_of_birth, and weight. We can define these attributes on a person by using the ``field`` macro. .. code-block:: ruby class Person include Mongoid::Document field :name, type: String field :date_of_birth, type: Date field :weight, type: Float end The valid types for fields are as follows: - ``Array`` - ``BSON::Binary`` - :ref:`BigDecimal ` - ``Mongoid::Boolean``, which may be specified simply as ``Boolean`` in the scope of a class which included ``Mongoid::Document``. - :ref:`Date ` - :ref:`DateTime ` - ``Float`` - :ref:`Hash ` - ``Integer`` - :ref:`Object ` - ``BSON::ObjectId`` - ``Range`` - :ref:`Regexp ` - ``Set`` - ``String`` - :ref:`Mongoid::StringifiedSymbol `, which may be specified simply as ``StringifiedSymbol`` in the scope of a class which included ``Mongoid::Document``. - :ref:`Symbol ` - :ref:`Time ` - ``ActiveSupport::TimeWithZone`` Mongoid also recognizes the string ``"Boolean"`` as an alias for the ``Mongoid::Boolean`` class. To define custom field types, refer to :ref:`Custom Field Types ` below. .. note:: Using the ``BSON::Int64`` and ``BSON::Int32`` types as field types is unsupported. Saving these types to the database will work as expected, however, querying them will return the native Ruby ``Integer`` type. Querying fields of type ``BSON::Decimal128`` will return values of type ``BSON::Decimal128`` in BSON <=4 and values of type ``BigDecimal`` in BSON 5+. .. _untyped-fields: Untyped Fields -------------- Not specifying a type for a field is the same as specifying the ``Object`` type. Such fields are untyped: .. code-block:: ruby class Product include Mongoid::Document field :properties # Equivalent to: field :properties, type: Object end An untyped field can store values of any type which is directly serializable to BSON. This is useful when a field may contain values of different types (i.e. it is a variant type field), or when the type of values is not known ahead of time: .. code-block:: ruby product = Product.new(properties: "color=white,size=large") product.properties # => "color=white,size=large" product = Product.new(properties: {color: "white", size: "large"}) product.properties # => {:color=>"white", :size=>"large"} When values are assigned to the field, Mongoid still performs mongoization but uses the class of the value rather than the field type for mongoization logic. .. code-block:: ruby product = Product.new(properties: 0..10) product.properties # The range 0..10, mongoized: # => {"min"=>0, "max"=>10} When reading data from the database, Mongoid does not perform any type conversions on untyped fields. For this reason, even though it is possible to write any BSON-serializable value into an untyped fields, values which require special handling on the database reading side will generally not work correctly in an untyped field. Among field types supported by Mongoid, values of the following types should not be stored in untyped fields: - ``Date`` (values will be returned as ``Time``) - ``DateTime`` (values will be returned as ``Time``) - ``Range`` (values will be returned as ``Hash``) .. _field-type-stringified-symbol: Field Type: StringifiedSymbol ----------------------------- The ``StringifiedSymbol`` field type is the recommended field type for storing values that should be exposed as symbols to Ruby applications. When using the ``Symbol`` field type, Mongoid defaults to storing values as BSON symbols. For more information on the BSON symbol type, see :ref:`here `. However, the BSON symbol type is deprecated and is difficult to work with in programming languages without native symbol types, so the ``StringifiedSymbol`` type allows the use of symbols while ensuring interoperability with other drivers. The ``StringifiedSymbol`` type stores all data on the database as strings, while exposing values to the application as symbols. An example usage is shown below: .. code-block:: ruby class Post include Mongoid::Document field :status, type: StringifiedSymbol end post = Post.new(status: :hello) # status is stored as "hello" on the database, but returned as a Symbol post.status # => :hello # String values can be assigned also: post = Post.new(status: "hello") # status is stored as "hello" on the database, but returned as a Symbol post.status # => :hello All non-string values will be stringified upon being sent to the database (via ``to_s``), and all values will be converted to symbols when returned to the application. Values that cannot be converted directly to symbols, such as integers and arrays, will first be converted to strings and then symbols before being returned to the application. For example, setting an integer as ``status``: .. code-block:: ruby post = Post.new(status: 42) post.status # => :"42" If the ``StringifiedSymbol`` type is applied to a field that contains BSON symbols, the values will be stored as strings instead of BSON symbols on the next save. This permits transparent lazy migration from fields that currently store either strings or BSON symbols in the database to the ``StringifiedSymbol`` field type. .. _field-type-symbol: Field Type: Symbol ------------------ New applications should use the :ref:`StringifiedSymbol field type ` to store Ruby symbols in the database. The ``StringifiedSymbol`` field type provides maximum compatibility with other applications and programming languages and has the same behavior in all circumstances. Mongoid also provides the deprecated ``Symbol`` field type for serializing Ruby symbols to BSON symbols. Because the BSON specification deprecated the BSON symbol type, the `bson` gem will serialize Ruby symbols into BSON strings when used on its own. However, in order to maintain backwards compatibility with older datasets, the `mongo` gem overrides this behavior to serialize Ruby symbols as BSON symbols. This is necessary to be able to specify queries for documents which contain BSON symbols as fields. To override the default behavior and configure the ``mongo`` gem (and thereby Mongoid as well) to encode symbol values as strings, include the following code snippet in your project: .. code-block:: ruby class Symbol def bson_type BSON::String::BSON_TYPE end end .. _field-type-hash: Field Type: Hash ---------------- When using a field of type Hash, be wary of adhering to the `legal key names for mongoDB `_, or else the values will not store properly. .. code-block:: ruby class Person include Mongoid::Document field :first_name field :url, type: Hash # will update the fields properly and save the values def set_vals self.first_name = 'Daniel' self.url = {'home_page' => 'http://www.homepage.com'} save end # all data will fail to save due to the illegal hash key def set_vals_fail self.first_name = 'Daniel' self.url = {'home.page' => 'http://www.homepage.com'} save end end .. _field-type-time: Field Type: Time ---------------- ``Time`` fields store values as ``Time`` instances in the :ref:`configured time zone `. ``Date`` and ``DateTime`` instances are converted to ``Time`` instances upon assignment to a ``Time`` field: .. code-block:: ruby class Voter include Mongoid::Document field :registered_at, type: Time end Voter.new(registered_at: Date.today) # => # In the above example, the value was interpreted as the beginning of today in local time, because the application was not configured to use UTC times. .. note:: When the database contains a string value for a ``Time`` field, Mongoid parses the string value using ``Time.parse`` which considers values without time zones to be in local time. .. _field-type-date: Field Type: Date ---------------- Mongoid allows assignment of values of several types to ``Date`` fields: - ``Date`` - the provided date is stored as is. - ``Time``, ``DateTime``, ``ActiveSupport::TimeWithZone`` - the date component of the value is taken in the value's time zone. - ``String`` - the date specified in the string is used. - ``Integer``, ``Float`` - the value is taken to be a UTC timestamp which is converted to the :ref:`configured time zone ` (note that ``Mongoid.use_utc`` has no effect on this conversion), then the date is taken from the resulting time. In other words, if a date is specified in the value, that date is used without first converting the value to the configured time zone. As a date & time to date conversion is lossy (it discards the time component), especially if an application operates with times in different time zones it is recommended to explicitly convert ``String``, ``Time`` and ``DateTime`` objects to ``Date`` objects before assigning the values to fields of type ``Date``. .. note:: When the database contains a string value for a ``Date`` field, Mongoid parses the string value using ``Time.parse``, discards the time portion of the resulting ``Time`` object and uses the date portion. ``Time.parse`` considers values without time zones to be in local time. .. _field-type-date-time: Field Type: DateTime --------------------- MongoDB stores all times as UTC timestamps. When assigning a value to a ``DateTime`` field, or when querying a ``DateTime`` field, Mongoid converts the passed in value to a UTC ``Time`` before sending it to the MongoDB server. ``Time``, ``ActiveSupport::TimeWithZone`` and ``DateTime`` objects embed time zone information, and the value persisted is the specified moment in time, in UTC. When the value is retrieved, the time zone in which it is returned is defined by the :ref:`configured time zone settings `. .. code-block:: ruby class Ticket include Mongoid::Document field :opened_at, type: DateTime end Mongoid.use_activesupport_time_zone = true Time.zone = 'Berlin' ticket = Ticket.create!(opened_at: '2018-02-18 07:00:08 -0500') ticket.opened_at # => Sun, 18 Feb 2018 13:00:08 +0100 ticket # => # Time.zone = 'America/New_York' ticket.opened_at # => Sun, 18 Feb 2018 07:00:08 -0500 Mongoid.use_utc = true ticket.opened_at # => Sun, 18 Feb 2018 12:00:08 +0000 Mongoid also supports casting integers and floats to ``DateTime``. When doing so, the integers/floats are assumed to be Unix timestamps (in UTC): .. code-block:: ruby ticket.opened_at = 1544803974 ticket.opened_at # => Fri, 14 Dec 2018 16:12:54 +0000 If a string is used as a ``DateTime`` field value, the behavior depends on whether the string includes a time zone. If no time zone is specified, the :ref:`default Mongoid time zone ` is used: .. code-block:: ruby Time.zone = 'America/New_York' ticket.opened_at = 'Mar 4, 2018 10:00:00' ticket.opened_at # => Sun, 04 Mar 2018 15:00:00 +0000 If a time zone is specified, it is respected: .. code-block:: ruby ticket.opened_at = 'Mar 4, 2018 10:00:00 +01:00' ticket.opened_at # => Sun, 04 Mar 2018 09:00:00 +0000 .. note:: When the database contains a string value for a ``DateTime`` field, Mongoid parses the string value using ``Time.parse`` which considers values without time zones to be in local time. .. _field-type-regexp: Field Type: Regexp ------------------ MongoDB supports storing regular expressions in documents, and querying using regular expressions. Note that MongoDB uses `Perl-compatible regular expressions (PCRE) `_ and Ruby uses `Onigmo `_, which is a fork of `Oniguruma regular expression engine `_. The two regular expression implementations generally provide equivalent functionality but have several important syntax differences. When a field is declared to be of type Regexp, Mongoid converts Ruby regular expressions to BSON regular expressions and stores the result in MongoDB. Retrieving the field from the database produces a ``BSON::Regexp::Raw`` instance: .. code-block:: ruby class Token include Mongoid::Document field :pattern, type: Regexp end token = Token.create!(pattern: /hello.world/m) token.pattern # => /hello.world/m token.reload token.pattern # => # Use ``#compile`` method on ``BSON::Regexp::Raw`` to get back the Ruby regular expression: .. code-block:: ruby token.pattern.compile # => /hello.world/m Note that, if the regular expression was not originally a Ruby one, calling ``#compile`` on it may produce a different regular expression. For example, the following is a PCRE matching a string that ends in "hello": .. code-block:: ruby BSON::Regexp::Raw.new('hello$', 's') # => # Compiling this regular expression produces a Ruby regular expression that matches strings containing "hello" before a newline, besides strings ending in "hello": .. code-block:: ruby BSON::Regexp::Raw.new('hello$', 's').compile =~ "hello\nworld" # => 0 This is because the meaning of ``$`` is different between PCRE and Ruby regular expressions. .. _field-type-big-decimal: BigDecimal Fields ----------------- The ``BigDecimal`` field type is used to store numbers with increased precision. The ``BigDecimal`` field type stores its values in two different ways in the database, depending on the value of the ``Mongoid.map_big_decimal_to_decimal128`` global config option. If this flag is set to false (which is the default), the ``BigDecimal`` field will be stored as a string, otherwise it will be stored as a ``BSON::Decimal128``. The ``BigDecimal`` field type has some limitations when converting to and from a ``BSON::Decimal128``: - ``BSON::Decimal128`` has a limited range and precision, while ``BigDecimal`` has no restrictions in terms of range and precision. ``BSON::Decimal128`` has a max value of approximately ``10^6145`` and a min value of approximately ``-10^6145``, and has a maximum of 34 bits of precision. When attempting to store values that don't fit into a ``BSON::Decimal128``, it is recommended to have them stored as a string instead of a ``BSON::Decimal128``. You can do that by setting ``Mongoid.map_big_decimal_to_decimal128`` to ``false``. If a value that does not fit in a ``BSON::Decimal128`` is attempted to be stored as one, an error will be raised. - ``BSON::Decimal128`` is able to accept signed ``NaN`` values, while ``BigDecimal`` is not. When retrieving signed ``NaN`` values from the database using the ``BigDecimal`` field type, the ``NaN`` will be unsigned. - ``BSON::Decimal128`` maintains trailing zeroes when stored in the database. ``BigDecimal``, however, does not maintain trailing zeroes, and therefore retrieving ``BSON::Decimal128`` values using the ``BigDecimal`` field type may result in a loss of precision. There is an additional caveat when storing a ``BigDecimal`` in a field with no type (i.e. a dynamically typed field) and ``Mongoid.map_big_decimal_to_decimal128`` is ``false``. In this case, the ``BigDecimal`` is stored as a string, and since a dynamic field is being used, querying for that field with a ``BigDecimal`` will not find the string for that ``BigDecimal``, since the query is looking for a ``BigDecimal``. In order to query for that string, the ``BigDecimal`` must first be converted to a string with ``to_s``. Note that this is not a problem when the field has type ``BigDecimal``. If you wish to avoid using ``BigDecimal`` altogether, you can set the field type to ``BSON::Decimal128``. This will allow you to keep track of trailing zeroes and signed ``NaN`` values. Migration to ``decimal128``-backed ``BigDecimal`` Field ``````````````````````````````````````````````````````` In a future major version of Mongoid, the ``Mongoid.map_big_decimal_to_decimal128`` global config option will be defaulted to ``true``. When this flag is turned on, ``BigDecimal`` values in queries will not match to the strings that are already stored in the database; they will only match to ``decimal128`` values that are in the database. If you have a ``BigDecimal`` field that is backed by strings, you have three options: 1. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be set to ``false``, and you can continue storing your ``BigDecimal`` values as strings. Note that you are surrendering the advantages of storing ``BigDecimal`` values as a ``decimal128``, like being able to do queries and aggregations based on the numerical value of the field. 2. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be set to ``true``, and you can convert all values for that field from strings to ``decimal128`` values in the database. You should do this conversion before setting the global config option to true. An example query to accomplish this is as follows: .. code-block:: javascript db.bands.updateMany({ "field": { "$exists": true } }, [ { "$set": { "field": { "$toDecimal": "$field" } } } ]) This query updates all documents that have the given field, setting that field to its corresponding ``decimal128`` value. Note that this query only works in MongoDB 4.2+. 3. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be set to ``true``, and you can have both strings and ``decimal128`` values for that field. This way, only ``decimal128`` values will be inserted into and updated to the database going forward. Note that you still don't get the full advantages of using only ``decimal128`` values, but your dataset is slowly migrating to all ``decimal128`` values, as old string values are updated to ``decimal128`` and new ``decimal128`` values are added. With this setup, you can still query for ``BigDecimal`` values as follows: .. code-block:: ruby Mongoid.map_big_decimal_to_decimal128 = true big_decimal = BigDecimal('2E9') Band.in(sales: [big_decimal, big_decimal.to_s]).to_a This query will find all values that are either a ``decimal128`` value or a string that match that value. Using Symbols Or Strings Instead Of Classes ------------------------------------------- Mongoid permits using symbols or strings instead of classes to specify the type of fields, for example: .. code-block:: ruby class Order include Mongoid::Document field :state, type: :integer # Equivalent to: field :state, type: "integer" # Equivalent to: field :state, type: Integer end Only standard field types as listed below can be specified using symbols or strings in this manner. Mongoid recognizes the following expansions: - ``:array`` => ``Array`` - ``:big_decimal`` => ``BigDecimal`` - ``:binary`` => ``BSON::Binary`` - ``:boolean`` => ``Mongoid::Boolean`` - ``:date`` => ``Date`` - ``:date_time`` => ``DateTime`` - ``:float`` => ``Float`` - ``:hash`` => ``Hash`` - ``:integer`` => ``Integer`` - ``:object_id`` => ``BSON::ObjectId`` - ``:range`` => ``Range`` - ``:regexp`` => ``Regexp`` - ``:set`` => ``Set`` - ``:string`` => ``String`` - ``:stringified_symbol`` => ``StringifiedSymbol`` - ``:symbol`` => ``Symbol`` - ``:time`` => ``Time`` .. _field-default-values: Specifying Field Default Values ------------------------------- A field can be configured to have a default value. The default value can be fixed, as in the following example: .. code-block:: ruby class Order include Mongoid::Document field :state, type: String, default: 'created' end The default value can also be specified as a ``Proc``: .. code-block:: ruby class Order include Mongoid::Document field :fulfill_by, type: Time, default: ->{ Time.now + 3.days } end .. note:: Default values that are not ``Proc`` instances are evaluated at class load time, meaning the following two definitions are not equivalent: .. code-block:: ruby field :submitted_at, type: Time, default: Time.now field :submitted_at, type: Time, default: ->{ Time.now } The second definition is most likely the desired one, which causes the time of submission to be set to the current time at the moment of document instantiation. To set a default which depends on the document's state, use ``self`` inside the ``Proc`` instance which would evaluate to the document instance being operated on: .. code-block:: ruby field :fulfill_by, type: Time, default: ->{ # Order should be fulfilled in 2 business hours. if (7..8).include?(self.submitted_at.hour) self.submitted_at + 4.hours elsif (9..3).include?(self.submitted_at.hour) self.submitted_at + 2.hours else (self.submitted_at + 1.day).change(hour: 11) end } When defining a default value as a ``Proc``, Mongoid will apply the default after all other attributes are set and associations are initialized. To have the default be applied before the other attributes are set, use the ``pre_processed: true`` field option: .. code-block:: ruby field :fulfill_by, type: Time, default: ->{ Time.now + 3.days }, pre_processed: true The ``pre_processed: true`` option is also necessary when specifying a custom default value via a ``Proc`` for the ``_id`` field, to ensure the ``_id`` is set correctly via associations: .. code-block:: ruby field :_id, type: String, default: -> { 'hello' }, pre_processed: true .. _storage-field-names: Specifying Storage Field Names ------------------------------ One of the drawbacks of having a schemaless database is that MongoDB must store all field information along with every document, meaning that it takes up a lot of storage space in RAM and on disk. A common pattern to limit this is to alias fields to a small number of characters, while keeping the domain in the application expressive. Mongoid allows you to do this and reference the fields in the domain via their long names in getters, setters, and criteria while performing the conversion for you. .. code-block:: ruby class Band include Mongoid::Document field :n, as: :name, type: String end band = Band.new(name: "Placebo") band.attributes # { "n" => "Placebo" } criteria = Band.where(name: "Placebo") criteria.selector # { "n" => "Placebo" } .. _field-aliases: Field Aliases ------------- It is possible to define field aliases. The value will be stored in the destination field but can be accessed from either the destination field or from the aliased field: .. code-block:: ruby class Band include Mongoid::Document field :name, type: String alias_attribute :n, :name end band = Band.new(n: 'Astral Projection') # => # band.attributes # => {"_id"=>BSON::ObjectId('5fc1c1ee2c97a64accbeb5e1'), "name"=>"Astral Projection"} band.n # => "Astral Projection" Aliases can be removed from model classes using the ``unalias_attribute`` method. .. code-block:: ruby class Band unalias_attribute :n end .. _unalias-id: Unaliasing ``id`` ````````````````` ``unalias_attribute`` can be used to remove the predefined ``id`` alias. This is useful for storing different values in ``id`` and ``_id`` fields: .. code-block:: ruby class Band include Mongoid::Document unalias_attribute :id field :id, type: String end Band.new(id: '42') # => # Reserved Names -------------- Attempting to define a field on a document that conflicts with a reserved method name in Mongoid will raise an error. The list of reserved names can be obtained by invoking the ``Mongoid.destructive_fields`` method. Field Redefinition ------------------ By default Mongoid allows redefining fields on a model. To raise an error when a field is redefined, set the ``duplicate_fields_exception`` :ref:`configuration option ` to ``true``. With the option set to true, the following example will raise an error: .. code-block:: ruby class Person include Mongoid::Document field :name field :name, type: String end To define the field anyway, use the ``overwrite: true`` option: .. code-block:: ruby class Person include Mongoid::Document field :name field :name, type: String, overwrite: true end .. _custom-id: Custom IDs ---------- By default, Mongoid defines the ``_id`` field on documents to contain a ``BSON::ObjectId`` value which is automatically generated by Mongoid. It is possible to replace the ``_id`` field definition to change the type of the ``_id`` values or have different default values: .. code-block:: ruby class Band include Mongoid::Document field :name, type: String field :_id, type: String, default: ->{ name } end It is possible to omit the default entirely: .. code-block:: ruby class Band include Mongoid::Document field :_id, type: String end If the default on ``_id`` is omitted, and no ``_id`` value is provided by your application, Mongoid will persist the document without the ``_id`` value. In this case, if the document is a top-level document, an ``_id`` value will be assigned by the server; if the document is an embedded document, no ``_id`` value will be assigned. Mongoid will not automatically retrieve this value, if assigned, when the document is persisted - you must obtain the persisted value (and the complete persisted document) using other means: .. code-block:: ruby band = Band.create! => # band.id => nil band.reload # raises Mongoid::Errors::DocumentNotFound Band.last => # Omitting ``_id`` fields is more common in :ref:`embedded documents `. Mongoid also defines the ``id`` field aliased to ``_id``. The ``id`` alias can :ref:`be removed ` if desired (such as to integrate with systems that use the ``id`` field to store value different from ``_id``. .. _uncastable-values: Uncastable Values ----------------- In Mongoid 8, Mongoid has standardized the treatment of the assignment and reading of "uncastable" values. A value is considered "uncastable" when it cannot be coerced to the type of its field. For example, an array would be an "uncastable" value to an Integer field. Assigning Uncastable Values ``````````````````````````` The assignment of uncastable values has been standardized to assign ``nil`` by default. Consider the following example: .. code:: class User include Mongoid::Document field :name, type: Integer end User.new(name: [ "hello" ]) Assigning an array to a field of type Integer doesn't work since an array can't be coerced to an Integer. The assignment of uncastable values to a field will cause a ``nil`` to be written: .. code:: user = User.new(name: [ "Mike", "Trout" ]) # => # Note that the original uncastable values will be stored in the ``attributes_before_type_cast`` hash with their field names: .. code:: user.attributes_before_type_cast["name"] # => ["Mike", "Trout"] .. note:: Note that for numeric fields, any class that defines ``to_i`` for Integer fields, ``to_f`` for Floats, and ``to_d`` for BigDecimals, is castable. Strings are the exception and will only call the corresponding ``to_*`` method if the string is numeric. If a class only defines ``to_i`` and not ``to_f`` and is being assigned to a Float field, this is uncastable, and Mongoid will not perform a two-step conversion (i.e. ``to_i`` and then ``to_f``). Reading Uncastable Values ````````````````````````` When documents in the database contain values of different types than their represenations in Mongoid, if Mongoid cannot coerce them into the correct type, it will replace the value with ``nil``. Consider the following model and document in the database: .. code:: class User include Mongoid::Document field :name, type: Integer end .. code:: { _id: ..., name: [ "Mike", "Trout" ] } Reading this document from the database will result in the model's name field containing ``nil``: .. code:: User.first.name # => nil The database value of type array cannot be stored in the attribute, since the array can't be coerced to an Integer. Note that the original uncastable values will be stored in the ``attributes_before_type_cast`` hash with their field names: .. code:: user.attributes_before_type_cast["name"] # => ["Mike", "Trout"] .. note:: The ``demongoize`` methods on container objects (i.e. Hash, Array) have not been changed to permit automatic persistence of mutated container attributes. See `MONGOID-2951 `_ for a longer discussion of this topic. .. _customizing-field-behavior: Customizing Field Behavior ========================== Mongoid offers several ways to customize the behavior of fields. .. _custom-getters-and-setters: Custom Getters And Setters -------------------------- You may override getters and setters for fields to modify the values when they are being accessed or written. The getters and setters use the same name as the field. Use ``read_attribute`` and ``write_attribute`` methods inside the getters and setters to operate on the raw attribute values. For example, Mongoid provides the ``:default`` field option to write a default value into the field. If you wish to have a field default value in your application but do not wish to persist it, you can override the getter as follows: .. code-block:: ruby class DistanceMeasurement include Mongoid::Document field :value, type: Float field :unit, type: String def unit read_attribute(:unit) || "m" end def to_s "#{value} #{unit}" end end measurement = DistanceMeasurement.new(value: 2) measurement.to_s # => "2.0 m" measurement.attributes # => {"_id"=>BSON::ObjectId('613fa0b0a15d5d61502f3447'), "value"=>2.0} To give another example, a field which converts empty strings to nil values may be implemented as follows: .. code-block:: ruby class DistanceMeasurement include Mongoid::Document field :value, type: Float field :unit, type: String def unit=(value) if value.blank? value = nil end write_attribute(:unit, value) end end measurement = DistanceMeasurement.new(value: 2, unit: "") measurement.attributes # => {"_id"=>BSON::ObjectId('613fa15aa15d5d617216104c'), "value"=>2.0, "unit"=>nil} .. _custom-field-types: Custom Field Types ------------------ You can define custom types in Mongoid and determine how they are serialized and deserialized. In this example, we define a new field type ``Point``, which we can use in our model class as follows: .. code-block:: ruby class Profile include Mongoid::Document field :location, type: Point end Then make a Ruby class to represent the type. This class must define methods used for MongoDB serialization and deserialization as follows: .. code-block:: ruby class Point attr_reader :x, :y def initialize(x, y) @x, @y = x, y end # Converts an object of this instance into a database friendly value. # In this example, we store the values in the database as array. def mongoize [ x, y ] end class << self # Takes any possible object and converts it to how it would be # stored in the database. def mongoize(object) case object when Point then object.mongoize when Hash then Point.new(object[:x], object[:y]).mongoize else object end end # Get the object as it was stored in the database, and instantiate # this custom class from it. def demongoize(object) Point.new(object[0], object[1]) end # Converts the object that was supplied to a criteria and converts it # into a query-friendly form. def evolve(object) case object when Point then object.mongoize else object end end end end The instance method ``mongoize`` takes an instance of your custom type object, and converts it into a represenation of how it will be stored in the database, i.e. to pass to the MongoDB Ruby driver. In our example above, we want to store our ``Point`` object as an ``Array`` in the form ``[ x, y ]``. The class method ``mongoize`` is similar to the instance method, however it must handle objects of all possible types as inputs. The ``mongoize`` method is used when calling the setter methods for fields of your custom type. .. code-block:: ruby point = Point.new(12, 24) venue = Venue.new(location: point) # This uses the Point#mongoize instance method. venue = Venue.new(location: [ 12, 24 ]) # This uses the Point.mongoize class method. The class method ``demongoize`` does the inverse of ``mongoize``. It takes the raw object from the MongoDB Ruby driver and converts it to an instance of your custom type. In this case, the database driver returns an ``Array`` and we instantiate a ``Point`` from it. The ``demongoize`` method is used when calling the getters of fields for your custom type. Note that in the example above, since ``demongoize`` calls ``Point.new``, a new instance of ``Point`` will be generated on each call to the getter. Mongoid will always call the ``demongoize`` method on values that were retrieved from the database, but applications may, in theory, call ``demongoize`` with arbitrary input. It is recommended that applications add handling for arbitrary input in their ``demongoize`` methods. We can rewrite ``Point``'s demongoize method as follows: .. code:: ruby def demongoize(object) if object.is_a?(Array) && object.length == 2 Point.new(object[0], object[1]) end end Notice that ``demongoize`` will only create a new ``Point`` if given an array of length 2, and will return ``nil`` otherwise. Both the ``mongoize`` and ``demongoize`` methods should be prepared to receive arbitrary input and should return ``nil`` on values that are uncastable to your custom type. See the section on :ref:`Uncastable Values ` for more details. Lastly, the class method ``evolve`` is similar to ``mongoize``, however it is used when transforming objects for use in Mongoid query criteria. .. code-block:: ruby point = Point.new(12, 24) Venue.where(location: point) # This uses Point.evolve The ``evolve`` method should also be prepared to receive arbitrary input, however, unlike the ``mongoize`` and ``demongoize`` methods, it should return the inputted value on values that are uncastable to your custom type. See the section on :ref:`Uncastable Values ` for more details. .. _phantom-custom-field-types: Phantom Custom Field Types `````````````````````````` The custom field type may perform conversions from user-visible attribute values to the values stored in the database when the user-visible attribute value type is different from the declared field type. For example, this can be used to implement a mapping from one enumeration to another, to have more descriptive values in the application and more compact values stored in the database: .. code-block:: ruby class ColorMapping MAPPING = { 'black' => 0, 'white' => 1, }.freeze INVERSE_MAPPING = MAPPING.invert.freeze class << self # Takes application-scope value and converts it to how it would be # stored in the database. Converts invalid values to nil. def mongoize(object) MAPPING[object] end # Get the value as it was stored in the database, and convert to # application-scope value. Converts invalid values to nil. def demongoize(object) INVERSE_MAPPING[object] end # Converts the object that was supplied to a criteria and converts it # into a query-friendly form. Returns invalid values as is. def evolve(object) MAPPING.fetch(object, object) end end end class Profile include Mongoid::Document field :color, type: ColorMapping end profile = Profile.new(color: 'white') profile.color # => "white" # Writes 0 to color field profile.save! .. _custom-field-options: Custom Field Options -------------------- You may define custom options for the ``field`` macro function which extend its behavior at the your time model classes are loaded. As an example, we will define a ``:max_length`` option which will add a length validator for the field. First, declare the new field option in an initializer, specifiying its handler function as a block: .. code-block:: ruby # in /config/initializers/mongoid_custom_fields.rb Mongoid::Fields.option :max_length do |model, field, value| model.validates_length_of field.name, maximum: value end Then, use it your model class: .. code-block:: ruby class Person include Mongoid::Document field :name, type: String, max_length: 10 end Note that the handler function will be invoked whenever the option is used in the field definition, even if the option's value is false or nil. .. _dynamic-fields: Dynamic Fields ============== By default, Mongoid requires all fields that may be set on a document to be explicitly defined using ``field`` declarations. Mongoid also supports creating fields on the fly from an arbitrary hash or documents stored in the database. When a model uses fields not explicitly defined, such fields are called *dynamic fields*. To enable dynamic fields, include ``Mongoid::Attributes::Dynamic`` module in the model: .. code-block:: ruby class Person include Mongoid::Document include Mongoid::Attributes::Dynamic end bob = Person.new(name: 'Bob', age: 42) bob.name # => "Bob" It is possible to use ``field`` declarations and dynamic fields in the same model class. Attributes for which there is a ``field`` declaration will be treated according to the ``field`` declaration, with remaining attributes being treated as dynamic fields. Attribute values in the dynamic fields must initially be set by either passing the attribute hash to the constructor, mass assignment via ``attributes=``, mass assignment via ``[]=``, using ``write_attribute``, or they must already be present in the database. .. code-block:: ruby # OK bob = Person.new(name: 'Bob') # OK bob = Person.new bob.attributes = {age: 42} # OK bob = Person.new bob['age'] = 42 # Raises NoMethodError: undefined method age= bob = Person.new bob.age = 42 # OK bob = Person.new # OK - string access bob.write_attribute('age', 42) # OK - symbol access bob.write_attribute(:name, 'Bob') # OK, initializes attributes from whatever is in the database bob = Person.find('123') If an attribute is not present in a particular model instance's attributes hash, both the reader and the writer for the corresponding field are not defined, and invoking them raises ``NoMethodError``: .. code-block:: ruby bob = Person.new bob.attributes = {age: 42} bob.age # => 42 # raises NoMethodError bob.name # raises NoMethodError bob.name = 'Bob' # OK bob['name'] = 'Bob' bob.name # => "Bob" Attributes can always be read using mass attribute access or ``read_attribute`` (this applies to models not using dynamic fields as well): .. code-block:: ruby bob = Person.new(age: 42) # OK - string access bob['name'] # => nil # OK - symbol access bob[:name] # => nil # OK - string access bob['age'] # => 42 # OK - symbol access bob[:age] # => 42 # OK bob.attributes['name'] # => nil # OK bob.attributes['age'] # => 42 # Returns nil - keys are always strings bob.attributes[:age] # => nil # OK bob.read_attribute('name') # => nil # OK bob.read_attribute(:name) # => nil # OK - string access bob.read_attribute('age') # => 42 # OK - symbol access bob.read_attribute(:age) # => 42 .. note:: The values returned from the ``read_attribute`` method, and those stored in the ``attributes`` hash, are the ``mongoized`` values. Special Characters in Field Names --------------------------------- Mongoid permits dynamic field names to include spaces and punctuation: .. code-block:: ruby bob = Person.new('hello world' => 'MDB') bob.send('hello world') # => "MDB" bob.write_attribute("hello%world", 'MDB') bob[:"hello%world"] # => "MDB" Localized Fields ================ Mongoid supports localized fields via `i18n `_. .. code-block:: ruby class Product include Mongoid::Document field :description, type: String, localize: true end By telling the field to ``localize``, Mongoid will under the covers store the field as a hash of locale/value pairs, but normal access to it will behave like a string. .. code-block:: ruby I18n.default_locale = :en product = Product.new product.description = "Marvelous!" I18n.locale = :de product.description = "Fantastisch!" product.attributes # { "description" => { "en" => "Marvelous!", "de" => "Fantastisch!" } You can get and set all the translations at once by using the corresponding ``_translations`` method. .. code-block:: ruby product.description_translations # { "en" => "Marvelous!", "de" => "Fantastisch!" } product.description_translations = { "en" => "Marvelous!", "de" => "Wunderbar!" } Localized fields can be used with any field type. For example, they can be used with float fields for differences with currency: .. code:: ruby class Product include Mongoid::Document field :price, type: Float, localize: true field :currency, type: String, localize: true end By creating the model in this way, we can separate the price from the currency type, which allows you to use all of the number-related functionalities on the price when querying or aggregating that field (provided that you index into the stored translations hash). We can create an instance of this model as follows: .. code:: ruby product = Product.new I18n.locale = :en product.price = 1.00 product.currency = "$" I18n.locale = :he product.price = 3.24 product.currency = "₪" product.attributes # => { "price" => { "en" => 1.0, "he" => 3.24 }, "currency" => { "en" => "$", "he" => "₪" } } .. _present-fields: Localize ``:present`` Field Option ---------------------------------- Mongoid supports the ``:present`` option when creating a localized field: .. code-block:: ruby class Product include Mongoid::Document field :description, localize: :present end This option automatically removes ``blank`` values (i.e. those that return true for the ``blank?`` method) from the ``_translations`` hash: .. code-block:: ruby I18n.default_locale = :en product = Product.new product.description = "Marvelous!" I18n.locale = :de product.description = "Fantastisch!" product.description_translations # { "en" => "Marvelous!", "de" => "Fantastisch!" } product.description = "" product.description_translations # { "en" => "Marvelous!" } When the empty string is written for the ``:de`` locale, the ``"de"`` key is removed from the ``_translations`` hash instead of writing the empty string. Fallbacks --------- Mongoid integrates with `i18n fallbacks `_. To use the fallbacks, the respective functionality must be explicitly enabled. In a Rails application, set the ``config.i18n.fallbacks`` configuration setting to ``true`` in your environment and specify the fallback languages: .. code-block:: ruby config.i18n.fallbacks = true config.after_initialize do I18n.fallbacks[:de] = [ :en, :es ] end In a non-Rails application, include the fallbacks module into the I18n backend you are using and specify the fallback languages: .. code-block:: ruby require "i18n/backend/fallbacks" I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) I18n.fallbacks[:de] = [ :en, :es ] When fallbacks are enabled, if a translation is not present in the active language, translations will be looked up in the fallback languages: .. code-block:: ruby product = Product.new I18n.locale = :en product.description = "Marvelous!" I18n.locale = :de product.description # "Marvelous!" Mongoid also defines a ``:fallbacks`` option on fields, which can be used to disable fallback functionality on a specific field: .. code:: ruby class Product include Mongoid::Document field :description, type: String, localize: true, fallbacks: false end product = Product.new I18n.locale = :en product.description = "Marvelous!" I18n.locale = :de product.description # nil Note that this option defaults to ``true``. .. note:: In i18n 1.1, the behavior of fallbacks `changed `_ to always require an explicit list of fallback locales rather than falling back to the default locale when no fallback locales have been provided. Querying -------- When querying for localized fields using Mongoid's criteria API, Mongoid will automatically alter the criteria to match the current locale. .. code-block:: ruby # Match all products with Marvelous as the description. Locale is en. Product.where(description: "Marvelous!") # The resulting MongoDB query filter: { "description.en" : "Marvelous!" } Indexing -------- If you plan to be querying extensively on localized fields, you should index each of the locales that you plan on searching on. .. code-block:: ruby class Product include Mongoid::Document field :description, localize: true index "description.de" => 1 index "description.en" => 1 end .. _read-only: Read-Only Attributes ==================== You can tell Mongoid that certain attributes are read-only. This will allow documents to be created with these attributes, but changes to them will be ignored when using mass update methods such as ``update_attributes``: .. code-block:: ruby class Band include Mongoid::Document field :name, type: String field :origin, type: String attr_readonly :name, :origin end band = Band.create(name: "Placebo") band.update_attributes(name: "Tool") # Filters out the name change. If you explicitly try to update or remove a read-only attribute by itself, a ``ReadonlyAttribute`` exception will be raised: .. code-block:: ruby band.update_attribute(:name, "Tool") # Raises the error. band.remove_attribute(:name) # Raises the error. Assignments to read-only attributes using their setters will be ignored: .. code-block:: ruby b = Band.create!(name: "The Rolling Stones") # => # b.name = "The Smashing Pumpkins" # => "The Smashing Pumpkins" b.name # => "The Rolling Stones" Calls to atomic persistence operators, like ``bit`` and ``inc``, will persist changes to readonly fields. Timestamp Fields ================ Mongoid supplies a timestamping module in ``Mongoid::Timestamps`` which can be included to get basic behavior for ``created_at`` and ``updated_at`` fields. .. code-block:: ruby class Person include Mongoid::Document include Mongoid::Timestamps end You may also choose to only have specific timestamps for creation or modification. .. code-block:: ruby class Person include Mongoid::Document include Mongoid::Timestamps::Created end class Post include Mongoid::Document include Mongoid::Timestamps::Updated end If you want to turn off timestamping for specific calls, use the timeless method: .. code-block:: ruby person.timeless.save Person.timeless.create! If you'd like shorter timestamp fields with aliases on them to save space, you can include the short versions of the modules. .. code-block:: ruby class Band include Mongoid::Document include Mongoid::Timestamps::Short # For c_at and u_at. end class Band include Mongoid::Document include Mongoid::Timestamps::Created::Short # For c_at only. end class Band include Mongoid::Document include Mongoid::Timestamps::Updated::Short # For u_at only. end .. _field-names-with-periods-and-dollar-signs: Field Names with Dots/Periods (``.``) and Dollar Signs (``$``) ============================================================== Using dots/periods (``.``) in fields names and starting a field name with a dollar sign (``$``) is not recommended, as Mongoid provides limited support for retrieving and operating on the documents stored in those fields. Both Mongoid and MongoDB query language (MQL) generally use the dot/period character (``.``) to separate field names in a field path that traverses embedded documents, and words beginning with the dollar sign (``$``) as operators. MongoDB provides `limited support `_ for using field names containing dots and starting with the dollar sign for interoperability with other software, however, due to this support being confined to specific operators (e.g. :manual:`getField `, :manual:`setField `) and requiring the usage of the aggregation pipeline for both queries and updates, applications should avoid using dots in field names and starting field names with the dollar sign if possible. Mongoid, starting in version 8, now allows users to access fields that begin with dollar signs and that contain dots/periods. They can be accessed using the ``send`` method as follows: .. code:: class User include Mongoid::Document field :"first.last", type: String field :"$_amount", type: Integer end user = User.first user.send(:"first.last") # => Mike.Trout user.send(:"$_amount") # => 42650000 It is also possible to use ``read_attribute`` to access these fields: .. code:: user.read_attribute("first.last") # => Mike.Trout Due to `server limitations `_, updating and replacing fields containing dots and dollars requires using special operators. For this reason, calling setters on these fields is prohibited and will raise an error: .. code:: class User include Mongoid::Document field :"first.last", type: String field :"$_amount", type: Integer end user = User.new user.send(:"first.last=", "Shohei.Ohtani") # raises a InvalidDotDollarAssignment error user.send(:"$_amount=", 8500000) # raises a InvalidDotDollarAssignment error