Navigation

Field Definition

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.

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
  • BigDecimal
  • Mongoid::Boolean, which may be specified simply as Boolean in the scope of a class which included Mongoid::Document.
  • Date
  • DateTime
  • Float
  • Hash
  • Integer
  • BSON::ObjectId
  • BSON::Binary
  • Range
  • Regexp
  • Set
  • String
  • Mongoid::StringifiedSymbol, which may be specified simply as StringifiedSymbol in the scope of a class which included Mongoid::Document.
  • Symbol
  • Time
  • ActiveSupport::TimeWithZone

Mongoid also recognizes the string "Boolean" as an alias for the Mongoid::Boolean class.

To define custom field types, refer to 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+.

Omitting Field Type Definition

If you decide not to specify the type of field with the definition, Mongoid will treat it as an object and not try to typecast it when sending the values to the database. This can be advantageous as the lack of attempted conversion will yield a slight performance gain. However some types are not supported if not defined as fields. You can safely omit type specifications when:

  • You’re not using a web front end and values are already properly cast.
  • All of your fields are strings.
class Person
  include Mongoid::Document
  field :first_name
  field :middle_name
  field :last_name
end

Types that are not supported as dynamic attributes since they cannot be cast are:

  • Date
  • DateTime
  • Range

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

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:

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

New applications should use the 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:

class Symbol
  def bson_type
    BSON::String::BSON_TYPE
  end
end

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.

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

Time fields store values as Time instances in the configured time zone.

Date and DateTime instances are converted to Time instances upon assignment to a Time field:

class Voter
  include Mongoid::Document

  field :registered_at, type: Time
end

Voter.new(registered_at: Date.today)
# => #<Voter _id: 5fdd80392c97a618f07ba344, registered_at: 2020-12-18 05:00:00 UTC>

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

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 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: 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 configured time zone settings.

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
 # => #<Ticket _id: 5c13d4b9026d7c4e7870bb2f, opened_at: 2018-02-18 12:00:08 UTC>

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

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 default Mongoid time zone is used:

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:

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

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:

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
# => #<BSON::Regexp::Raw:0x0000555f505e4a20 @pattern="hello.world", @options="ms">

Use #compile method on BSON::Regexp::Raw to get back the Ruby regular expression:

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

BSON::Regexp::Raw.new('hello$', 's')
# => #<BSON::Regexp::Raw:0x0000555f51441640 @pattern="hello$", @options="s">

Compiling this regular expression produces a Ruby regular expression that matches strings containing “hello” before a newline, besides strings ending in “hello”:

BSON::Regexp::Raw.new('hello$', 's').compile =~ "hello\nworld"
# => 0

This is because the meaning of $ is different between PCRE and Ruby regular expressions.

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:

    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:

    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:

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

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:

class Order
  include Mongoid::Document

  field :state, type: String, default: 'created'
end

The default value can also be specified as a Proc:

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:

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:

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:

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:

field :_id, type: String, default: -> { 'hello' }, pre_processed: true

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.

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

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:

class Band
  include Mongoid::Document

  field :name, type: String
  alias_attribute :n, :name
end

band = Band.new(n: 'Astral Projection')
# => #<Band _id: 5fc1c1ee2c97a64accbeb5e1, name: "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.

class Band
  unalias_attribute :n
end

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:

class Band
  include Mongoid::Document

  unalias_attribute :id
  field :id, type: String
end

Band.new(id: '42')
# => #<Band _id: 5fc1c3f42c97a6590684046c, 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 configuration option to true.

With the option set to true, the following example will raise an error:

class Person
  include Mongoid::Document

  field :name

  field :name, type: String
end

To define the field anyway, use the overwrite: true option:

class Person
  include Mongoid::Document

  field :name

  field :name, type: String, overwrite: true
end

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:

class Band
  include Mongoid::Document
  field :name, type: String
  field :_id, type: String, default: ->{ name }
end

It is possible to omit the default entirely:

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:

band = Band.create!
=> #<Band _id: , >
band.id
=> nil
band.reload
# raises Mongoid::Errors::DocumentNotFound
Band.last
=> #<Band _id: 5fc681c22c97a6791f324b99, >

Omitting _id fields is more common in embedded documents.

Mongoid also defines the id field aliased to _id. The id alias can be removed if desired (such as to integrate with systems that use the id field to store value different from _id.

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:

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:

user = User.new(name: [ "Mike", "Trout" ])
# => #<User _id: 62b222d43282a47bf73e3264, name: nil>

Note that the original uncastable values will be stored in the attributes_before_type_cast hash with their field names:

user.attributes_before_type_cast["name"]
# => ["Mike", "Trout"]

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:

class User
  include Mongoid::Document

  field :name, type: Integer
end
{ _id: ..., name: [ "Mike", "Trout" ] }

Reading this document from the database will result in the model’s name field containing nil:

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:

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

Mongoid offers several ways to customize the behavior of fields.

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:

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:

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

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:

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:

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.

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.

Note

The mongoize and demongoize methods should return nil on values that are uncastable to your custom type. See the section on 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.

point = Point.new(12, 24)
Venue.where(location: point) # This uses Point.evolve

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:

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

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 :required option which will add a presence validator for the field. First, declare the new field option in an initializer, specifiying its handler function as a block:

# in /config/initializers/mongoid_custom_fields.rb

Mongoid::Fields.option :required do |model, field, value|
  model.validates_presence_of field if value
end

Then, use it your model class:

class Person
  include Mongoid::Document

  field :name, type: String, required: true
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

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:

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.

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

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

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:

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.

class Product
  include Mongoid::Document
  field :description, 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.

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.

product.description_translations
# { "en" => "Marvelous!", "de" => "Fantastisch!" }
product.description_translations =
  { "en" => "Marvelous!", "de" => "Wunderbar!" }

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:

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:

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:

product = Product.new
I18n.locale = :en
product.description = "Marvelous!"
I18n.locale = :de
product.description # "Marvelous!"

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.

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

class Product
  include Mongoid::Document
  field :description, localize: true

  index "description.de" => 1
  index "description.en" => 1
end

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:

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:

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:

b = Band.create!(name: "The Rolling Stones")
# => #<Band _id: 6287a3d5d1327a5292535383, name: "The Rolling Stones", origin: nil>
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.

class Person
  include Mongoid::Document
  include Mongoid::Timestamps
end

You may also choose to only have specific timestamps for creation or modification.

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:

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.

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 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. getField, 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:

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:

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:

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