Model class names cannot end with “s”, because it will be considered as the pluralized form of the word. For example “Status” would be considered as the plural form of “Statu”, which will cause a few known problems.
ActiveSupport::Inflector.inflections do |inflect|
inflect.singular("status", "status")
end
The collection for the model’s documents can be changed at the class level if you would like them persisted elsewhere.
class Person
include Mongoid::Document
store_in collection: "citizens", database: "other", client: "secondary"
end
The store_in macro can also take lambdas – a common case for this is multi-tenant applications.
class Band
include Mongoid::Document
store_in database: ->{ Thread.current[:database] }
end
In cases where you want to set multiple field values at once, there are a few different ways of handling this as well.
person.attributes = { first_name: "Jean-Baptiste", middle_name: "Emmanuel" }
person.write_attributes(
first_name: "Jean-Baptiste",
middle_name: "Emmanuel"
)
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 :url, type: Hash
# all data will fail to save due to the illegal hashkey
def set_vals_fail
self.first_name = 'Daniel'
self.url = {'home.page' => 'http://www.homepage.com'}
save
end
end
Be wary that default values that are not defined as lambdas or procs are evaluated at class load time, so the following 2 definitions are not equivalent.
field :dob, type: Time, default: Time.now
field :dob, type: Time, default: ->{ Time.now }
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.
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" }
You can define custom types in Mongoid and determine how they are serialized and deserialized. You simply need to provide 3 methods on it for Mongoid to call to convert your object to and from MongoDB friendly values.
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.
def mongoize
[ x, y ]
end
class << self
# 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
# 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
# Converts the object that was supplied to a criteria and converts it
# into a database friendly form.
def evolve(object)
case object
when Point then object.mongoize
else object
end
end
end
end
By default Mongoid doesn’t supports dynamic fields. You can tell mongoid that you want to add dynamic fields by including Mongoid::Attributes::Dynamic in model. Mongoid::Attributes::Dynamic will allow attributes to get set and persisted on the document even if a field was not defined for them. There is a slight ‘gotcha’ however when dealing with dynamic attributes in that Mongoid is not completely lenient about the use of method_missing and breaking the public interface of the Document class.
class Person
include Mongoid::Document
include Mongoid::Attributes::Dynamic
end
Mongoid now supports localized fields without the need of any external gem.
class Product
include Mongoid::Document
field :description, localize: true
end
I18n.default_locale = :en
product = Product.new
product.description = "Marvelous!"
I18n.locale = :de
product.description = "Fantastisch!"
product.attributes
#=> { "description" => { "en" => "Marvelous!", "de" => "Fantastisch!" }
product.description_translations
#=> { "en" => "Marvelous!", "de" => "Fantastisch!" }
product.description_translations =
{ "en" => "Marvelous!", "de" => "Wunderbar!" }
When using fallbacks, Mongoid will automatically use them when a translation is not available.
config.i18n.fallbacks = true
I18n.default_locale = :en
I18n.fallbacks = true
::I18n.fallbacks[:de] = [ :de, :en, :es ]
product = Product.new
product.description = "Marvelous!"
I18n.locale = :de
product.description #=> "Marvelous!"
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. If no changes have been made, Mongoid will not hit the database on a call to Model#save.
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"
person = Person.first
person.name = "Alan Garner"
# Reset the changed name back to the original
person.reset_name!
person.name #=> "Alan Parsons"
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" ] }
You can tell Mongoid that certain attributes are readonly. This will allow documents to be created with theses attributes, but changes to them will be filtered out.
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.
band.update_attribute(:name, "Tool") # Raises the error.
band.remove_attribute(:name) # Raises the error.
Mongoid supports inheritance in both root and embedded documents. In scenarios where documents are inherited from their fields, relations, validations and scopes get copied down into their child documents, but not vise-versa.
class Canvas
include Mongoid::Document
field :name, type: String
embeds_many :shapes
end
class Shape
include Mongoid::Document
field :x, type: Integer
field :y, type: Integer
embedded_in :canvas
end
# Builds a Shape object
firefox.shapes.build({ x: 0, y: 0 })
# Builds a Circle object
firefox.shapes.build({ x: 0, y: 0 }, Circle)
# Creates a Rectangle object
firefox.shapes.create({ x: 0, y: 0 }, Rectangle)
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!
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
Insert a document or multiple documents into the database.
Person.create([
{ first_name: "Heinrich", last_name: "Heine" },
{ first_name: "Willy", last_name: "Brandt" }
])
Person.create(first_name: "Heinrich") do |doc|
doc.last_name = "Heine"
end
Can bypass validations if wanted
person.save(validate: false)
Update a single attribute, bypassing validations.
person.update_attribute(:first_name, "Jean")
Performs a MongoDB upsert on the document. If the document exists in the database, it will get overwritten with the current attributes of the document in memory. If the document does not exist in the database, it will be inserted. Note that this only runs the {before|after|around}_upsert callbacks.
person = Person.new(
first_name: "Heinrich",
last_name: "Heine"
)
person.upsert
Update the document’s updated_at timestamp, optionally with one extra provided time field. This will cascade the touch to all belongs_to relations of the document with the option set. This operation skips validations and callbacks.
person.touch
person.touch(:audited_at)
Deletes the document from the database without running callbacks.
Deletes the document from the database while running destroy callbacks.
Deletes all documents from the database without running any callbacks.
Person.delete_all
You can change at runtime where to store, query, update, or remove documents by prefixing any operation with #with.
Band.with(database: "music-non-stop").create
Band.with(collection: "artists").delete_all
band.with(client: :tertiary).save!
band = Band.new(name: "Scuba")
band.with(collection: "artists").save!
band.update_attribute(likes: 1000) # This will not save - tries the collection "bands"
band.with(collection: "artists").update_attribute(likes: 1000) # This will update the document.
If you want to switch the persistence context for all operations at runtime, but don’t want to be using with all over your code, Mongoid provides the ability to do this as the client and database level globally. The methods for this are Mongoid.override_client and Mongoid.override_database.
class BandsController < ApplicationController
before_filter :switch_database
after_filter :reset_database
private
def switch_database
I18n.locale = params[:locale] || I18n.default_locale
Mongoid.override_database("my_db_name_#{I18n.locale}")
end
def reset_database
Mongoid.override_database(nil)
end
end
If you want to drop down to the driver level to perform operations, you can grab the Mongo client or collection from the model or document instance.
client = Band.mongo_client.with(write: { w: 0 }, database: "musik")
client[:artists].find(...)
collection_w_0 = Band.collection.with(write: { w: 0 })
collection_w_0[:artists].find(...)
Mongoid does not provide a mechanism for creating capped collections on the fly
client["name", :capped => true, :size => 1024].create
db.createCollection("name", { capped: true, size: 1024 });
Band.where(name: "Photek").first_or_initialize
Find documents for a provided javascript expression. This will wrap the javascript in a `BSON::Code` object which is the safe way to avoid javascript injection attacks.*
Band.for_js("this.name = param", param: "Tool")
Same as count but caches subsequent calls to the database
Band.length
Band.where(name: "FKA Twigs").size
Eager loaded is supported on all relations with the exception of polymorphic belongs_to associations.
Band.includes(:albums).each do |band|
p band.albums.first.name # Does not hit the database again.
end
Update attributes of the first matching document.
Band.where(name: "Photek").update(label: "Mute")
Update attributes of all matching documents.
Band.where(members: 2).update_all(label: "Mute")
Named scopes can take procs and blocks for accepting parameters or extending functionality.
class Band
include Mongoid::Document
field :name, type: String
field :country, type: String
field :active, type: Boolean, default: true
scope :named, ->(name){ where(name: name) }
scope :active, ->{
where(active: true) do
def deutsch
tap |scope| do
scope.selector.store("origin" => "Deutschland")
end
end
end
}
end
Band.named("Depeche Mode") # Find Depeche Mode.
Band.active.deutsch # Find active German bands.
Default scopes can be useful when you find yourself applying the same criteria to most queries, and want something to be there by default. Default scopes take procs that return criteria objects.
class Band
include Mongoid::Document
field :name, type: String
field :active, type: Boolean, default: true
default_scope ->{ where(active: true) }
end
Band.each do |band|
# All bands here are active.
end
You can tell Mongoid not to apply the default scope by using unscoped, which can be inline or take a block.
Band.unscoped.where(name: "Depeche Mode")
Band.unscoped do
Band.where(name: "Depeche Mode")
end
Note that chaining unscoped with a scope does not work. In these cases, it is recommended that you use the block form of unscoped:
Client.unscoped {
Client.created_before(Time.zone.now)
}
You can also tell Mongoid to explicitly apply the default scope again later to always ensure it’s there.
Band.unscoped.where(name: "Depeche Mode").scoped
If you are using a default scope on a model that is part of a relation, you must reload the relation to have scoping reapplied. This is important to note if you change a value of a document in the relation that would affect its visibility within the scoped relation.
class Label
include Mongoid::Document
embeds_many :bands
end
class Band
include Mongoid::Document
field :active, default: true
embedded_in :label
default_scoped ->{ where(active: true) }
end
label.bands.push(band)
label.bands #=> [ band ]
band.update_attribute(:active, false)
label.bands #=> [ band ] Must reload.
label.reload.bands #=> []
Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.
class Band
include Mongoid::Document
field :name, type: String
field :active, type: Boolean, default: true
def self.active
where(active: true)
end
end
Band.active
Mongoid provides a DSL around MongoDB’s map/reduce framework, for performing custom map/reduce jobs or simple aggregations.
map = %Q{
function() {
emit(this.name, { likes: this.likes });
}
}
reduce = %Q{
function(key, values) {
var result = { likes: 0 };
values.forEach(function(value) {
result.likes += value.likes;
});
return result;
}
}
Band.where(:likes.gt => 100).map_reduce(map, reduce).out(inline: true)
Just like criteria, map/reduce calls are lazily evaluated. So nothing will hit the database until you iterate over the results, or make a call on the wrapper that would need to force a database hit.
Band.map_reduce(map, reduce).out(replace: "mr-results").each do |document|
p document # { "_id" => "Tool", "value" => { "likes" => 200 }}
end
- inline: 1: Don’t store the output in a collection.
- replace: "name": Store in a collection with the provided name, and overwrite any documents that exist in it.
- merge: "name": Store in a collection with the provided name, and merge the results with the existing documents.
- reduce: "name": Store in a collection with the provided name, and reduce all existing results in that collection.
All relations contain a target, which is the proxied document or documents, a base which is the document the relation hangs off, and metadata which provides information about the relation.
class Person
include Mongoid::Document
embeds_many :addresses
end
person.addresses = [ address ]
person.addresses.target # returns [ address ]
person.addresses.base # returns person
person.addresses.metadata # returns the metadata
All relations can have extensions, which provides a way to add application specific functionality to the relation. They are defined by providing a block to the relation definition.
class Person
include Mongoid::Document
embeds_many :addresses do
def find_by_country(country)
where(country: country).first
end
def chinese
@target.select { |address| address.country == "China" }
end
end
end
person.addresses.find_by_country("Mongolia") # returns address
person.addresses.chinese # returns [ address ]
It is important to note that by default, Mongoid will validate the children of any relation that are loaded into memory via a validates_associated.If you do not want this behavior, you may turn it off when defining the relation.
class Person
include Mongoid::Document
embeds_many :addresses, validate: false
has_many :posts, validate: false
end
If you want the embedded document callbacks to fire when calling a persistence operation on its parent, you will need to provide the cascade callbacks option to the relation.
class Band
include Mongoid::Document
embeds_many :albums, cascade_callbacks: true
embeds_one :label, cascade_callbacks: true
end
band.save # Fires all save callbacks on the band, albums, and label.
You can provide dependent options to referenced associations to instruct Mongoid how to handle situations where one side of the relation is deleted, or is attempted to be deleted. The options are as follows:
- :delete: Delete the child document without running any of the model callbacks.
- :destroy: Destroy the child document and run all of the model callbacks.
- :nullify: Orphan the child document.
- :restrict: Raise an error if the child is not empty.
class Band
include Mongoid::Document
has_many :albums, dependent: :delete
belongs_to :label, dependent: :nullify
end
class Album
include Mongoid::Document
belongs_to :band
end
class Label
include Mongoid::Document
has_many :bands, dependent: :restrict
end
One core difference between Mongoid and Active Record from a behavior standpoint is that Mongoid does not automatically save child relations for relational associations. This is for performance reasons.
To enable an autosave on a relational association (embedded associations do not need this since they are actually part of the parent in the database) add the autosave option to the relation.
class Band
include Mongoid::Document
has_many :albums, autosave: true
end
A document can recursively embed itself using recursively_embeds_one or recursively_embeds_many, which provides accessors for the parent and children via parent_ and child_ methods.
class Tag
include Mongoid::Document
recursively_embeds_many
end
All relations have existence predicates on them in the form of name? and has_name? to check if the relation is blank.
band.label?
band.has_label?
band.albums?
band.has_albums?
One to one relations (embeds_one, has_one) have an autobuild option which tells Mongoid to instantiate a new document when the relation is accessed and it is nil.
class Band
include Mongoid::Document
embeds_one :label, autobuild: true
has_one :producer, autobuild: true
end
Any belongs_to relation, no matter where it hangs off from, can take an optional :touch option which will call the touch method on it and any parent relations with the option defined when the base document calls #touch.
class Band
include Mongoid::Document
belongs_to :label, touch: true
end
You can create a one sided many to many if you want to mimic a has_many that stores the keys as an array on the parent.
class Band
include Mongoid::Document
belongs_to :label, touch: true
end
You can create a one sided many to many if you want to mimic a has_many that stores the keys as an array on the parent.
class Band
include Mongoid::Document
has_and_belongs_to_many :tags, inverse_of: nil
end
class Tag
include Mongoid::Document
field :name, type: String
end
Nested attributes can be enabled for any relation, embedded or referenced. To enable this for the relation, simply provide the relation name to the accepts_nested_attributes_for macro.
class Band
include Mongoid::Document
embeds_many :albums
belongs_to :producer
accepts_nested_attributes_for :albums, :producer
end
Note that when you add nested attributes functionality to a referenced relation, Mongoid will automatically enable autosave for that relation.
When a relation gains nested attributes behavior, an additional method is added to the base model, which should be used to update the attributes with the new functionality. This method is the relation name plus _attributes=
band = Band.first
band.producer_attributes = { name: "Flood" }
band.attributes = { producer_attributes: { name: "Flood" }}
Note that this will work with any attribute based setter method in Mongoid. This includes: update_attributes, update_attributes! and attributes=.
Note that using callbacks for domain logic is a bad design practice, and can lead to unexpected errors that are hard to debug when callbacks in the chain halt execution. It is our recommendation to only use them for cross-cutting concerns, like queueing up background jobs.
class Article
include Mongoid::Document
field :name, type: String
field :body, type: String
field :slug, type: String
before_create :send_message
after_save do |document|
# Handle callback here.
end
protected
def send_message
# Message sending code here.
end
end
Callbacks are coming from Active Support, so you can use the new syntax as well:
class Article
include Mongoid::Document
field :name, type: String
set_callback(:create, :before) do |document|
# Message sending code here.
end
end
Mongoid has a set of callbacks that are specific to collection based relations – these are:
- after_add
- after_remove
- before_add
- before_remove
class Person
include Mongoid::Document
has_many :posts, after_add: :send_email_to_subscribers
end
def send_email_to_subscribers(post)
Notifications.new_post(post).deliver
end
You can define indexes on documents using the index macro. Provide the key for the index along with a direction. For additional options, supply them in a second options hash parameter.
class Person
include Mongoid::Document
field :ssn
index({ ssn: 1 }, { unique: true, name: "ssn_index" })
end
You can define indexes on embedded document fields as well.
class Person
include Mongoid::Document
embeds_many :addresses
index "addresses.street" => 1
end
Sparse indexes only contain entries for documents that have the indexed field, even if the index field contains a null value. The index skips over any document that is missing the indexed field. The index is “sparse” because it does not include all documents of a collection.
class Person
include Mongoid::Document
field :ssn
index({ ssn: -1 }, { sparse: true })
end
By default, MongoDB builds indexes in the foreground, which prevents all read and write operations to the database while the index builds. Also, no operation that requires a read or write lock on all databases (e.g. listDatabases) can occur during a foreground index build.
class Person
include Mongoid::Document
field :ssn
index({ ssn: 1 }, { unique: true, background: true })
end
For unique indexes that are defined for a column that might already have duplicate values, you can drop the duplicate entries:
class Person
include Mongoid::Document
field :ssn
index({ ssn: 1 }, { unique: true, drop_dups: true })
end
Indexes can be scoped to a specific database.
class Person
include Mongoid::Document
field :ssn
index({ ssn: 1 }, { database: "users", unique: true, background: true })
end
You can have Mongoid define indexes for you on “foreign key” fields for relational associations. This only works on the relation macro that the foreign key is stored on.
class Comment
include Mongoid::Document
belongs_to :post, index: true
has_and_belongs_to_many :preferences, index: true
end
When you want to create the indexes in the database, use the provided rake task.
$ rake db:mongoid:create_indexes
Mongoid also provides a rake task to delete all secondary indexes.
$ rake db:mongoid:remove_indexes
In order to properly set up single collection inheritance, Mongoid needs to preload all models before every request in development mode. This can get slow, so if you are not using any inheritance it is recommended you turn this feature off.
config.mongoid.preload_models = false
Mongoid provides the following rake tasks when used in a Rails 3 environment:
- db:create: Exists only for dependency purposes, does not actually do anything.
- db:create_indexes: Reads all index definitions from the models and attempts to create them in the database.
- db:remove_indexes: Reads all secondary index definitions from the models.
- db:drop: Drops all collections in the database with the exception of the system collections.
- db:migrate: Exists only for dependency purposes, does not actually do anything.
- db:purge: Deletes all data, including indexes, from the database. Since 3.1.0
- db:schema:load: Exists only for framework dependency purposes, does not actually do anything.
- db:seed: Seeds the database from db/seeds.rb
- db:setup: Creates indexes and seeds the database.
- db:test:prepare: Exists only for framework dependency purposes, does not actually do anything.
find_and_modify has been removed and replaced with 3 options: find_one_and_update, find_one_and_delete and find_one_and_replace.