Most apps will only use this one instance.
show codefunction Mongoose () {
this.connections = [];
this.plugins = [];
this.models = {};
this.modelSchemas = {};
// default global options
this.options = {
pluralization: true
};
var conn = this.createConnection(); // default connection
conn.models = this.models;
};
The Mongoose constructor
The exports of the mongoose module is an instance of this class.
var mongoose = require('mongoose');
var mongoose2 = new mongoose.Mongoose();
The Mongoose Schema constructor
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CatSchema = new Schema(..);
schema
)Applies global plugins to schema
.
schema
<Schema> Mongoose.prototype._applyPlugins = function (schema) {
for (var i = 0, l = this.plugins.length; i < l; i++) {
schema.plugin(this.plugins[i][0], this.plugins[i][1]);
}
}
uri
)Checks if ?replicaSet query parameter is specified in URI
uri
<String> checkReplicaSetInUri('localhost:27000?replicaSet=rs0'); // true
uri(s)
, [options]
, [callback]
)Opens the default mongoose connection.
If arguments are passed, they are proxied to either Connection#open or Connection#openSet appropriately.
Options passed take precedence over options included in connection strings.
mongoose.connect('mongodb://user:pass@localhost:port/database');
// replica sets
var uri = 'mongodb://user:pass@localhost:port/database,mongodb://anotherhost:port,mongodb://yetanother:port';
mongoose.connect(uri);
// with options
mongoose.connect(uri, options);
// connecting to multiple mongos
var uri = 'mongodb://hostA:27501,hostB:27501';
var opts = { mongos: true };
mongoose.connect(uri, opts);
Mongoose.prototype.connect = function() {
var conn = this.connection;
if (rgxReplSet.test(arguments[0]) || checkReplicaSetInUri(arguments[0])) {
conn.openSet.apply(conn, arguments);
} else {
conn.open.apply(conn, arguments);
}
return this;
};
[uri]
, [options]
)Creates a Connection instance.
Each connection
instance maps to a single database. This method is helpful when mangaging multiple db connections.
If arguments are passed, they are proxied to either Connection#open or Connection#openSet appropriately. This means we can pass db
, server
, and replset
options to the driver. Note that the safe
option specified in your schema will overwrite the safe
db option specified here unless you set your schemas safe
option to undefined
. See this for more information.
Options passed take precedence over options included in connection strings.
// with mongodb:// URI
db = mongoose.createConnection('mongodb://user:pass@localhost:port/database');
// and options
var opts = { db: { native_parser: true }}
db = mongoose.createConnection('mongodb://user:pass@localhost:port/database', opts);
// replica sets
db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database');
// and options
var opts = { replset: { strategy: 'ping', rs_name: 'testSet' }}
db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database', opts);
// with [host, database_name[, port] signature
db = mongoose.createConnection('localhost', 'database', port)
// and options
var opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' }
db = mongoose.createConnection('localhost', 'database', port, opts)
// initialize now, connect later
db = mongoose.createConnection();
db.open('localhost', 'database', port, [opts]);
Mongoose.prototype.createConnection = function () {
var conn = new Connection(this);
this.connections.push(conn);
if (arguments.length) {
if (rgxReplSet.test(arguments[0]) || checkReplicaSetInUri(arguments[0])) {
conn.openSet.apply(conn, arguments);
} else {
conn.open.apply(conn, arguments);
}
}
return conn;
};
[fn]
)Disconnects all connections.
[fn]
<Function> called after all connection close.Mongoose.prototype.disconnect = function (fn) {
var count = this.connections.length
, error
this.connections.forEach(function(conn){
conn.close(function(err){
if (error) return;
if (err) {
error = err;
if (fn) return fn(err);
throw err;
}
if (fn)
--count || fn();
});
});
return this;
};
key
)Gets mongoose options
key
<String> mongoose.get('test') // returns the 'test' value
name
, [schema]
, [collection]
, [skipInit]
)Defines a model or retrieves it.
Models defined on the mongoose
instance are available to all connection created by the same mongoose
instance.
var mongoose = require('mongoose');
// define an Actor model with this mongoose instance
mongoose.model('Actor', new Schema({ name: String }));
// create a new connection
var conn = mongoose.createConnection(..);
// retrieve the Actor model
var Actor = conn.model('Actor');
When no collection
argument is passed, Mongoose produces a collection name by passing the model name
to the utils.toCollectionName method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option.
var schema = new Schema({ name: String }, { collection: 'actor' });
// or
schema.set('collection', 'actor');
// or
var collectionName = 'actor'
var M = mongoose.model('Actor', schema, collectionName)
Mongoose.prototype.model = function (name, schema, collection, skipInit) {
if ('string' == typeof schema) {
collection = schema;
schema = false;
}
if (utils.isObject(schema) && !(schema instanceof Schema)) {
schema = new Schema(schema);
}
if ('boolean' === typeof collection) {
skipInit = collection;
collection = null;
}
// handle internal options from connection.model()
var options;
if (skipInit && utils.isObject(skipInit)) {
options = skipInit;
skipInit = true;
} else {
options = {};
}
// look up schema for the collection.
if (!this.modelSchemas[name]) {
if (schema) {
// cache it so we only apply plugins once
this.modelSchemas[name] = schema;
this._applyPlugins(schema);
} else {
throw new mongoose.Error.MissingSchemaError(name);
}
}
var model;
var sub;
// connection.model() may be passing a different schema for
// an existing model name. in this case don't read from cache.
if (this.models[name] && false !== options.cache) {
if (schema instanceof Schema && schema != this.models[name].schema) {
throw new mongoose.Error.OverwriteModelError(name);
}
if (collection) {
// subclass current model with alternate collection
model = this.models[name];
schema = model.prototype.schema;
sub = model.__subclass(this.connection, schema, collection);
// do not cache the sub model
return sub;
}
return this.models[name];
}
// ensure a schema exists
if (!schema) {
schema = this.modelSchemas[name];
if (!schema) {
throw new mongoose.Error.MissingSchemaError(name);
}
}
// Apply relevant "global" options to the schema
if (!('pluralization' in schema.options)) schema.options.pluralization = this.options.pluralization;
if (!collection) {
collection = schema.get('collection') || format(name, schema.options);
}
var connection = options.connection || this.connection;
model = Model.compile(name, schema, collection, connection, this);
if (!skipInit) {
model.init();
}
if (false === options.cache) {
return model;
}
return this.models[name] = model;
}
Returns an array of model names created on this instance of Mongoose.
Does not include names of models created using connection.model()
.
Mongoose.prototype.modelNames = function () {
var names = Object.keys(this.models);
return names;
}
fn
, [opts]
)Declares a global plugin executed on all Schemas.
Equivalent to calling .plugin(fn)
on each Schema you create.
Mongoose.prototype.plugin = function (fn, opts) {
this.plugins.push([fn, opts]);
return this;
};
key
, value
)Sets mongoose options
mongoose.set('test', value) // sets the 'test' option to `value`
mongoose.set('debug', true) // enable logging collection methods + arguments to the console
Mongoose.prototype.set = function (key, value) {
if (arguments.length == 1) {
return this.options[key];
}
this.options[key] = value;
return this;
};
The various Mongoose SchemaTypes.
Alias of mongoose.Schema.Types for backwards compatibility.
show codeMongoose.prototype.SchemaTypes = Schema.Types;
The various Mongoose Types.
var mongoose = require('mongoose');
var array = mongoose.Types.Array;
Using this exposed access to the ObjectId
type, we can construct ids on demand.
var ObjectId = mongoose.Types.ObjectId;
var id1 = new ObjectId;
show codeMongoose.prototype.Types = Types;
The default connection of the mongoose module.
var mongoose = require('mongoose');
mongoose.connect(...);
mongoose.connection.on('error', cb);
This is the connection used by default for every model created using mongoose.model.
show codeMongoose.prototype.__defineGetter__('connection', function(){
return this.connections[0];
});
The node-mongodb-native driver Mongoose uses.
show codeMongoose.prototype.mongo = require('mongodb');
The mquery query builder Mongoose uses.
show codeMongoose.prototype.mquery = require('mquery');
base
)Connection constructor
base
<Mongoose> a mongoose instanceconnecting
: Emitted when connection.{open,openSet}()
is executed on this connection.
connected
: Emitted when this connection successfully connects to the db. May be emitted multiple times in reconnected
scenarios.
open
: Emitted after we connected
and onOpen
is executed on all of this connections models.
disconnecting
: Emitted when connection.close()
was executed.
disconnected
: Emitted after getting disconnected from the db.
close
: Emitted after we disconnected
and onClose
executed on all of this connections models.
reconnected
: Emitted after we connected
and subsequently disconnected
, followed by successfully another successfull connection.
error
: Emitted when an error occurs on this connection.
fullsetup
: Emitted in a replica-set scenario, when all nodes specified in the connection string are connected.
For practical reasons, a Connection equals a Db.
function Connection (base) {
this.base = base;
this.collections = {};
this.models = {};
this.config = {autoIndex: true};
this.replica = false;
this.hosts = null;
this.host = null;
this.port = null;
this.user = null;
this.pass = null;
this.name = null;
this.options = null;
this.otherDbs = [];
this._readyState = STATES.disconnected;
this._closeCalled = false;
this._hasOpened = false;
};
callback
)Handles opening the connection with the appropriate method based on connection type.
callback
<Function> Connection.prototype._open = function (callback) {
this.readyState = STATES.connecting;
this._closeCalled = false;
var self = this;
var method = this.replica
? 'doOpenSet'
: 'doOpen';
// open connection
this[method](function (err) {
if (err) {
self.readyState = STATES.disconnected;
if (self._hasOpened) {
if (callback) callback(err);
} else {
self.error(err, callback);
}
return;
}
self.onOpen(callback);
});
}
[callback]
)Closes the connection
[callback]
<Function> optionalConnection.prototype.close = function (callback) {
var self = this;
this._closeCalled = true;
switch (this.readyState){
case 0: // disconnected
callback && callback();
break;
case 1: // connected
case 4: // unauthorized
this.readyState = STATES.disconnecting;
this.doClose(function(err){
if (err){
self.error(err, callback);
} else {
self.onClose();
callback && callback();
}
});
break;
case 2: // connecting
this.once('open', function(){
self.close(callback);
});
break;
case 3: // disconnecting
if (!callback) break;
this.once('close', function () {
callback();
});
break;
}
return this;
};
name
, [options]
)Retrieves a collection, creating it if not cached.
Not typically needed by applications. Just talk to your collection through your model.
Connection.prototype.collection = function (name, options) {
if (!(name in this.collections))
this.collections[name] = new Collection(name, this, options);
return this.collections[name];
};
err
, callback
)error
Graceful error handling, passes error to callback
if available, else emits error on the connection.
Connection.prototype.error = function (err, callback) {
if (callback) return callback(err);
this.emit('error', err);
}
name
, [schema]
, [collection]
)Defines or retrieves a model.
var mongoose = require('mongoose');
var db = mongoose.createConnection(..);
db.model('Venue', new Schema(..));
var Ticket = db.model('Ticket', new Schema(..));
var Venue = db.model('Venue');
When no collection
argument is passed, Mongoose produces a collection name by passing the model name
to the utils.toCollectionName method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option.
var schema = new Schema({ name: String }, { collection: 'actor' });
// or
schema.set('collection', 'actor');
// or
var collectionName = 'actor'
var M = conn.model('Actor', schema, collectionName)
Connection.prototype.model = function (name, schema, collection) {
// collection name discovery
if ('string' == typeof schema) {
collection = schema;
schema = false;
}
if (utils.isObject(schema) && !(schema instanceof Schema)) {
schema = new Schema(schema);
}
if (this.models[name] && !collection) {
// model exists but we are not subclassing with custom collection
if (schema instanceof Schema && schema != this.models[name].schema) {
throw new MongooseError.OverwriteModelError(name);
}
return this.models[name];
}
var opts = { cache: false, connection: this }
var model;
if (schema instanceof Schema) {
// compile a model
model = this.base.model(name, schema, collection, opts)
// only the first model with this name is cached to allow
// for one-offs with custom collection names etc.
if (!this.models[name]) {
this.models[name] = model;
}
model.init();
return model;
}
if (this.models[name] && collection) {
// subclassing current model with alternate collection
model = this.models[name];
schema = model.prototype.schema;
var sub = model.__subclass(this, schema, collection);
// do not cache the sub model
return sub;
}
// lookup model in mongoose module
model = this.base.models[name];
if (!model) {
throw new MongooseError.MissingSchemaError(name);
}
if (this == model.prototype.db
&& (!collection || collection == model.collection.name)) {
// model already uses this connection.
// only the first model with this name is cached to allow
// for one-offs with custom collection names etc.
if (!this.models[name]) {
this.models[name] = model;
}
return model;
}
return this.models[name] = model.__subclass(this, schema, collection);
};
Returns an array of model names created on this connection.
Connection.prototype.modelNames = function () {
return Object.keys(this.models);
};
Called when the connection closes
show codeConnection.prototype.onClose = function () {
this.readyState = STATES.disconnected;
// avoid having the collection subscribe to our event emitter
// to prevent 0.3 warning
for (var i in this.collections)
this.collections[i].onClose();
this.emit('close');
};
Called when the connection is opened
show codeConnection.prototype.onOpen = function (callback) {
var self = this;
function open(err, isAuth) {
if (err) {
self.readyState = isAuth ? STATES.unauthorized : STATES.disconnected;
if (self._hasOpened) {
if (callback) callback(err);
} else {
self.error(err, callback);
}
return;
}
self.readyState = STATES.connected;
// avoid having the collection subscribe to our event emitter
// to prevent 0.3 warning
for (var i in self.collections)
self.collections[i].onOpen();
callback && callback();
self.emit('open');
};
// re-authenticate
if (self.user && self.pass) {
self.db.authenticate(self.user, self.pass, self.options.auth, function(err) {
open(err, true);
});
} else {
open();
}
};
connection_string
, [database]
, [port]
, [options]
, [callback]
)Opens the connection to MongoDB.
options
is a hash with the following possible properties:
config - passed to the connection config instance
db - passed to the connection db instance
server - passed to the connection server instance(s)
replset - passed to the connection ReplSet instance
user - username for authentication
pass - password for authentication
auth - options for authentication (see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate)
Mongoose forces the db option forceServerObjectId
false and cannot be overridden.
Mongoose defaults the server auto_reconnect
options to true which can be overridden.
See the node-mongodb-native driver instance for options that it understands.
Options passed take precedence over options included in connection strings.
Connection.prototype.open = function (host, database, port, options, callback) {
var self = this
, parsed
, uri;
if ('string' === typeof database) {
switch (arguments.length) {
case 2:
port = 27017;
case 3:
switch (typeof port) {
case 'function':
callback = port, port = 27017;
break;
case 'object':
options = port, port = 27017;
break;
}
break;
case 4:
if ('function' === typeof options)
callback = options, options = {};
}
} else {
switch (typeof database) {
case 'function':
callback = database, database = undefined;
break;
case 'object':
options = database;
database = undefined;
callback = port;
break;
}
if (!rgxProtocol.test(host)) {
host = 'mongodb://' + host;
}
try {
parsed = muri(host);
} catch (err) {
this.error(err, callback);
return this;
}
database = parsed.db;
host = parsed.hosts[0].host || parsed.hosts[0].ipc;
port = parsed.hosts[0].port || 27017;
}
this.options = this.parseOptions(options, parsed && parsed.options);
// make sure we can open
if (STATES.disconnected !== this.readyState) {
var err = new Error('Trying to open unclosed connection.');
err.state = this.readyState;
this.error(err, callback);
return this;
}
if (!host) {
this.error(new Error('Missing hostname.'), callback);
return this;
}
if (!database) {
this.error(new Error('Missing database name.'), callback);
return this;
}
// authentication
if (options && options.user && options.pass) {
this.user = options.user;
this.pass = options.pass;
} else if (parsed && parsed.auth) {
this.user = parsed.auth.user;
this.pass = parsed.auth.pass;
// Check hostname for user/pass
} else if (/@/.test(host) && /:/.test(host.split('@')[0])) {
host = host.split('@');
var auth = host.shift().split(':');
host = host.pop();
this.user = auth[0];
this.pass = auth[1];
} else {
this.user = this.pass = undefined;
}
// global configuration options
if (options && options.config) {
if (options.config.autoIndex === false){
this.config.autoIndex = false;
}
else {
this.config.autoIndex = true;
}
}
this.name = database;
this.host = host;
this.port = port;
this._open(callback);
return this;
};
uris
, [database]
, [options]
, [callback]
)Opens the connection to a replica set.
var db = mongoose.createConnection();
db.openSet("mongodb://user:pwd@localhost:27020/testing,mongodb://example.com:27020,mongodb://localhost:27019");
The database name and/or auth need only be included in one URI.
The options
is a hash which is passed to the internal driver connection object.
Valid options
db - passed to the connection db instance
server - passed to the connection server instance(s)
replset - passed to the connection ReplSetServer instance
user - username for authentication
pass - password for authentication
auth - options for authentication (see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate)
mongos - Boolean - if true, enables High Availability support for mongos
Options passed take precedence over options included in connection strings.
If connecting to multiple mongos servers, set the mongos
option to true.
conn.open('mongodb://mongosA:27501,mongosB:27501', { mongos: true }, cb);
Mongoose forces the db option forceServerObjectId
false and cannot be overridden.
Mongoose defaults the server auto_reconnect
options to true which can be overridden.
See the node-mongodb-native driver instance for options that it understands.
Options passed take precedence over options included in connection strings.
Connection.prototype.openSet = function (uris, database, options, callback) {
if (!rgxProtocol.test(uris)) {
uris = 'mongodb://' + uris;
}
var self = this;
switch (arguments.length) {
case 3:
switch (typeof database) {
case 'string':
this.name = database;
break;
case 'object':
callback = options;
options = database;
database = null;
break;
}
if ('function' === typeof options) {
callback = options;
options = {};
}
break;
case 2:
switch (typeof database) {
case 'string':
this.name = database;
break;
case 'function':
callback = database, database = null;
break;
case 'object':
options = database, database = null;
break;
}
}
var parsed;
try {
parsed = muri(uris);
} catch (err) {
this.error(err, callback);
return this;
}
if (!this.name) {
this.name = parsed.db;
}
this.hosts = parsed.hosts;
this.options = this.parseOptions(options, parsed && parsed.options);
this.replica = true;
if (!this.name) {
this.error(new Error('No database name provided for replica set'), callback);
return this;
}
// authentication
if (options && options.user && options.pass) {
this.user = options.user;
this.pass = options.pass;
} else if (parsed && parsed.auth) {
this.user = parsed.auth.user;
this.pass = parsed.auth.pass;
} else {
this.user = this.pass = undefined;
}
// global configuration options
if (options && options.config) {
if (options.config.autoIndex === false){
this.config.autoIndex = false;
}
else {
this.config.autoIndex = true;
}
}
this._open(callback);
return this;
};
A hash of the collections associated with this connection
show codeConnection.prototype.collections;
The mongodb.Db instance, set when the connection is opened
show codeConnection.prototype.db;
A hash of the global options that are associated with this connection
show codeConnection.prototype.config;
Connection ready state
Each state change emits its associated event name.
conn.on('connected', callback);
conn.on('disconnected', callback);
show codeObject.defineProperty(Connection.prototype, 'readyState', {
get: function(){ return this._readyState; }
, set: function (val) {
if (!(val in STATES)) {
throw new Error('Invalid connection state: ' + val);
}
if (this._readyState !== val) {
this._readyState = val;
// loop over the otherDbs on this connection and change their state
for (var i=0; i < this.otherDbs.length; i++) {
this.otherDbs[i].readyState = val;
}
if (STATES.connected === val)
this._hasOpened = true;
this.emit(STATES[val]);
}
}
});
The default built-in validator error messages. These may be customized.
// customize within each schema or globally like so
var mongoose = require('mongoose');
mongoose.Error.messages.String.enum = "Your custom message for {PATH}.";
As you might have noticed, error messages support basic templating
{PATH}
is replaced with the invalid document path{VALUE}
is replaced with the invalid value{TYPE}
is replaced with the validator type such as "regexp", "min", or "user defined"{MIN}
is replaced with the declared min value for the Number.min validator{MAX}
is replaced with the declared max value for the Number.max validatorClick the "show code" link below to see all defaults.
show codevar msg = module.exports = exports = {};
msg.general = {};
msg.general.default = "Validator failed for path `{PATH}` with value `{VALUE}`";
msg.general.required = "Path `{PATH}` is required.";
msg.Number = {};
msg.Number.min = "Path `{PATH}` ({VALUE}) is less than minimum allowed value ({MIN}).";
msg.Number.max = "Path `{PATH}` ({VALUE}) is more than maximum allowed value ({MAX}).";
msg.Date = {};
msg.Date.min = "Path `{PATH}` ({VALUE}) is before minimum allowed value ({MIN}).";
msg.Date.max = "Path `{PATH}` ({VALUE}) is after maximum allowed value ({MAX}).";
msg.String = {};
msg.String.enum = "`{VALUE}` is not a valid enum value for path `{PATH}`.";
msg.String.match = "Path `{PATH}` is invalid ({VALUE}).";
msg.String.minlength = "Path `{PATH}` (`{VALUE}`) is shorter than the minimum allowed length ({MINLENGTH}).";
msg.String.maxlength = "Path `{PATH}` (`{VALUE}`) is longer than the maximum allowed length ({MAXLENGTH}).";
Version Error constructor.
function VersionError () {
MongooseError.call(this, 'No matching document found.');
Error.captureStackTrace && Error.captureStackTrace(this, arguments.callee);
this.name = 'VersionError';
};
type
, value
)Casting Error constructor.
function CastError (type, value, path) {
MongooseError.call(this, 'Cast to ' + type + ' failed for value "' + value + '" at path "' + path + '"');
Error.captureStackTrace && Error.captureStackTrace(this, arguments.callee);
this.name = 'CastError';
this.kind = type;
this.value = value;
this.path = path;
};
instance
)Document Validation Error
instance
<Document> function ValidationError (instance) {
if (instance && instance.constructor.name === 'model') {
MongooseError.call(this, instance.constructor.modelName + " validation failed");
} else {
MongooseError.call(this, "Validation failed");
}
Error.captureStackTrace && Error.captureStackTrace(this, arguments.callee);
this.name = 'ValidationError';
this.errors = {};
if (instance) {
instance.errors = this.errors;
}
}
Console.log helper
show codeValidationError.prototype.toString = function () {
var ret = this.name + ': ';
var msgs = [];
Object.keys(this.errors).forEach(function (key) {
if (this == this.errors[key]) return;
msgs.push(String(this.errors[key]));
}, this);
return ret + msgs.join(', ');
};
properties
)Schema validator error
properties
<Object> function ValidatorError (properties) {
var msg = properties.message;
if (!msg) {
msg = errorMessages.general.default;
}
this.properties = properties;
var message = this.formatMessage(msg, properties);
MongooseError.call(this, message);
Error.captureStackTrace && Error.captureStackTrace(this, arguments.callee);
this.name = 'ValidatorError';
this.kind = properties.type;
this.path = properties.path;
this.value = properties.value;
};
A node-mongodb-native connection implementation.
function NativeConnection() {
MongooseConnection.apply(this, arguments);
this._listening = false;
};
fn
)Closes the connection
fn
<Function> NativeConnection.prototype.doClose = function (fn) {
this.db.close();
if (fn) fn();
return this;
}
fn
)Opens the connection to MongoDB.
fn
<Function> NativeConnection.prototype.doOpen = function (fn) {
if (this.db) {
mute(this);
}
var server = new Server(this.host, this.port, this.options.server);
this.db = new Db(this.name, server, this.options.db);
var self = this;
this.db.open(function (err) {
if (err) return fn(err);
listen(self);
fn();
});
return this;
};
fn
)Opens a connection to a MongoDB ReplicaSet.
fn
<Function> See description of doOpen for server options. In this case options.replset
is also passed to ReplSetServers.
NativeConnection.prototype.doOpenSet = function (fn) {
if (this.db) {
mute(this);
}
var servers = []
, self = this;
this.hosts.forEach(function (server) {
var host = server.host || server.ipc;
var port = server.port || 27017;
servers.push(new Server(host, port, self.options.server));
})
var server = this.options.mongos
? new Mongos(servers, this.options.mongos)
: new ReplSetServers(servers, this.options.replset);
this.db = new Db(this.name, server, this.options.db);
this.db.on('fullsetup', function () {
self.emit('fullsetup')
});
this.db.open(function (err) {
if (err) return fn(err);
fn();
listen(self);
});
return this;
};
passed
, [connStrOptions]
)Prepares default connection options for the node-mongodb-native driver.
NOTE: passed
options take precedence over connection string options.
NativeConnection.prototype.parseOptions = function (passed, connStrOpts) {
var o = passed || {};
o.db || (o.db = {});
o.auth || (o.auth = {});
o.server || (o.server = {});
o.replset || (o.replset = {});
o.server.socketOptions || (o.server.socketOptions = {});
o.replset.socketOptions || (o.replset.socketOptions = {});
var opts = connStrOpts || {};
Object.keys(opts).forEach(function (name) {
switch (name) {
case 'ssl':
case 'poolSize':
if ('undefined' == typeof o.server[name]) {
o.server[name] = o.replset[name] = opts[name];
}
break;
case 'slaveOk':
if ('undefined' == typeof o.server.slave_ok) {
o.server.slave_ok = opts[name];
}
break;
case 'autoReconnect':
if ('undefined' == typeof o.server.auto_reconnect) {
o.server.auto_reconnect = opts[name];
}
break;
case 'socketTimeoutMS':
case 'connectTimeoutMS':
if ('undefined' == typeof o.server.socketOptions[name]) {
o.server.socketOptions[name] = o.replset.socketOptions[name] = opts[name];
}
break;
case 'authdb':
if ('undefined' == typeof o.auth.authdb) {
o.auth.authdb = opts[name];
}
break;
case 'authSource':
if ('undefined' == typeof o.auth.authSource) {
o.auth.authSource = opts[name];
}
break;
case 'retries':
case 'reconnectWait':
case 'rs_name':
if ('undefined' == typeof o.replset[name]) {
o.replset[name] = opts[name];
}
break;
case 'replicaSet':
if ('undefined' == typeof o.replset.rs_name) {
o.replset.rs_name = opts[name];
}
break;
case 'readSecondary':
if ('undefined' == typeof o.replset.read_secondary) {
o.replset.read_secondary = opts[name];
}
break;
case 'nativeParser':
if ('undefined' == typeof o.db.native_parser) {
o.db.native_parser = opts[name];
}
break;
case 'w':
case 'safe':
case 'fsync':
case 'journal':
case 'wtimeoutMS':
if ('undefined' == typeof o.db[name]) {
o.db[name] = opts[name];
}
break;
case 'readPreference':
if ('undefined' == typeof o.db.read_preference) {
o.db.read_preference = opts[name];
}
break;
case 'readPreferenceTags':
if ('undefined' == typeof o.db.read_preference_tags) {
o.db.read_preference_tags = opts[name];
}
break;
}
})
if (!('auto_reconnect' in o.server)) {
o.server.auto_reconnect = true;
}
if (!o.db.read_preference) {
// read from primaries by default
o.db.read_preference = 'primary';
}
// mongoose creates its own ObjectIds
o.db.forceServerObjectId = false;
// default safe using new nomenclature
if (!('journal' in o.db || 'j' in o.db ||
'fsync' in o.db || 'safe' in o.db || 'w' in o.db)) {
o.db.w = 1;
}
validate(o);
return o;
}
name
)Switches to a different database using the same connection pool.
name
<String> The database nameReturns a new connection object, with the new db.
NativeConnection.prototype.useDb = function (name) {
// we have to manually copy all of the attributes...
var newConn = new this.constructor();
newConn.name = name;
newConn.base = this.base;
newConn.collections = {};
newConn.models = {};
newConn.replica = this.replica;
newConn.hosts = this.hosts;
newConn.host = this.host;
newConn.port = this.port;
newConn.user = this.user;
newConn.pass = this.pass;
newConn.options = this.options;
newConn._readyState = this._readyState;
newConn._closeCalled = this._closeCalled;
newConn._hasOpened = this._hasOpened;
newConn._listening = false;
// First, when we create another db object, we are not guaranteed to have a
// db object to work with. So, in the case where we have a db object and it
// is connected, we can just proceed with setting everything up. However, if
// we do not have a db or the state is not connected, then we need to wait on
// the 'open' event of the connection before doing the rest of the setup
// the 'connected' event is the first time we'll have access to the db object
var self = this;
if (this.db && this._readyState === STATES.connected) {
wireup();
} else {
this.once('connected', wireup);
}
function wireup () {
newConn.db = self.db.db(name);
newConn.onOpen();
// setup the events appropriately
listen(newConn);
}
newConn.name = name;
// push onto the otherDbs stack, this is used when state changes
this.otherDbs.push(newConn);
newConn.otherDbs.push(this);
return newConn;
};
Expose the possible connection states.
show codeNativeConnection.STATES = STATES;
A node-mongodb-native collection implementation.
All methods methods from the node-mongodb-native driver are copied and wrapped in queue management.
function NativeCollection () {
this.collection = null;
MongooseCollection.apply(this, arguments);
}
callback
)Retreives information about this collections indexes.
callback
<Function> Called when the connection closes
show codeNativeCollection.prototype.onClose = function () {
MongooseCollection.prototype.onClose.call(this);
};
Called when the connection opens.
show codeNativeCollection.prototype.onOpen = function () {
var self = this;
// always get a new collection in case the user changed host:port
// of parent db instance when re-opening the connection.
if (!self.opts.capped.size) {
// non-capped
return self.conn.db.collection(self.name, callback);
}
// capped
return self.conn.db.collection(self.name, function (err, c) {
if (err) return callback(err);
// discover if this collection exists and if it is capped
self.conn.db.listCollections({ name: self.name }).toArray(function(err, docs) {
if (err) {
return callback(err);
}
var doc = docs[0];
var exists = !!doc;
if (exists) {
if (doc.options && doc.options.capped) {
callback(null, c);
} else {
var msg = 'A non-capped collection exists with the name: '+ self.name +'
'
+ ' To use this collection as a capped collection, please '
+ 'first convert it.
'
+ ' http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-Convertingacollectiontocapped'
err = new Error(msg);
callback(err);
}
} else {
// create
var opts = utils.clone(self.opts.capped);
opts.capped = true;
self.conn.db.createCollection(self.name, opts, callback);
}
});
});
function callback (err, collection) {
if (err) {
// likely a strict mode error
self.conn.emit('error', err);
} else {
self.collection = collection;
MongooseCollection.prototype.onOpen.call(self);
}
};
};
[path]
)Returns the full path to this document.
[path]
<String> Handle generic save stuff.
to solve #1446 use use hierarchy instead of hooks
Handles the actual setting of the value and marking the path modified if appropriate.
schema
)Assigns/compiles schema
into this documents prototype.
schema
<Schema> Stores the current values of the shard keys.
Shard key values do not / are not allowed to change.
obj
, [fields]
, [skipId]
)Document constructor.
init
: Emitted on a document after it has was retreived from the db and fully hydrated by Mongoose.
save
: Emitted when the document is successfully saved
function Document (obj, fields, skipId) {
this.$__ = new InternalCache;
this.$__.emitter = new EventEmitter();
this.isNew = true;
this.errors = undefined;
var schema = this.schema;
if ('boolean' === typeof fields) {
this.$__.strictMode = fields;
fields = undefined;
} else {
this.$__.strictMode = schema.options && schema.options.strict;
this.$__.selected = fields;
}
var required = schema.requiredPaths();
for (var i = 0; i < required.length; ++i) {
this.$__.activePaths.require(required[i]);
}
this.$__.emitter.setMaxListeners(0);
this._doc = this.$__buildDoc(obj, fields, skipId);
if (obj) {
this.set(obj, undefined, true);
}
if (!schema.options.strict && obj) {
var self = this
, keys = Object.keys(this._doc);
keys.forEach(function(key) {
if (!(key in schema.tree)) {
define(key, null, self);
}
});
}
this.$__registerHooksFromSchema();
}
doc
)Returns true if the Document stores the same data as doc.
doc
<Document> a document to compareDocuments are considered equal when they have matching _id
s, unless neither
document has an _id
, in which case this function falls back to usingdeepEqual()
.
Document.prototype.equals = function (doc) {
var tid = this.get('_id');
var docid = doc.get('_id');
if (!tid && !docid) {
return deepEqual(this, doc);
}
return tid && tid.equals
? tid.equals(docid)
: tid === docid;
};
Explicitly executes population and returns a promise. Useful for ES6
integration.
var promise = doc.
populate('company').
populate({
path: 'notes',
match: /airline/,
select: 'text',
model: 'modelName'
options: opts
}).
execPopulate();
// summary
doc.execPopulate()
Population does not occur unless a callback
is passed.
Passing the same path a second time will overwrite the previous path options.
See Model.populate() for explaination of options.
Document.prototype.execPopulate = function() {
var promise = new Promise;
var _this = this;
this.populate(function(error) {
if (error) {
return promise.reject(error);
}
promise.fulfill(_this);
});
return promise;
};
path
, [type]
)Returns the value of a path.
// path
doc.get('age') // 47
// dynamic casting to a string
doc.get('age', String) // "47"
Document.prototype.get = function (path, type) {
var adhoc;
if (type) {
adhoc = Schema.interpretAsType(path, type);
}
var schema = this.$__path(path) || this.schema.virtualpath(path)
, pieces = path.split('.')
, obj = this._doc;
for (var i = 0, l = pieces.length; i < l; i++) {
obj = undefined === obj || null === obj
? undefined
: obj[pieces[i]];
}
if (adhoc) {
obj = adhoc.cast(obj);
}
if (schema) {
obj = schema.applyGetters(obj, this);
}
return obj;
};
path
)Gets a raw value from a path (no getters)
path
<String> Document.prototype.getValue = function (path) {
return utils.getValue(path, this._doc);
}
doc
, fn
)Initializes the document without setters or marking anything modified.
Called internally after a document is returned from mongodb.
Document.prototype.init = function (doc, opts, fn) {
// do not prefix this method with $__ since its
// used by public hooks
if ('function' == typeof opts) {
fn = opts;
opts = null;
}
this.isNew = false;
// handle docs with populated paths
// If doc._id is not null or undefined
if (doc._id != null && opts && opts.populated && opts.populated.length) {
var id = String(doc._id);
for (var i = 0; i < opts.populated.length; ++i) {
var item = opts.populated[i];
this.populated(item.path, item._docs[id], item);
}
}
init(this, doc, this._doc);
this.$__storeShard();
this.emit('init', this);
if (fn) fn(null);
return this;
};
Helper for console.log
show codeDocument.prototype.inspect = function (options) {
var opts = options && 'Object' == utils.getFunctionName(options.constructor) ? options : {};
opts.minimize = false;
return inspect(this.toObject(opts));
};
path
, errorMsg
, value
)Marks a path as invalid, causing validation to fail.
The errorMsg
argument will become the message of the ValidationError
.
The value
argument (if passed) will be available through the ValidationError.value
property.
doc.invalidate('size', 'must be less than 20', 14);
doc.validate(function (err) {
console.log(err)
// prints
{ message: 'Validation failed',
name: 'ValidationError',
errors:
{ size:
{ message: 'must be less than 20',
name: 'ValidatorError',
path: 'size',
type: 'user defined',
value: 14 } } }
})
Document.prototype.invalidate = function (path, err, val) {
if (!this.$__.validationError) {
this.$__.validationError = new ValidationError(this);
}
if (this.$__.validationError.errors[path]) return;
if (!err || 'string' === typeof err) {
err = new ValidatorError({
path: path,
message: err,
type: 'user defined',
value: val
});
}
if (this.$__.validationError == err) return;
this.$__.validationError.errors[path] = err;
}
path
)Returns true if path
was directly set and modified, else false.
path
<String> doc.set('documents.0.title', 'changed');
doc.isDirectModified('documents.0.title') // true
doc.isDirectModified('documents') // false
Document.prototype.isDirectModified = function (path) {
return (path in this.$__.activePaths.states.modify);
};
path
)Checks if path
was initialized.
path
<String> Document.prototype.isInit = function (path) {
return (path in this.$__.activePaths.states.init);
};
[path]
)Returns true if this document was modified, else false.
[path]
<String> optionalIf path
is given, checks if a path or any full path containing path
as part of its path chain has been modified.
doc.set('documents.0.title', 'changed');
doc.isModified() // true
doc.isModified('documents') // true
doc.isModified('documents.0.title') // true
doc.isDirectModified('documents') // false
Document.prototype.isModified = function (path) {
return path
? !!~this.modifiedPaths().indexOf(path)
: this.$__.activePaths.some('modify');
};
path
)Checks if path
was selected in the source query which initialized this document.
path
<String> Thing.findOne().select('name').exec(function (err, doc) {
doc.isSelected('name') // true
doc.isSelected('age') // false
})
Document.prototype.isSelected = function isSelected (path) {
if (this.$__.selected) {
if ('_id' === path) {
return 0 !== this.$__.selected._id;
}
var paths = Object.keys(this.$__.selected)
, i = paths.length
, inclusive = false
, cur
if (1 === i && '_id' === paths[0]) {
// only _id was selected.
return 0 === this.$__.selected._id;
}
while (i--) {
cur = paths[i];
if ('_id' == cur) continue;
inclusive = !! this.$__.selected[cur];
break;
}
if (path in this.$__.selected) {
return inclusive;
}
i = paths.length;
var pathDot = path + '.';
while (i--) {
cur = paths[i];
if ('_id' == cur) continue;
if (0 === cur.indexOf(pathDot)) {
return inclusive;
}
if (0 === pathDot.indexOf(cur + '.')) {
return inclusive;
}
}
return ! inclusive;
}
return true;
};
path
)Marks the path as having pending changes to write to the db.
path
<String> the path to mark modifiedVery helpful when using Mixed types.
doc.mixed.type = 'changed';
doc.markModified('mixed.type');
doc.save() // changes to mixed.type are now persisted
Document.prototype.markModified = function (path) {
this.$__.activePaths.modify(path);
}
Returns the list of paths that have been modified.
Document.prototype.modifiedPaths = function () {
var directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
return directModifiedPaths.reduce(function (list, path) {
var parts = path.split('.');
return list.concat(parts.reduce(function (chains, part, i) {
return chains.concat(parts.slice(0, i).concat(part).join('.'));
}, []));
}, []);
};
[path]
, [callback]
)Populates document references, executing the callback
when complete.
doc
.populate('company')
.populate({
path: 'notes',
match: /airline/,
select: 'text',
model: 'modelName'
options: opts
}, function (err, user) {
assert(doc._id == user._id) // the document itself is passed
})
// summary
doc.populate(path) // not executed
doc.populate(options); // not executed
doc.populate(path, callback) // executed
doc.populate(options, callback); // executed
doc.populate(callback); // executed
Population does not occur unless a callback
is passed or you explicitly
call execPopulate()
.
Passing the same path a second time will overwrite the previous path options.
See Model.populate() for explaination of options.
Document.prototype.populate = function populate () {
if (0 === arguments.length) return this;
var pop = this.$__.populate || (this.$__.populate = {});
var args = utils.args(arguments);
var fn;
if ('function' == typeof args[args.length-1]) {
fn = args.pop();
}
// allow `doc.populate(callback)`
if (args.length) {
// use hash to remove duplicate paths
var res = utils.populate.apply(null, args);
for (var i = 0; i < res.length; ++i) {
pop[res[i].path] = res[i];
}
}
if (fn) {
var paths = utils.object.vals(pop);
this.$__.populate = undefined;
this.constructor.populate(this, paths, fn);
}
return this;
};
path
)Gets _id(s) used during population of the given path
.
path
<String> Model.findOne().populate('author').exec(function (err, doc) {
console.log(doc.author.name) // Dr.Seuss
console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
})
If the path was not populated, undefined is returned.
Document.prototype.populated = function (path, val, options) {
// val and options are internal
if (val == null) {
if (!this.$__.populated) return undefined;
var v = this.$__.populated[path];
if (v) return v.value;
return undefined;
}
// internal
if (true === val) {
if (!this.$__.populated) return undefined;
return this.$__.populated[path];
}
this.$__.populated || (this.$__.populated = {});
this.$__.populated[path] = { value: val, options: options };
return val;
}
path
, val
, [type]
, [options]
)Sets the value of a path, or many paths.
// path, value
doc.set(path, value)
// object
doc.set({
path : value
, path2 : {
path : value
}
})
// only-the-fly cast to number
doc.set(path, value, Number)
// only-the-fly cast to string
doc.set(path, value, String)
// changing strict mode behavior
doc.set(path, value, { strict: false });
Document.prototype.set = function (path, val, type, options) {
if (type && 'Object' == utils.getFunctionName(type.constructor)) {
options = type;
type = undefined;
}
var merge = options && options.merge
, adhoc = type && true !== type
, constructing = true === type
, adhocs
var strict = options && 'strict' in options
? options.strict
: this.$__.strictMode;
if (adhoc) {
adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
adhocs[path] = Schema.interpretAsType(path, type);
}
if ('string' !== typeof path) {
// new Document({ key: val })
if (null === path || undefined === path) {
var _ = path;
path = val;
val = _;
} else {
var prefix = val
? val + '.'
: '';
if (path instanceof Document) path = path._doc;
var keys = Object.keys(path)
, i = keys.length
, pathtype
, key;
while (i--) {
key = keys[i];
var pathName = prefix + key;
pathtype = this.schema.pathType(pathName);
if (null != path[key]
// need to know if plain object - no Buffer, ObjectId, ref, etc
&& utils.isObject(path[key])
&& (!path[key].constructor || 'Object' == utils.getFunctionName(path[key].constructor))
&& 'virtual' != pathtype
&& !(this.$__path(pathName) instanceof MixedSchema)
&& !(this.schema.paths[pathName] && this.schema.paths[pathName].options.ref)) {
this.set(path[key], prefix + key, constructing);
} else if (strict) {
if ('real' === pathtype || 'virtual' === pathtype) {
this.set(prefix + key, path[key], constructing);
} else if ('throw' == strict) {
throw new Error('Field `' + key + '` is not in schema.');
}
} else if (undefined !== path[key]) {
this.set(prefix + key, path[key], constructing);
}
}
return this;
}
}
// ensure _strict is honored for obj props
// docschema = new Schema({ path: { nest: 'string' }})
// doc.set('path', obj);
var pathType = this.schema.pathType(path);
if ('nested' == pathType && val) {
if (utils.isObject(val) &&
(!val.constructor || 'Object' == utils.getFunctionName(val.constructor))) {
if (!merge) this.setValue(path, null);
this.set(val, path, constructing);
return this;
}
this.$__error(new MongooseError.CastError('Object', val, path));
return this;
}
var schema;
var parts = path.split('.');
if ('adhocOrUndefined' == pathType && strict) {
// check for roots that are Mixed types
var mixed;
for (var i = 0; i < parts.length; ++i) {
var subpath = parts.slice(0, i+1).join('.');
schema = this.schema.path(subpath);
if (schema instanceof MixedSchema) {
// allow changes to sub paths of mixed types
mixed = true;
break;
}
}
if (!mixed) {
if ('throw' == strict) {
throw new Error("Field `" + path + "` is not in schema.");
}
return this;
}
} else if ('virtual' == pathType) {
schema = this.schema.virtualpath(path);
schema.applySetters(val, this);
return this;
} else {
schema = this.$__path(path);
}
var pathToMark;
// When using the $set operator the path to the field must already exist.
// Else mongodb throws: "LEFT_SUBFIELD only supports Object"
if (parts.length <= 1) {
pathToMark = path;
} else {
for (var i = 0; i < parts.length; ++i) {
var subpath = parts.slice(0, i+1).join('.');
if (this.isDirectModified(subpath) // earlier prefixes that are already
// marked as dirty have precedence
|| this.get(subpath) === null) {
pathToMark = subpath;
break;
}
}
if (!pathToMark) pathToMark = path;
}
// if this doc is being constructed we should not trigger getters
var priorVal = constructing
? undefined
: this.getValue(path);
if (!schema) {
this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
return this;
}
var shouldSet = true;
try {
// If the user is trying to set a ref path to a document with
// the correct model name, treat it as populated
if (schema.options &&
schema.options.ref &&
val instanceof Document &&
schema.options.ref === val.constructor.modelName) {
this.populated(path, val._id);
}
val = schema.applySetters(val, this, false, priorVal);
} catch (e) {
this.invalidate(e.path, new ValidatorError({
path: e.path,
message: e.message,
type: 'cast',
value: e.value
}));
shouldSet = false;
}
if (shouldSet) {
this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
}
return this;
}
path
, value
)Sets a raw value for a path (no casting, setters, transformations)
show codeDocument.prototype.setValue = function (path, val) {
utils.setValue(path, val, this._doc);
return this;
}
options
)The return value of this method is used in calls to JSON.stringify(doc).
options
<Object> This method accepts the same options as Document#toObject. To apply the options to every document of your schema by default, set your schemas toJSON
option to the same argument.
schema.set('toJSON', { virtuals: true })
See schema options for details.
Document.prototype.toJSON = function (options) {
// check for object type since an array of documents
// being stringified passes array indexes instead
// of options objects. JSON.stringify([doc, doc])
// The second check here is to make sure that populated documents (or
// subdocuments) use their own options for `.toJSON()` instead of their
// parent's
if (!(options && 'Object' == utils.getFunctionName(options.constructor))
|| ((!options || options.json) && this.schema.options.toJSON)) {
options = this.schema.options.toJSON
? clone(this.schema.options.toJSON)
: {};
}
options.json = true;
return this.toObject(options);
};
[options]
)Converts this document into a plain javascript object, ready for storage in MongoDB.
[options]
<Object> Buffers are converted to instances of mongodb.Binary for proper storage.
getters
apply all getters (path and virtual getters)virtuals
apply virtual getters (can override getters
option)minimize
remove empty objects (defaults to true)transform
a transform function to apply to the resulting document before returningdepopulate
depopulate any populated paths, replacing them with their original refs (defaults to false)versionKey
whether to include the version key (defaults to true)Example of only applying path getters
doc.toObject({ getters: true, virtuals: false })
Example of only applying virtual getters
doc.toObject({ virtuals: true })
Example of applying both path and virtual getters
doc.toObject({ getters: true })
To apply these options to every document of your schema by default, set your schemas toObject
option to the same argument.
schema.set('toObject', { virtuals: true })
We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional transform
function.
Transform functions receive three arguments
function (doc, ret, options) {}
doc
The mongoose document which is being convertedret
The plain object representation which has been convertedoptions
The options in use (either schema options or the options passed inline)// specify the transform schema option
if (!schema.options.toObject) schema.options.toObject = {};
schema.options.toObject.transform = function (doc, ret, options) {
// remove the _id of every document before returning the result
delete ret._id;
}
// without the transformation in the schema
doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
// with the transformation
doc.toObject(); // { name: 'Wreck-it Ralph' }
With transformations we can do a lot more than remove properties. We can even return completely new customized objects:
if (!schema.options.toObject) schema.options.toObject = {};
schema.options.toObject.transform = function (doc, ret, options) {
return { movie: ret.name }
}
// without the transformation in the schema
doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
// with the transformation
doc.toObject(); // { movie: 'Wreck-it Ralph' }
Note: if a transform function returns undefined
, the return value will be ignored.
Transformations may also be applied inline, overridding any transform set in the options:
function xform (doc, ret, options) {
return { inline: ret.name, custom: true }
}
// pass the transform as an inline option
doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
Note: if you call toObject
and pass any options, the transform declared in your schema options will not be applied. To force its application pass transform: true
if (!schema.options.toObject) schema.options.toObject = {};
schema.options.toObject.hide = '_id';
schema.options.toObject.transform = function (doc, ret, options) {
if (options.hide) {
options.hide.split(' ').forEach(function (prop) {
delete ret[prop];
});
}
}
var doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
doc.toObject(); // { secret: 47, name: 'Wreck-it Ralph' }
doc.toObject({ hide: 'secret _id' }); // { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
Transforms are applied only to the document and are not applied to sub-documents.
Transforms, like all of these options, are also available for toJSON
.
See schema options for some more details.
During save, no custom options are applied to the document before being sent to the database.
Document.prototype.toObject = function (options) {
var defaultOptions = { transform: true };
if (options && options.depopulate && !options._skipDepopulateTopLevel && this.$__.wasPopulated) {
// populated paths that we set to a document
return clone(this._id, options);
}
// If we're calling toObject on a populated doc, we may want to skip
// depopulated on the top level
if (options && options._skipDepopulateTopLevel) {
options._skipDepopulateTopLevel = false;
}
// When internally saving this document we always pass options,
// bypassing the custom schema options.
var optionsParameter = options;
if (!(options && 'Object' == utils.getFunctionName(options.constructor)) ||
(options && options._useSchemaOptions)) {
options = this.schema.options.toObject
? clone(this.schema.options.toObject)
: {};
}
for (var key in defaultOptions) {
if (options[key] === undefined) {
options[key] = defaultOptions[key];
}
}
;('minimize' in options) || (options.minimize = this.schema.options.minimize);
if (!optionsParameter) {
options._useSchemaOptions = true;
}
// remember the root transform function
// to save it from being overwritten by sub-transform functions
var originalTransform = options.transform;
var ret = clone(this._doc, options);
if (options.virtuals || options.getters && false !== options.virtuals) {
applyGetters(this, ret, 'virtuals', options);
}
if (options.getters) {
applyGetters(this, ret, 'paths', options);
// applyGetters for paths will add nested empty objects;
// if minimize is set, we need to remove them.
if (options.minimize) {
ret = minimize(ret) || {};
}
}
if (options.versionKey === false && this.schema.options.versionKey) {
delete ret[this.schema.options.versionKey];
}
var transform = options.transform;
// In the case where a subdocument has its own transform function, we need to
// check and see if the parent has a transform (options.transform) and if the
// child schema has a transform (this.schema.options.toObject) In this case,
// we need to adjust options.transform to be the child schema's transform and
// not the parent schema's
if (true === transform ||
(this.schema.options.toObject && transform)) {
var opts = options.json? this.schema.options.toJSON : this.schema.options.toObject;
if (opts) {
transform = (typeof options.transform === 'function' ? options.transform : opts.transform);
}
} else {
options.transform = originalTransform;
}
if ('function' == typeof transform) {
var xformed = transform(this, ret, options);
if ('undefined' != typeof xformed) ret = xformed;
}
return ret;
};
doc
, options
, callback
)Sends an update command with this document _id
as the query selector.
weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
Document.prototype.update = function update () {
var args = utils.args(arguments);
args.unshift({_id: this._id});
return this.constructor.update.apply(this.constructor, args);
}
optional
)Executes registered validation rules for this document.
optional
<Function> cb called after validation completes, passing an error if one occurredThis method is called pre
save and if a validation rule is violated, save is aborted and the error is returned to your callback
.
doc.validate(function (err) {
if (err) handleError(err);
else // validation passed
});
Document.prototype.validate = function (cb) {
var self = this;
var promise = new Promise(cb);
var preSaveErr = self.$__presaveValidate();
if (preSaveErr) {
promise.reject(preSaveErr);
return promise;
}
// only validate required fields when necessary
var paths = Object.keys(this.$__.activePaths.states.require).filter(function (path) {
if (!self.isSelected(path) && !self.isModified(path)) return false;
return true;
});
paths = paths.concat(Object.keys(this.$__.activePaths.states.init));
paths = paths.concat(Object.keys(this.$__.activePaths.states.modify));
paths = paths.concat(Object.keys(this.$__.activePaths.states.default));
if (0 === paths.length) {
process.nextTick(function() {
complete();
});
return promise;
}
var validating = {}
, total = 0;
// gh-661: if a whole array is modified, make sure to run validation on all
// the children as well
for (var i = 0; i < paths.length; ++i) {
var path = paths[i];
var val = self.getValue(path);
if (val instanceof Array && !Buffer.isBuffer(val) &&
!val.isMongooseDocumentArray) {
var numElements = val.length;
for (var j = 0; j < numElements; ++j) {
paths.push(path + '.' + j);
}
}
}
paths.forEach(validatePath);
return promise;
function validatePath (path) {
if (validating[path]) return;
validating[path] = true;
total++;
process.nextTick(function(){
var p = self.schema.path(path);
if (!p) return --total || complete();
var val = self.getValue(path);
p.doValidate(val, function (err) {
if (err) {
self.invalidate(path, err, undefined, true);
}
--total || complete();
}, self);
});
}
function complete () {
var err = self.$__.validationError;
self.$__.validationError = undefined;
self.emit('validate', self);
if (err) {
promise.reject(err);
} else {
promise.fulfill();
}
}
};
Executes registered validation rules (skipping asynchronous validators) for this document.
This method is useful if you need synchronous validation.
var err = doc.validateSync();
if ( err ){
handleError( err );
} else {
// validation passed
}
Document.prototype.validateSync = function () {
var self = this;
// only validate required fields when necessary
var paths = Object.keys(this.$__.activePaths.states.require).filter(function (path) {
if (!self.isSelected(path) && !self.isModified(path)) return false;
return true;
});
paths = paths.concat(Object.keys(this.$__.activePaths.states.init));
paths = paths.concat(Object.keys(this.$__.activePaths.states.modify));
paths = paths.concat(Object.keys(this.$__.activePaths.states.default));
var validating = {};
paths.forEach(function (path) {
if (validating[path]) return;
validating[path] = true;
var p = self.schema.path(path);
if (!p) return;
var val = self.getValue(path);
var err = p.doValidateSync( val, self );
if ( err ){
self.invalidate(path, err, undefined, true);
}
});
var err = self.$__.validationError;
self.$__.validationError = undefined;
self.emit('validate', self);
return err;
};
The string version of this documents _id.
This getter exists on all documents by default. The getter can be disabled by setting the id
option of its Schema
to false at construction time.
new Schema({ name: String }, { id: false });
show codeDocument.prototype.id;
Returns the Document constructor for the current context
show codemodule.exports = function() {
if (typeof window !== 'undefined' && typeof document !== 'undefined' && document === window.document) {
return BrowserDocument;
} else {
return Document;
}
};
query
, schema
, castedDoc
, options
)Applies validators and defaults to update and fineOneAndUpdate operations,
specifically passing a null doc as this
to validators and defaults
js
)Specifies a javascript function or expression to pass to MongoDBs query system.
query.$where('this.comments.length === 10 || this.name.length === 5')
// or
query.$where(function () {
return this.comments.length === 10 || this.name.length === 5;
})
Only use $where
when you have a condition that cannot be met using other MongoDB operators like $lt
.
Be sure to read about all of its caveats before using.
[options]
, [model]
, [conditions]
, [collection]
)Query constructor used for building queries.
var query = new Query();
query.setOptions({ lean : true });
query.collection(model.collection);
query.where('age').gte(21).exec(callback);
function Query(conditions, options, model, collection) {
// this stuff is for dealing with custom queries created by #toConstructor
if (!this._mongooseOptions) {
this._mongooseOptions = {};
}
// this is the case where we have a CustomQuery, we need to check if we got
// options passed in, and if we did, merge them in
if (options) {
var keys = Object.keys(options);
for (var i=0; i < keys.length; i++) {
var k = keys[i];
this._mongooseOptions[k] = options[k];
}
}
if (collection) {
this.mongooseCollection = collection;
}
if (model) {
this.model = model;
this.schema = model.schema;
}
// this is needed because map reduce returns a model that can be queried, but
// all of the queries on said model should be lean
if (this.model && this.model._mapreduce) {
this.lean();
}
// inherit mquery
mquery.call(this, this.mongooseCollection, options);
if (conditions) {
this.find(conditions);
}
if (this.schema) {
this._execUpdate = this.model.hooks.createWrapper('update',
Query.prototype._execUpdate, this);
}
}
Applies schematype selected options to this query.
show codeQuery.prototype._applyPaths = function applyPaths () {
// determine if query is selecting or excluding fields
var fields = this._fields
, exclude
, keys
, ki
if (fields) {
keys = Object.keys(fields);
ki = keys.length;
while (ki--) {
if ('+' == keys[ki][0]) continue;
exclude = 0 === fields[keys[ki]];
break;
}
}
// if selecting, apply default schematype select:true fields
// if excluding, apply schematype select:false fields
var selected = []
, excluded = []
, seen = [];
var analyzePath = function(path, type) {
if ('boolean' != typeof type.selected) return;
var plusPath = '+' + path;
if (fields && plusPath in fields) {
// forced inclusion
delete fields[plusPath];
// if there are other fields being included, add this one
// if no other included fields, leave this out (implied inclusion)
if (false === exclude && keys.length > 1 && !~keys.indexOf(path)) {
fields[path] = 1;
}
return
};
// check for parent exclusions
var root = path.split('.')[0];
if (~excluded.indexOf(root)) return;
;(type.selected ? selected : excluded).push(path);
};
var analyzeSchema = function(schema, prefix) {
prefix || (prefix = '');
// avoid recursion
if (~seen.indexOf(schema)) return;
seen.push(schema);
schema.eachPath(function (path, type) {
if (prefix) path = prefix + '.' + path;
analyzePath(path, type);
// array of subdocs?
if (type.schema) {
analyzeSchema(type.schema, path);
}
});
};
analyzeSchema(this.model.schema);
switch (exclude) {
case true:
excluded.length && this.select('-' + excluded.join(' -'));
break;
case false:
selected.length && this.select(selected.join(' '));
break;
case undefined:
// user didn't specify fields, implies returning all fields.
// only need to apply excluded fields
excluded.length && this.select('-' + excluded.join(' -'));
break;
}
return seen = excluded = selected = keys = fields = null;
}
fields
)Casts selected field arguments for field selection with mongo 2.2
fields
<Object> query.select({ ids: { $elemMatch: { $in: [hexString] }})
Query.prototype._castFields = function _castFields (fields) {
var selected
, elemMatchKeys
, keys
, key
, out
, i
if (fields) {
keys = Object.keys(fields);
elemMatchKeys = [];
i = keys.length;
// collect $elemMatch args
while (i--) {
key = keys[i];
if (fields[key].$elemMatch) {
selected || (selected = {});
selected[key] = fields[key];
elemMatchKeys.push(key);
}
}
}
if (selected) {
// they passed $elemMatch, cast em
try {
out = this.cast(this.model, selected);
} catch (err) {
return err;
}
// apply the casted field args
i = elemMatchKeys.length;
while (i--) {
key = elemMatchKeys[i];
fields[key] = out[key];
}
}
return fields;
}
obj
)Casts obj for an update command.
obj
<Object> Query.prototype._castUpdate = function _castUpdate (obj, overwrite) {
if (!obj) return undefined;
var ops = Object.keys(obj)
, i = ops.length
, ret = {}
, hasKeys
, val;
while (i--) {
var op = ops[i];
// if overwrite is set, don't do any of the special $set stuff
if ('$' !== op[0] && !overwrite) {
// fix up $set sugar
if (!ret.$set) {
if (obj.$set) {
ret.$set = obj.$set;
} else {
ret.$set = {};
}
}
ret.$set[op] = obj[op];
ops.splice(i, 1);
if (!~ops.indexOf('$set')) ops.push('$set');
} else if ('$set' === op) {
if (!ret.$set) {
ret[op] = obj[op];
}
} else {
ret[op] = obj[op];
}
}
// cast each value
i = ops.length;
// if we get passed {} for the update, we still need to respect that when it
// is an overwrite scenario
if (overwrite) {
hasKeys = true;
}
while (i--) {
op = ops[i];
val = ret[op];
if (val && 'Object' === val.constructor.name && !overwrite) {
hasKeys |= this._walkUpdatePath(val, op);
} else if (overwrite && 'Object' === ret.constructor.name) {
// if we are just using overwrite, cast the query and then we will
// *always* return the value, even if it is an empty object. We need to
// set hasKeys above because we need to account for the case where the
// user passes {} and wants to clobber the whole document
// Also, _walkUpdatePath expects an operation, so give it $set since that
// is basically what we're doing
this._walkUpdatePath(ret.$set || ret, '$set');
} else {
var msg = 'Invalid atomic update value for ' + op + '. '
+ 'Expected an object, received ' + typeof val;
throw new Error(msg);
}
}
return hasKeys && ret;
}
schema
, val
, op
, [$conditional]
)Casts val
according to schema
and atomic op
.
Query.prototype._castUpdateVal = function _castUpdateVal (schema, val, op, $conditional) {
if (!schema) {
// non-existing schema path
return op in numberOps
? Number(val)
: val
}
var cond = schema.caster && op in castOps &&
(utils.isObject(val) || Array.isArray(val));
if (cond) {
// Cast values for ops that add data to MongoDB.
// Ensures embedded documents get ObjectIds etc.
var tmp = schema.cast(val);
if (Array.isArray(val)) {
val = tmp;
} else {
val = tmp[0];
}
}
if (op in numberOps) {
return Number(val);
}
if (op === '$currentDate') {
if (typeof val === 'object') {
return { $type: val.$type };
}
return Boolean(val);
}
if (/^\$/.test($conditional)) {
return schema.castForQuery($conditional, val);
}
return schema.castForQuery(val)
}
[criteria]
, [callback]
)Internal helper for count
function _count(conditions, callback) {
try {
this.cast(this.model);
} catch (err) {
process.nextTick(function() {
callback(err);
});
return this;
}
var conds = this._conditions;
var options = this._optionsForExec();
this._collection.count(conds, options, utils.tick(callback));
}
method
)Makes sure _path is set.
method
<String> castedQuery
, castedDoc
, options
, callback
)Internal helper for update
Query.prototype._execUpdate = function(castedQuery, castedDoc, options, callback) {
var schema = this.model.schema;
var doValidate;
var _this;
if (this.options.runValidators) {
_this = this;
doValidate = updateValidators(this, schema, castedDoc, options);
doValidate(function(err) {
if (err) {
return callback(err);
}
Query.base.update.call(_this, castedQuery, castedDoc, options, callback);
});
return this;
}
Query.base.update.call(this, castedQuery, castedDoc, options, callback);
return this;
};
Returns fields selection for this query.
type
, callback
)Override mquery.prototype._findAndModify to provide casting etc.
show codeQuery.prototype._findAndModify = function (type, callback) {
if ('function' != typeof callback) {
throw new Error("Expected callback in _findAndModify");
}
var model = this.model;
var schema = model.schema;
var promise = new Promise(callback);
var self = this;
var castedQuery;
var castedDoc;
var fields;
var opts;
var doValidate;
castedQuery = castQuery(this);
if (castedQuery instanceof Error) {
process.nextTick(promise.error.bind(promise, castedQuery));
return promise;
}
opts = this._optionsForExec(model);
if ('strict' in opts) {
this._mongooseOptions.strict = opts.strict;
}
if ('remove' == type) {
opts.remove = true;
} else {
if (!('new' in opts)) {
opts.new = false;
}
if (!('upsert' in opts)) {
opts.upsert = false;
}
if (opts.upsert || opts['new']) {
opts.remove = false;
}
castedDoc = castDoc(this, opts.overwrite);
if (!castedDoc) {
if (opts.upsert) {
// still need to do the upsert to empty doc
var doc = utils.clone(castedQuery);
delete doc._id;
castedDoc = { $set: doc };
} else {
return this.findOne(callback);
}
} else if (castedDoc instanceof Error) {
process.nextTick(promise.error.bind(promise, castedDoc));
return promise;
} else {
// In order to make MongoDB 2.6 happy (see
// https://jira.mongodb.org/browse/SERVER-12266 and related issues)
// if we have an actual update document but $set is empty, junk the $set.
if (castedDoc.$set && Object.keys(castedDoc.$set).length === 0) {
delete castedDoc.$set;
}
}
doValidate = updateValidators(this, schema, castedDoc, options);
}
this._applyPaths();
var self = this;
var options = this._mongooseOptions;
if (this._fields) {
fields = utils.clone(this._fields);
opts.fields = this._castFields(fields);
if (opts.fields instanceof Error) {
process.nextTick(promise.error.bind(promise, opts.fields));
return promise;
}
}
if (opts.sort) convertSortToArray(opts);
var cb = function(err, doc) {
if (err) return promise.error(err);
if (!doc || (utils.isObject(doc) && Object.keys(doc).length === 0)) {
return promise.complete(null);
}
if (!options.populate) {
return true === options.lean
? promise.complete(doc)
: completeOne(self.model, doc, fields, self, null, promise);
}
var pop = helpers.preparePopulationOptionsMQ(self, options);
self.model.populate(doc, pop, function (err, doc) {
if (err) return promise.error(err);
return true === options.lean
? promise.complete(doc)
: completeOne(self.model, doc, fields, self, pop, promise);
});
};
if (opts.runValidators && doValidate) {
doValidate(function(error) {
if (error) {
return promise.error(error);
}
self._collection.findAndModify(castedQuery, castedDoc, opts, utils.tick(function(error, res) {
return cb(error, res ? res.value : res);
}));
});
} else {
this._collection.findAndModify(castedQuery, castedDoc, opts, utils.tick(function(error, res) {
return cb(error, res ? res.value : res);
}));
}
return promise;
}
path
)Finds the schema for path
. This is different than
calling schema.path
as it also resolves paths with
positional selectors (something.$.another.$.path).
path
<String> Query.prototype._getSchema = function _getSchema (path) {
return this.model._getSchema(path);
}
doc
)Override mquery.prototype._mergeUpdate to handle mongoose objects in
updates.
doc
<Object> Query.prototype._mergeUpdate = function(doc) {
if (!this._update) this._update = {};
if (doc instanceof Query) {
if (doc._update) {
utils.mergeClone(this._update, doc._update);
}
} else {
utils.mergeClone(this._update, doc);
}
};
model
)Returns default options for this query.
model
<Model> Query.prototype._optionsForExec = function (model) {
var options = Query.base._optionsForExec.call(this);
delete options.populate;
model = model || this.model;
if (!model) {
return options;
} else {
if (!('safe' in options) && model.schema.options.safe) {
options.safe = model.schema.options.safe;
}
if (!('readPreference' in options) && model.schema.options.read) {
options.readPreference = model.schema.options.read;
}
return options;
}
};
Return an update document with corrected $set operations.
obj
, op
, pref
)Walk each path of obj and cast its values
according to its schema.
Query.prototype._walkUpdatePath = function _walkUpdatePath (obj, op, pref) {
var prefix = pref ? pref + '.' : ''
, keys = Object.keys(obj)
, i = keys.length
, hasKeys = false
, schema
, key
, val;
var strict = 'strict' in this._mongooseOptions
? this._mongooseOptions.strict
: this.model.schema.options.strict;
while (i--) {
key = keys[i];
val = obj[key];
if (val && 'Object' === val.constructor.name) {
// watch for embedded doc schemas
schema = this._getSchema(prefix + key);
if (schema && schema.caster && op in castOps) {
// embedded doc schema
if (strict && !schema) {
// path is not in our strict schema
if ('throw' == strict) {
throw new Error('Field `' + key + '` is not in schema.');
} else {
// ignore paths not specified in schema
delete obj[key];
}
} else {
hasKeys = true;
if ('$each' in val) {
obj[key] = {
$each: this._castUpdateVal(schema, val.$each, op)
}
if (val.$slice) {
obj[key].$slice = val.$slice | 0;
}
if (val.$sort) {
obj[key].$sort = val.$sort;
}
if (!!val.$position || val.$position === 0) {
obj[key].$position = val.$position;
}
} else {
obj[key] = this._castUpdateVal(schema, val, op);
}
}
} else if (op === '$currentDate') {
// $currentDate can take an object
obj[key] = this._castUpdateVal(schema, val, op);
} else if (op === '$set' && schema) {
obj[key] = this._castUpdateVal(schema, val, op);
} else {
// gh-2314
// we should be able to set a schema-less field
// to an empty object literal
hasKeys |= this._walkUpdatePath(val, op, prefix + key) ||
(utils.isObject(val) && Object.keys(val).length === 0);
}
} else {
schema = ('$each' === key || '$or' === key || '$and' === key)
? this._getSchema(pref)
: this._getSchema(prefix + key);
var skip = strict &&
!schema &&
!/real|nested/.test(this.model.schema.pathType(prefix + key));
if (skip) {
if ('throw' == strict) {
throw new Error('Field `' + prefix + key + '` is not in schema.');
} else {
delete obj[key];
}
} else {
hasKeys = true;
obj[key] = this._castUpdateVal(schema, val, op, key);
}
}
}
return hasKeys;
}
[path]
, val
)Specifies an $all query condition.
When called with one argument, the most recent path passed to where()
is used.
array
)Specifies arguments for a $and
condition.
array
<Array> array of conditionsquery.and([{ color: 'green' }, { status: 'ok' }])
val
)Specifies the batchSize option.
val
<Number> query.batchSize(100)
Cannot be used with distinct()
val
, Upper
)Specifies a $box condition
var lowerLeft = [40.73083, -73.99756]
var upperRight= [40.741404, -73.988135]
query.where('loc').within().box(lowerLeft, upperRight)
query.box({ ll : lowerLeft, ur : upperRight })
conds
)Determines if conds
can be merged using mquery().merge()
conds
<Object> model
, [obj]
)Casts this query to the schema of model
If obj
is present, it is cast instead of this query.
Query.prototype.cast = function (model, obj) {
obj || (obj = this._conditions);
return cast(model.schema, obj);
}
[path]
, val
)DEPRECATED Specifies a $centerSphere condition
Deprecated. Use circle instead.
var area = { center: [50, 50], radius: 10 };
query.where('loc').within().centerSphere(area);
Query.prototype.centerSphere = function () {
if (arguments[0] && arguments[0].constructor.name == 'Object') {
arguments[0].spherical = true;
}
if (arguments[1] && arguments[1].constructor.name == 'Object') {
arguments[1].spherical = true;
}
Query.base.circle.apply(this, arguments);
}
[path]
, area
)Specifies a $center or $centerSphere condition.
var area = { center: [50, 50], radius: 10, unique: true }
query.where('loc').within().circle(area)
// alternatively
query.circle('loc', area);
// spherical calculations
var area = { center: [50, 50], radius: 10, unique: true, spherical: true }
query.where('loc').within().circle(area)
// alternatively
query.circle('loc', area);
New in 3.7.0
val
)Specifies the comment
option.
val
<Number> query.comment('login query')
Cannot be used with distinct()
[criteria]
, [callback]
)Specifying this query as a count
query.
Passing a callback
executes the query.
var countQuery = model.where({ 'color': 'black' }).count();
query.count({ color: 'black' }).count(callback)
query.count({ color: 'black' }, callback)
query.where('color', 'black').count(function (err, count) {
if (err) return handleError(err);
console.log('there are %d kittens', count);
})
Query.prototype.count = function (conditions, callback) {
if ('function' == typeof conditions) {
callback = conditions;
conditions = undefined;
}
if (mquery.canMerge(conditions)) {
this.merge(conditions);
}
this.op = 'count';
if (!callback) {
return this;
}
this.model.hooks.wrap('count', _count, this, [conditions, callback]);
return this;
}
[field]
, [criteria]
, [callback]
)Declares or executes a distict() operation.
Passing a callback
executes the query.
distinct(field, conditions, callback)
distinct(field, conditions)
distinct(field, callback)
distinct(field)
distinct(callback)
distinct()
Query.prototype.distinct = function (field, conditions, callback) {
if (!callback) {
if ('function' === typeof conditions) {
callback = conditions;
conditions = undefined;
} else if ('function' === typeof field) {
callback = field;
field = undefined;
conditions = undefined;
}
}
if (conditions instanceof Document) {
conditions = conditions.toObject();
}
if (mquery.canMerge(conditions)) {
this.merge(conditions)
}
try {
this.cast(this.model);
} catch (err) {
callback(err);
return this;
}
return Query.base.distinct.call(this, {}, field, callback);
}
path
, criteria
)Specifies an $elemMatch
condition
query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}})
query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}})
query.elemMatch('comment', function (elem) {
elem.where('author').equals('autobot');
elem.where('votes').gte(5);
})
query.where('comment').elemMatch(function (elem) {
elem.where({ author: 'autobot' });
elem.where('votes').gte(5);
})
val
)Specifies the complementary comparison value for paths specified with where()
val
<Object> User.where('age').equals(49);
// is the same as
User.where('age', 49);
[operation]
, [callback]
)Executes the query
var promise = query.exec();
var promise = query.exec('update');
query.exec(callback);
query.exec('find', callback);
Query.prototype.exec = function exec (op, callback) {
var promise = new Promise();
var _this = this;
if ('function' == typeof op) {
callback = op;
op = null;
} else if ('string' == typeof op) {
this.op = op;
}
if (callback) promise.addBack(callback);
if (!this.op) {
promise.complete();
return promise;
}
this[this.op].call(this, promise.resolve.bind(promise));
return promise;
}
[path]
, val
)Specifies an $exists
condition
// { name: { $exists: true }}
Thing.where('name').exists()
Thing.where('name').exists(true)
Thing.find().exists('name')
// { name: { $exists: false }}
Thing.where('name').exists(false);
Thing.find().exists('name', false);
[criteria]
, [callback]
)Finds documents.
When no callback
is passed, the query is not executed. When the query is executed, the result will be an array of documents.
query.find({ name: 'Los Pollos Hermanos' }).find(callback)
Query.prototype.find = function (conditions, callback) {
var _this = this;
if ('function' == typeof conditions) {
callback = conditions;
conditions = {};
} else if (conditions instanceof Document) {
conditions = conditions.toObject();
}
if (mquery.canMerge(conditions)) {
this.merge(conditions);
}
prepareDiscriminatorCriteria(this);
try {
this.cast(this.model);
this._castError = null;
} catch (err) {
this._castError = err;
}
// if we don't have a callback, then just return the query object
if (!callback) {
return Query.base.find.call(this);
}
var promise;
if (this.schema) {
promise = new Promise(function(error, docs) {
if (error) {
return callback(error);
}
_this.model.hooks.execPost('find', _this, [docs], callback);
});
} else {
promise = new Promise(callback);
}
if (this._castError) {
promise.error(this._castError);
return this;
}
this._applyPaths();
this._fields = this._castFields(this._fields);
var fields = this._fieldsForExec();
var options = this._mongooseOptions;
var self = this;
var cb = function(err, docs) {
if (err) return promise.error(err);
if (0 === docs.length) {
return promise.complete(docs);
}
if (!options.populate) {
return true === options.lean
? promise.complete(docs)
: completeMany(self.model, docs, fields, self, null, promise);
}
var pop = helpers.preparePopulationOptionsMQ(self, options);
self.model.populate(docs, pop, function (err, docs) {
if(err) return promise.error(err);
return true === options.lean
? promise.complete(docs)
: completeMany(self.model, docs, fields, self, pop, promise);
});
};
if (this.schema) {
this.model.hooks.execPre('find', this, function(error) {
if (error) {
return cb(error);
}
Query.base.find.call(self, {}, cb);
});
return this;
}
return Query.base.find.call(this, {}, cb);
}
[criteria]
, [callback]
)Declares the query a findOne operation. When executed, the first found document is passed to the callback.
Passing a callback
executes the query. The result of the query is a single document.
var query = Kitten.where({ color: 'white' });
query.findOne(function (err, kitten) {
if (err) return handleError(err);
if (kitten) {
// doc may be null if no document matched
}
});
Query.prototype.findOne = function (conditions, fields, options, callback) {
if ('function' == typeof conditions) {
callback = conditions;
conditions = null;
fields = null;
options = null;
} else if ('function' == typeof fields) {
callback = fields;
options = null;
fields = null;
} else if ('function' == typeof options) {
callback = options;
options = null;
}
// make sure we don't send in the whole Document to merge()
if (conditions instanceof Document) {
conditions = conditions.toObject();
}
if (options) {
this.setOptions(options);
}
if (fields) {
this.select(fields);
}
if (mquery.canMerge(conditions)) {
this.merge(conditions);
}
prepareDiscriminatorCriteria(this);
try {
this.cast(this.model);
this._castError = null;
} catch (err) {
this._castError = err;
}
if (!callback) {
// already merged in the conditions, don't need to send them in.
return Query.base.findOne.call(this);
}
var promise;
if (this.schema) {
promise = new Promise(function(error, doc) {
if (error) {
return callback(error);
}
self.model.hooks.execPost('findOne', self, [doc], function(error, doc) {
if (error) {
return callback(error);
}
return callback(null, doc);
});
});
} else {
promise = new Promise(callback);
}
if (this._castError) {
promise.error(this._castError);
return this;
}
this._applyPaths();
this._fields = this._castFields(this._fields);
var options = this._mongooseOptions;
var fields = this._fieldsForExec();
var self = this;
this.model.hooks.execPre('findOne', this, function(error) {
if (error) {
return promise.error(error);
}
// don't pass in the conditions because we already merged them in
Query.base.findOne.call(self, {}, function(err, doc) {
if (err) return promise.error(err);
if (!doc) return promise.complete(null);
if (!options.populate) {
return true === options.lean
? promise.complete(doc)
: completeOne(self.model, doc, fields, self, null, promise);
}
var pop = helpers.preparePopulationOptionsMQ(self, options);
self.model.populate(doc, pop, function(err, doc) {
if (err) return promise.error(err);
return true === options.lean
? promise.complete(doc)
: completeOne(self.model, doc, fields, self, pop, promise);
});
});
});
return this;
}
[conditions]
, [options]
, [callback]
)Issues a mongodb findAndModify remove command.
Finds a matching document, removes it, passing the found document (if any) to the callback. Executes immediately if callback
is passed.
sort
: if multiple docs are found by the conditions, sets the sort order to choose which doc to updateA.where().findOneAndRemove(conditions, options, callback) // executes
A.where().findOneAndRemove(conditions, options) // return Query
A.where().findOneAndRemove(conditions, callback) // executes
A.where().findOneAndRemove(conditions) // returns Query
A.where().findOneAndRemove(callback) // executes
A.where().findOneAndRemove() // returns Query
[query]
, [doc]
, [options]
, [callback]
)Issues a mongodb findAndModify update command.
Finds a matching document, updates it according to the update
arg, passing any options
, and returns the found document (if any) to the callback. The query executes immediately if callback
is passed.
new
: bool - true to return the modified document rather than the original. defaults to trueupsert
: bool - creates the object if it doesn't exist. defaults to false.sort
: if multiple docs are found by the conditions, sets the sort order to choose which doc to updatequery.findOneAndUpdate(conditions, update, options, callback) // executes
query.findOneAndUpdate(conditions, update, options) // returns Query
query.findOneAndUpdate(conditions, update, callback) // executes
query.findOneAndUpdate(conditions, update) // returns Query
query.findOneAndUpdate(update, callback) // returns Query
query.findOneAndUpdate(update) // returns Query
query.findOneAndUpdate(callback) // executes
query.findOneAndUpdate() // returns Query
object
)Specifies a $geometry
condition
object
<Object> Must contain a `type` property which is a String and a `coordinates` property which is an Array. See the examples.var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]]
query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA })
// or
var polyB = [[ 0, 0 ], [ 1, 1 ]]
query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB })
// or
var polyC = [ 0, 0 ]
query.where('loc').within().geometry({ type: 'Point', coordinates: polyC })
// or
query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC })
The argument is assigned to the most recent path passed to where()
.
geometry()
must come after either intersects()
or within()
.
The object
argument must contain type
and coordinates
properties.
- type {String}
- coordinates {Array}
[path]
, val
)Specifies a $gt query condition.
When called with one argument, the most recent path passed to where()
is used.
Thing.find().where('age').gt(21)
// or
Thing.find().gt('age', 21)
[path]
, val
)Specifies a $gte query condition.
When called with one argument, the most recent path passed to where()
is used.
val
)Sets query hints.
val
<Object> a hint objectquery.hint({ indexA: 1, indexB: -1})
Cannot be used with distinct()
[path]
, val
)Specifies an $in query condition.
When called with one argument, the most recent path passed to where()
is used.
[arg]
)Declares an intersects query for geometry()
.
[arg]
<Object> query.where('path').intersects().geometry({
type: 'LineString'
, coordinates: [[180.0, 11.0], [180, 9.0]]
})
query.where('path').intersects({
type: 'LineString'
, coordinates: [[180.0, 11.0], [180, 9.0]]
})
MUST be used after where()
.
In Mongoose 3.7, intersects
changed from a getter to a function. If you need the old syntax, use this.
bool
)Sets the lean option.
bool
<Boolean> defaults to trueDocuments returned from queries with the lean
option enabled are plain javascript objects, not MongooseDocuments. They have no save
method, getters/setters or other Mongoose magic applied.
new Query().lean() // true
new Query().lean(true)
new Query().lean(false)
Model.find().lean().exec(function (err, docs) {
docs[0] instanceof mongoose.Document // false
});
This is a great option in high-performance read-only scenarios, especially when combined with stream.
Query.prototype.lean = function (v) {
this._mongooseOptions.lean = arguments.length ? !!v : true;
return this;
}
val
)Specifies the maximum number of documents the query will return.
val
<Number> query.limit(20)
Cannot be used with distinct()
[path]
, val
)Specifies a $lt query condition.
When called with one argument, the most recent path passed to where()
is used.
[path]
, val
)Specifies a $lte query condition.
When called with one argument, the most recent path passed to where()
is used.
[path]
, val
)Specifies a $maxDistance query condition.
When called with one argument, the most recent path passed to where()
is used.
val
)Specifies the maxScan option.
val
<Number> query.maxScan(100)
Cannot be used with distinct()
source
)Merges another Query or conditions object into this one.
When a Query is passed, conditions, field selection and options are merged.
New in 3.7.0
[path]
, val
)Specifies a $ne query condition.
When called with one argument, the most recent path passed to where()
is used.
[path]
, val
)Specifies a $near
or $nearSphere
condition
These operators return documents sorted by distance.
query.where('loc').near({ center: [10, 10] });
query.where('loc').near({ center: [10, 10], maxDistance: 5 });
query.where('loc').near({ center: [10, 10], maxDistance: 5, spherical: true });
query.near('loc', { center: [10, 10], maxDistance: 5 });
DEPRECATED Specifies a $nearSphere
condition
query.where('loc').nearSphere({ center: [10, 10], maxDistance: 5 });
Deprecated. Use query.near()
instead with the spherical
option set to true
.
query.where('loc').near({ center: [10, 10], spherical: true });
Query.prototype.nearSphere = function () {
this._mongooseOptions.nearSphere = true;
this.near.apply(this, arguments);
return this;
}
[path]
, val
)Specifies an $nin query condition.
When called with one argument, the most recent path passed to where()
is used.
array
)Specifies arguments for a $nor
condition.
array
<Array> array of conditionsquery.nor([{ color: 'green' }, { status: 'ok' }])
array
)Specifies arguments for an $or
condition.
array
<Array> array of conditionsquery.or([{ color: 'red' }, { status: 'emergency' }])
[path]
, [coordinatePairs...]
)Specifies a $polygon condition
query.where('loc').within().polygon([10,20], [13, 25], [7,15])
query.polygon('loc', [10,20], [13, 25], [7,15])
path
, [select]
, [model]
, [match]
, [options]
)Specifies paths which should be populated with other documents.
path
<Object, String> either the path to populate or an object specifying all parameters[select]
<Object, String> Field selection for the population query[model]
<Model> The name of the model you wish to use for population. If not specified, the name is looked up from the Schema ref.[match]
<Object> Conditions for the population query[options]
<Object> Options for the population query (sort, etc)Kitten.findOne().populate('owner').exec(function (err, kitten) {
console.log(kitten.owner.name) // Max
})
Kitten.find().populate({
path: 'owner'
, select: 'name'
, match: { color: 'black' }
, options: { sort: { name: -1 }}
}).exec(function (err, kittens) {
console.log(kittens[0].owner.name) // Zoopa
})
// alternatively
Kitten.find().populate('owner', 'name', null, {sort: { name: -1 }}).exec(function (err, kittens) {
console.log(kittens[0].owner.name) // Zoopa
})
Paths are populated after the query executes and a response is received. A separate query is then executed for each path specified for population. After a response for each query has also been returned, the results are passed to the callback.
Query.prototype.populate = function () {
var res = utils.populate.apply(null, arguments);
var opts = this._mongooseOptions;
if (!utils.isObject(opts.populate)) {
opts.populate = {};
}
for (var i = 0; i < res.length; ++i) {
opts.populate[res[i].path] = res[i];
}
return this;
}
pref
, [tags]
)Determines the MongoDB nodes from which to read.
primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
secondary Read from secondary if available, otherwise error.
primaryPreferred Read from primary if available, otherwise a secondary.
secondaryPreferred Read from a secondary if available, otherwise read from the primary.
nearest All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection.
Aliases
p primary
pp primaryPreferred
s secondary
sp secondaryPreferred
n nearest
new Query().read('primary')
new Query().read('p') // same as primary
new Query().read('primaryPreferred')
new Query().read('pp') // same as primaryPreferred
new Query().read('secondary')
new Query().read('s') // same as secondary
new Query().read('secondaryPreferred')
new Query().read('sp') // same as secondaryPreferred
new Query().read('nearest')
new Query().read('n') // same as nearest
// read from secondaries with matching tags
new Query().read('s', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }])
[path]
, val
)Specifies a $regex query condition.
When called with one argument, the most recent path passed to where()
is used.
[criteria]
, [callback]
)Declare and/or execute this query as a remove() operation.
Model.remove({ artist: 'Anne Murray' }, callback)
The operation is only executed when a callback is passed. To force execution without a callback (which would be an unsafe write), we must first call remove() and then execute it by using the exec()
method.
// not executed
var query = Model.find().remove({ name: 'Anne Murray' })
// executed
query.remove({ name: 'Anne Murray' }, callback)
query.remove({ name: 'Anne Murray' }).remove(callback)
// executed without a callback (unsafe write)
query.exec()
// summary
query.remove(conds, fn); // executes
query.remove(conds)
query.remove(fn) // executes
query.remove()
Query.prototype.remove = function (cond, callback) {
if ('function' == typeof cond) {
callback = cond;
cond = null;
}
var cb = 'function' == typeof callback;
try {
this.cast(this.model);
} catch (err) {
if (cb) return process.nextTick(callback.bind(null, err));
return this;
}
return Query.base.remove.call(this, cond, callback);
}
arg
)Specifies which document fields to include or exclude
When using string syntax, prefixing a path with -
will flag that path as excluded. When a path does not have the -
prefix, it is included. Lastly, if a path is prefixed with +
, it forces inclusion of the path, which is useful for paths excluded at the schema level.
// include a and b, exclude other fields
query.select('a b');
// exclude c and d, include other fields
query.select('-c -d');
// or you may use object notation, useful when
// you have keys already prefixed with a "-"
query.select({ a: 1, b: 1 });
query.select({ c: 0, d: 0 });
// force inclusion of field excluded at schema level
query.select('+path')
Cannot be used with distinct()
.
v2 had slightly different syntax such as allowing arrays of field names. This support was removed in v3.
Determines if exclusive field selection has been made.
query.selectedExclusively() // false
query.select('-name')
query.selectedExclusively() // true
query.selectedInclusively() // false
Determines if inclusive field selection has been made.
query.selectedInclusively() // false
query.select('name')
query.selectedInclusively() // true
options
)Sets query options.
options
<Object> * denotes a query helper method is also available
Query.prototype.setOptions = function (options, overwrite) {
// overwrite is only for internal use
if (overwrite) {
// ensure that _mongooseOptions & options are two different objects
this._mongooseOptions = (options && utils.clone(options)) || {};
this.options = options || {};
if('populate' in options) {
this.populate(this._mongooseOptions);
}
return this;
}
if (!(options && 'Object' == options.constructor.name)) {
return this;
}
return Query.base.setOptions.call(this, options);
}
[path]
, val
)Specifies a $size query condition.
When called with one argument, the most recent path passed to where()
is used.
MyModel.where('tags').size(0).exec(function (err, docs) {
if (err) return handleError(err);
assert(Array.isArray(docs));
console.log('documents with 0 tags', docs);
})
val
)Specifies the number of documents to skip.
val
<Number> query.skip(100).limit(20)
Cannot be used with distinct()
v
)DEPRECATED Sets the slaveOk option.
v
<Boolean> defaults to trueDeprecated in MongoDB 2.2 in favor of read preferences.
query.slaveOk() // true
query.slaveOk(true)
query.slaveOk(false)
[path]
, val
)Specifies a $slice projection for an array.
query.slice('comments', 5)
query.slice('comments', -5)
query.slice('comments', [10, 5])
query.where('comments').slice(5)
query.where('comments').slice([-10, 5])
Specifies this query as a snapshot
query.
query.snapshot() // true
query.snapshot(true)
query.snapshot(false)
Cannot be used with distinct()
arg
)Sets the sort order
If an object is passed, values allowed are asc
, desc
, ascending
, descending
, 1
, and -1
.
If a string is passed, it must be a space delimited list of path names. The
sort order of each path is ascending unless the path name is prefixed with -
which will be treated as descending.
// sort by "field" ascending and "test" descending
query.sort({ field: 'asc', test: -1 });
// equivalent
query.sort('field -test');
Cannot be used with distinct()
Query.prototype.sort = function (arg) {
var nArg = {};
if (arguments.length > 1) {
throw new Error("sort() only takes 1 Argument");
}
if (Array.isArray(arg)) {
// time to deal with the terrible syntax
for (var i=0; i < arg.length; i++) {
if (!Array.isArray(arg[i])) throw new Error("Invalid sort() argument.");
nArg[arg[i][0]] = arg[i][1];
}
} else {
nArg = arg;
}
// workaround for gh-2374 when sort is called after count
// if previous operation is count, we ignore
if (this.op == 'count') {
delete this.op;
}
return Query.base.sort.call(this, nArg);
}
[options]
)Returns a Node.js 0.8 style read stream interface.
[options]
<Object> // follows the nodejs 0.8 stream api
Thing.find({ name: /^hello/ }).stream().pipe(res)
// manual streaming
var stream = Thing.find({ name: /^hello/ }).stream();
stream.on('data', function (doc) {
// do something with the mongoose document
}).on('error', function (err) {
// handle the error
}).on('close', function () {
// the stream is closed
});
transform
: optional function which accepts a mongoose document. The return value of the function will be emitted on data
.// JSON.stringify all documents before emitting
var stream = Thing.find().stream({ transform: JSON.stringify });
stream.pipe(writeStream);
Query.prototype.stream = function stream (opts) {
this._applyPaths();
this._fields = this._castFields(this._fields);
return new QueryStream(this, opts);
}
// the rest of these are basically to support older Mongoose syntax with mquery
bool
)Sets the tailable option (for use with capped collections).
bool
<Boolean> defaults to truequery.tailable() // true
query.tailable(true)
query.tailable(false)
Cannot be used with distinct()
Query.prototype.tailable = function (val, opts) {
// we need to support the tailable({ awaitdata : true }) as well as the
// tailable(true, {awaitdata :true}) syntax that mquery does not support
if (val && val.constructor.name == 'Object') {
opts = val;
val = true;
}
if (val === undefined) {
val = true;
}
if (opts && opts.awaitdata) this.options.awaitdata = true;
return Query.base.tailable.call(this, val);
}
Converts this query to a customized, reusable query constructor with all arguments and options retained.
// Create a query for adventure movies and read from the primary
// node in the replica-set unless it is down, in which case we'll
// read from a secondary node.
var query = Movie.find({ tags: 'adventure' }).read('primaryPreferred');
// create a custom Query constructor based off these settings
var Adventure = query.toConstructor();
// Adventure is now a subclass of mongoose.Query and works the same way but with the
// default query parameters and options set.
Adventure().exec(callback)
// further narrow down our query results while still using the previous settings
Adventure().where({ name: /^Life/ }).exec(callback);
// since Adventure is a stand-alone constructor we can also add our own
// helper methods and getters without impacting global queries
Adventure.prototype.startsWith = function (prefix) {
this.where({ name: new RegExp('^' + prefix) })
return this;
}
Object.defineProperty(Adventure.prototype, 'highlyRated', {
get: function () {
this.where({ rating: { $gt: 4.5 }});
return this;
}
})
Adventure().highlyRated.startsWith('Life').exec(callback)
New in 3.7.3
Query.prototype.toConstructor = function toConstructor () {
var CustomQuery = function(criteria, options) {
if (!(this instanceof CustomQuery))
return new CustomQuery(criteria, options);
Query.call(this, criteria, options || null);
};
util.inherits(CustomQuery, Query);
// set inherited defaults
var p = CustomQuery.prototype;
p.options = {};
p.setOptions(this.options);
p.op = this.op;
p._conditions = utils.clone(this._conditions);
p._fields = utils.clone(this._fields);
p._update = utils.clone(this._update);
p._path = this._path;
p._distict = this._distinct;
p._collection = this._collection;
p.model = this.model;
p.mongooseCollection = this.mongooseCollection;
p._mongooseOptions = this._mongooseOptions;
return CustomQuery;
}
[criteria]
, [doc]
, [options]
, [callback]
)Declare and/or execute this query as an update() operation.
All paths passed that are not $atomic operations will become $set ops.
Model.where({ _id: id }).update({ title: 'words' })
// becomes
Model.where({ _id: id }).update({ $set: { title: 'words' }})
Passing an empty object {}
as the doc will result in a no-op unless the overwrite
option is passed. Without the overwrite
option set, the update operation will be ignored and the callback executed without sending the command to MongoDB so as to prevent accidently overwritting documents in the collection.
The operation is only executed when a callback is passed. To force execution without a callback (which would be an unsafe write), we must first call update() and then execute it by using the exec()
method.
var q = Model.where({ _id: id });
q.update({ $set: { name: 'bob' }}).update(); // not executed
q.update({ $set: { name: 'bob' }}).exec(); // executed as unsafe
// keys that are not $atomic ops become $set.
// this executes the same command as the previous example.
q.update({ name: 'bob' }).exec();
// overwriting with empty docs
var q = Model.where({ _id: id }).setOptions({ overwrite: true })
q.update({ }, callback); // executes
// multi update with overwrite to empty doc
var q = Model.where({ _id: id });
q.setOptions({ multi: true, overwrite: true })
q.update({ });
q.update(callback); // executed
// multi updates
Model.where()
.update({ name: /^match/ }, { $set: { arr: [] }}, { multi: true }, callback)
// more multi updates
Model.where()
.setOptions({ multi: true })
.update({ $set: { arr: [] }}, callback)
// single update by default
Model.where({ email: 'address@example.com' })
.update({ $inc: { counter: 1 }}, callback)
API summary
update(criteria, doc, options, cb) // executes
update(criteria, doc, options)
update(criteria, doc, cb) // executes
update(criteria, doc)
update(doc, cb) // executes
update(doc)
update(cb) // executes
update(true) // executes (unsafe write)
update()
Query.prototype.update = function (conditions, doc, options, callback) {
if ('function' === typeof options) {
// Scenario: update(conditions, doc, callback)
callback = options;
options = null;
} else if ('function' === typeof doc) {
// Scenario: update(doc, callback);
callback = doc;
doc = conditions;
conditions = {};
options = null;
} else if ('function' === typeof conditions) {
callback = conditions;
conditions = undefined;
doc = undefined;
options = undefined;
}
// make sure we don't send in the whole Document to merge()
if (conditions instanceof Document) {
conditions = conditions.toObject();
}
var oldCb = callback;
if (oldCb) {
callback = function(error, result) {
oldCb(error, result ? result.result : { ok: 0, n: 0, nModified: 0 });
};
}
// strict is an option used in the update checking, make sure it gets set
if (options) {
if ('strict' in options) {
this._mongooseOptions.strict = options.strict;
}
}
// if doc is undefined at this point, this means this function is being
// executed by exec(not always see below). Grab the update doc from here in
// order to validate
// This could also be somebody calling update() or update({}). Probably not a
// common use case, check for _update to make sure we don't do anything bad
if (!doc && this._update) {
doc = this._updateForExec();
}
if (mquery.canMerge(conditions)) {
this.merge(conditions);
}
// validate the selector part of the query
var castedQuery = castQuery(this);
if (castedQuery instanceof Error) {
if(callback) {
callback(castedQuery);
return this;
} else {
throw castedQuery;
}
}
// validate the update part of the query
var castedDoc;
try {
castedDoc = this._castUpdate(doc, options && options.overwrite);
} catch (err) {
if (callback) {
callback(err);
return this;
} else {
throw err;
}
}
if (!castedDoc) {
callback && callback(null);
return this;
}
if (utils.isObject(options)) {
this.setOptions(options);
}
// Hooks
if (callback) {
return this._execUpdate(castedQuery, castedDoc, options, callback);
}
return Query.base.update.call(this, castedQuery, castedDoc, options, callback);
}
[path]
, [val]
)Specifies a path
for use with chaining.
// instead of writing:
User.find({age: {$gte: 21, $lte: 65}}, callback);
// we can instead write:
User.where('age').gte(21).lte(65);
// passing query conditions is permitted
User.find().where({ name: 'vonderful' })
// chaining
User
.where('age').gte(21).lte(65)
.where('name', /^vonderful/i)
.where('friends').slice(10)
.exec(callback)
Defines a $within
or $geoWithin
argument for geo-spatial queries.
query.where(path).within().box()
query.where(path).within().circle()
query.where(path).within().geometry()
query.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true });
query.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] });
query.where('loc').within({ polygon: [[],[],[],[]] });
query.where('loc').within([], [], []) // polygon
query.where('loc').within([], []) // box
query.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry
MUST be used after where()
.
As of Mongoose 3.7, $geoWithin
is always used for queries. To change this behavior, see Query.use$geoWithin.
In Mongoose 3.7, within
changed from a getter to a function. If you need the old syntax, use this.
Flag to opt out of using $geoWithin
.
mongoose.Query.use$geoWithin = false;
MongoDB 2.4 deprecated the use of $within
, replacing it with $geoWithin
. Mongoose uses $geoWithin
by default (which is 100% backward compatible with $within). If you are running an older version of MongoDB, set this flag to false
so your within()
queries continue to work.
Query.use$geoWithin = mquery.use$geoWithin;
The Mongoose Schema constructor
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CatSchema = new Schema(..);
The various Mongoose SchemaTypes.
Alias of mongoose.Schema.Types for backwards compatibility.
show codeexports.SchemaType = require('./schematype.js');
The various Mongoose Types.
var mongoose = require('mongoose');
var array = mongoose.Types.Array;
Using this exposed access to the ObjectId
type, we can construct ids on demand.
var ObjectId = mongoose.Types.ObjectId;
var id1 = new ObjectId;
show codeexports.Types = require('./types');
VirtualType constructor
This is what mongoose uses to define virtual attributes via Schema.prototype.virtual
.
var fullname = schema.virtual('fullname');
fullname instanceof mongoose.VirtualType // true
function VirtualType (options, name) {
this.path = name;
this.getters = [];
this.setters = [];
this.options = options || {};
}
value
, scope
)Applies getters to value
using optional scope
.
VirtualType.prototype.applyGetters = function (value, scope) {
var v = value;
for (var l = this.getters.length - 1; l >= 0; l--) {
v = this.getters[l].call(scope, v, this);
}
return v;
};
value
, scope
)Applies setters to value
using optional scope
.
VirtualType.prototype.applySetters = function (value, scope) {
var v = value;
for (var l = this.setters.length - 1; l >= 0; l--) {
v = this.setters[l].call(scope, v, this);
}
return v;
};
fn
)Defines a getter.
fn
<Function> var virtual = schema.virtual('fullname');
virtual.get(function () {
return this.name.first + ' ' + this.name.last;
});
VirtualType.prototype.get = function (fn) {
this.getters.push(fn);
return this;
};
fn
)Defines a setter.
fn
<Function> var virtual = schema.virtual('fullname');
virtual.set(function (v) {
var parts = v.split(' ');
this.name.first = parts[0];
this.name.last = parts[1];
});
VirtualType.prototype.set = function (fn) {
this.setters.push(fn);
return this;
};
path
, [options]
, [instance]
)SchemaType constructor
show codefunction SchemaType (path, options, instance) {
this.path = path;
this.instance = instance;
this.validators = [];
this.setters = [];
this.getters = [];
this.options = options;
this._index = null;
this.selected;
for (var i in options) if (this[i] && 'function' == typeof this[i]) {
// { unique: true, index: true }
if ('index' == i && this._index) continue;
var opts = Array.isArray(options[i])
? options[i]
: [options[i]];
this[i].apply(this, opts);
}
};
value
, scope
)Applies getters to a value
show codeSchemaType.prototype.applyGetters = function (value, scope) {
var v = value
, getters = this.getters
, len = getters.length;
if (!len) {
return v;
}
while (len--) {
v = getters[len].call(scope, v, this);
}
return v;
};
value
, scope
, init
)Applies setters
show codeSchemaType.prototype.applySetters = function (value, scope, init, priorVal) {
var v = value
, setters = this.setters
, len = setters.length
, caster = this.caster;
while (len--) {
v = setters[len].call(scope, v, this);
}
if (Array.isArray(v) && caster && caster.setters) {
for (var i = 0; i < v.length; i++) {
v[i] = caster.applySetters(v[i], scope, init, priorVal);
}
}
if (null === v || undefined === v) return v;
// do not cast until all setters are applied #665
v = this.cast(v, scope, init, priorVal);
return v;
};
val
)Sets a default value for this SchemaType.
val
<Function, T> the default valuevar schema = new Schema({ n: { type: Number, default: 10 })
var M = db.model('M', schema)
var m = new M;
console.log(m.n) // 10
Defaults can be either functions
which return the value to use as the default or the literal value itself. Either way, the value will be cast based on its schema type before being set during document creation.
// values are cast:
var schema = new Schema({ aNumber: { type: Number, default: 4.815162342 }})
var M = db.model('M', schema)
var m = new M;
console.log(m.aNumber) // 4.815162342
// default unique objects for Mixed types:
var schema = new Schema({ mixed: Schema.Types.Mixed });
schema.path('mixed').default(function () {
return {};
});
// if we don't use a function to return object literals for Mixed defaults,
// each document will receive a reference to the same object literal creating
// a "shared" object instance:
var schema = new Schema({ mixed: Schema.Types.Mixed });
schema.path('mixed').default({});
var M = db.model('M', schema);
var m1 = new M;
m1.mixed.added = 1;
console.log(m1.mixed); // { added: 1 }
var m2 = new M;
console.log(m2.mixed); // { added: 1 }
SchemaType.prototype.default = function (val) {
if (1 === arguments.length) {
this.defaultValue = typeof val === 'function'
? val
: this.cast(val);
return this;
} else if (arguments.length > 1) {
this.defaultValue = utils.args(arguments);
}
return this.defaultValue;
};
value
, callback
, scope
)Performs a validation of value
using the validators declared for this SchemaType.
SchemaType.prototype.doValidate = function (value, fn, scope) {
var err = false
, path = this.path
, count = this.validators.length;
if (!count) return fn(null);
var validate = function(ok, validatorProperties) {
if (err) return;
if (ok === undefined || ok) {
--count || fn(null);
} else {
err = new ValidatorError(validatorProperties);
fn(err);
}
};
var self = this;
this.validators.forEach(function (v) {
if (err) {
return;
}
var validator = v.validator;
var message = v.message;
var type = v.type;
var validatorProperties = utils.clone(v);
validatorProperties.path = path;
validatorProperties.value = value;
if (validator instanceof RegExp) {
validate(validator.test(value), validatorProperties);
} else if ('function' === typeof validator) {
if (value === undefined && !self.isRequired) {
validate(true, validatorProperties);
return;
}
if (2 === validator.length) {
validator.call(scope, value, function (ok) {
validate(ok, validatorProperties);
});
} else {
validate(validator.call(scope, value), validatorProperties);
}
}
});
};
value
, scope
)Performs a validation of value
using the validators declared for this SchemaType.
value
<T> scope
<Object> This method ignores the asynchronous validators.
SchemaType.prototype.doValidateSync = function (value, scope) {
var err = null
, path = this.path
, count = this.validators.length;
if (!count) return null;
var validate = function(ok, validatorProperties) {
if (err) return;
if (ok === undefined || ok) {
} else {
err = new ValidatorError(validatorProperties);
}
};
var self = this;
if (value === undefined && !self.isRequired) {
return null;
}
this.validators.forEach(function (v) {
var validator = v.validator;
var validatorProperties = utils.clone(v);
validatorProperties.path = path;
validatorProperties.value = value;
if (validator instanceof RegExp) {
validate(validator.test(value), validatorProperties);
} else if ('function' === typeof validator) {
// if not async validators
if (2 !== validator.length) {
validate(validator.call(scope, value), validatorProperties);
}
}
});
return err;
};
fn
)Adds a getter to this schematype.
fn
<Function> function dob (val) {
if (!val) return val;
return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear();
}
// defining within the schema
var s = new Schema({ born: { type: Date, get: dob })
// or by retreiving its SchemaType
var s = new Schema({ born: Date })
s.path('born').get(dob)
Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see.
Suppose you are storing credit card numbers and you want to hide everything except the last 4 digits to the mongoose user. You can do so by defining a getter in the following way:
function obfuscate (cc) {
return '****-****-****-' + cc.slice(cc.length-4, cc.length);
}
var AccountSchema = new Schema({
creditCardNumber: { type: String, get: obfuscate }
});
var Account = db.model('Account', AccountSchema);
Account.findById(id, function (err, found) {
console.log(found.creditCardNumber); // '****-****-****-1234'
});
Getters are also passed a second argument, the schematype on which the getter was defined. This allows for tailored behavior based on options passed in the schema.
function inspector (val, schematype) {
if (schematype.options.required) {
return schematype.path + ' is required';
} else {
return schematype.path + ' is not';
}
}
var VirusSchema = new Schema({
name: { type: String, required: true, get: inspector },
taxonomy: { type: String, get: inspector }
})
var Virus = db.model('Virus', VirusSchema);
Virus.findById(id, function (err, virus) {
console.log(virus.name); // name is required
console.log(virus.taxonomy); // taxonomy is not
})
SchemaType.prototype.get = function (fn) {
if ('function' != typeof fn)
throw new TypeError('A getter must be a function.');
this.getters.push(fn);
return this;
};
scope
, init
)Gets the default value
show codeSchemaType.prototype.getDefault = function (scope, init) {
var ret = 'function' === typeof this.defaultValue
? this.defaultValue.call(scope)
: this.defaultValue;
if (null !== ret && undefined !== ret) {
return this.cast(ret, scope, init);
} else {
return ret;
}
};
options
)Declares the index options for this schematype.
var s = new Schema({ name: { type: String, index: true })
var s = new Schema({ loc: { type: [Number], index: 'hashed' })
var s = new Schema({ loc: { type: [Number], index: '2d', sparse: true })
var s = new Schema({ loc: { type: [Number], index: { type: '2dsphere', sparse: true }})
var s = new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }})
Schema.path('my.path').index(true);
Schema.path('my.date').index({ expires: 60 });
Schema.path('my.path').index({ unique: true, sparse: true });
Indexes are created in the background by default. Specify background: false
to override.
SchemaType.prototype.index = function (options) {
this._index = options;
utils.expires(this._index);
return this;
};
required
, [message]
)Adds a required validator to this schematype.
var s = new Schema({ born: { type: Date, required: true })
// or with custom error message
var s = new Schema({ born: { type: Date, required: '{PATH} is required!' })
// or through the path API
Schema.path('name').required(true);
// with custom error messaging
Schema.path('name').required(true, 'grrr :( ');
SchemaType.prototype.required = function (required, message) {
if (false === required) {
this.validators = this.validators.filter(function (v) {
return v.validator != this.requiredValidator;
}, this);
this.isRequired = false;
return this;
}
var self = this;
this.isRequired = true;
this.requiredValidator = function (v) {
// in here, `this` refers to the validating document.
// no validation when this path wasn't selected in the query.
if ('isSelected' in this &&
!this.isSelected(self.path) &&
!this.isModified(self.path)) return true;
return (('function' === typeof required) && !required.apply(this)) ||
self.checkRequired(v, this);
}
if ('string' == typeof required) {
message = required;
required = undefined;
}
var msg = message || errorMessages.general.required;
this.validators.push({
validator: this.requiredValidator,
message: msg,
type: 'required'
});
return this;
};
val
)Sets default select()
behavior for this path.
val
<Boolean> Set to true
if this path should always be included in the results, false
if it should be excluded by default. This setting can be overridden at the query level.
T = db.model('T', new Schema({ x: { type: String, select: true }}));
T.find(..); // field x will always be selected ..
// .. unless overridden;
T.find().select('-x').exec(callback);
SchemaType.prototype.select = function select (val) {
this.selected = !! val;
return this;
};
fn
)Adds a setter to this schematype.
fn
<Function> function capitalize (val) {
if ('string' != typeof val) val = '';
return val.charAt(0).toUpperCase() + val.substring(1);
}
// defining within the schema
var s = new Schema({ name: { type: String, set: capitalize }})
// or by retreiving its SchemaType
var s = new Schema({ name: String })
s.path('name').set(capitalize)
Setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
Suppose you are implementing user registration for a website. Users provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.
You can set up email lower case normalization easily via a Mongoose setter.
function toLower (v) {
return v.toLowerCase();
}
var UserSchema = new Schema({
email: { type: String, set: toLower }
})
var User = db.model('User', UserSchema)
var user = new User({email: 'AVENUE@Q.COM'})
console.log(user.email); // 'avenue@q.com'
// or
var user = new User
user.email = 'Avenue@Q.com'
console.log(user.email) // 'avenue@q.com'
As you can see above, setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
NOTE: we could have also just used the built-in lowercase: true
SchemaType option instead of defining our own function.
new Schema({ email: { type: String, lowercase: true }})
Setters are also passed a second argument, the schematype on which the setter was defined. This allows for tailored behavior based on options passed in the schema.
function inspector (val, schematype) {
if (schematype.options.required) {
return schematype.path + ' is required';
} else {
return val;
}
}
var VirusSchema = new Schema({
name: { type: String, required: true, set: inspector },
taxonomy: { type: String, set: inspector }
})
var Virus = db.model('Virus', VirusSchema);
var v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' });
console.log(v.name); // name is required
console.log(v.taxonomy); // Parvovirinae
SchemaType.prototype.set = function (fn) {
if ('function' != typeof fn)
throw new TypeError('A setter must be a function.');
this.setters.push(fn);
return this;
};
bool
)Declares a sparse index.
bool
<Boolean> var s = new Schema({ name: { type: String, sparse: true })
Schema.path('name').index({ sparse: true });
SchemaType.prototype.sparse = function (bool) {
if (null == this._index || 'boolean' == typeof this._index) {
this._index = {};
} else if ('string' == typeof this._index) {
this._index = { type: this._index };
}
this._index.sparse = bool;
return this;
};
)Declares a full text index.
<bool> var s = new Schema({name : {type: String, text : true })
Schema.path('name').index({text : true});
SchemaType.prototype.text = function(bool) {
if (null == this._index || 'boolean' == typeof this._index) {
this._index = {};
} else if ('string' == typeof this._index) {
this._index = { type: this._index };
}
this._index.text = bool;
return this;
};
bool
)Declares an unique index.
bool
<Boolean> var s = new Schema({ name: { type: String, unique: true })
Schema.path('name').index({ unique: true });
NOTE: violating the constraint returns an E11000
error from MongoDB when saving, not a Mongoose validation error.
SchemaType.prototype.unique = function (bool) {
if (null == this._index || 'boolean' == typeof this._index) {
this._index = {};
} else if ('string' == typeof this._index) {
this._index = { type: this._index };
}
this._index.unique = bool;
return this;
};
obj
, [errorMsg]
, [type]
)Adds validator(s) for this document path.
Validators always receive the value to validate as their first argument and must return Boolean
. Returning false
means validation failed.
The error message argument is optional. If not passed, the default generic error message template will be used.
// make sure every value is equal to "something"
function validator (val) {
return val == 'something';
}
new Schema({ name: { type: String, validate: validator }});
// with a custom error message
var custom = [validator, 'Uh oh, {PATH} does not equal "something".']
new Schema({ name: { type: String, validate: custom }});
// adding many validators at a time
var many = [
{ validator: validator, msg: 'uh oh' }
, { validator: anotherValidator, msg: 'failed' }
]
new Schema({ name: { type: String, validate: many }});
// or utilizing SchemaType methods directly:
var schema = new Schema({ name: 'string' });
schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`');
From the examples above, you may have noticed that error messages support baseic templating. There are a few other template keywords besides {PATH}
and {VALUE}
too. To find out more, details are available here
Passing a validator function that receives two arguments tells mongoose that the validator is an asynchronous validator. The first argument passed to the validator function is the value being validated. The second argument is a callback function that must called when you finish validating the value and passed either true
or false
to communicate either success or failure respectively.
schema.path('name').validate(function (value, respond) {
doStuff(value, function () {
...
respond(false); // validation failed
})
}, '{PATH} failed validation.');
You might use asynchronous validators to retreive other documents from the database to validate against or to meet other I/O bound validation needs.
Validation occurs pre('save')
or whenever you manually execute document#validate.
If validation fails during pre('save')
and no callback was passed to receive the error, an error
event will be emitted on your Models associated db connection, passing the validation error object along.
var conn = mongoose.createConnection(..);
conn.on('error', handleError);
var Product = conn.model('Product', yourSchema);
var dvd = new Product(..);
dvd.save(); // emits error on the `conn` above
If you desire handling these errors at the Model level, attach an error
listener to your Model and the event will instead be emitted there.
// registering an error listener on the Model lets us handle errors more locally
Product.on('error', handleError);
SchemaType.prototype.validate = function (obj, message, type) {
if ('function' == typeof obj || obj && 'RegExp' === utils.getFunctionName(obj.constructor)) {
var properties;
if (message instanceof Object && !type) {
properties = utils.clone(message);
if (!properties.message) {
properties.message = properties.msg;
}
properties.validator = obj;
} else {
if (!message) message = errorMessages.general.default;
if (!type) type = 'user defined';
properties = { message: message, type: type, validator: obj };
}
this.validators.push(properties);
return this;
}
var i
, length
, arg;
for (i=0, length=arguments.length; i<length; i++) {
arg = arguments[i];
if (!(arg && 'Object' === utils.getFunctionName(arg.constructor))) {
var msg = 'Invalid validator. Received (' + typeof arg + ') '
+ arg
+ '. See http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate';
throw new Error(msg);
}
this.validate(arg.validator, arg);
}
return this;
};
self
, value
, doc
, init
)Determines if value is a valid Reference.
show codeSchemaType._isRef = function (self, value, doc, init) {
// fast path
var ref = init && self.options && self.options.ref;
if (!ref && doc && doc.$__fullPath) {
// checks for
// - this populated with adhoc model and no ref was set in schema OR
// - setting / pushing values after population
var path = doc.$__fullPath(self.path);
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
ref = owner.populated(path);
}
if (ref) {
if (null == value) return true;
if (!Buffer.isBuffer(value) && // buffers are objects too
'Binary' != value._bsontype // raw binary value from the db
&& utils.isObject(value) // might have deselected _id in population query
) {
return true;
}
}
return false;
};
self
<SchemaType> value
<Object> doc
<Document> init
<Boolean> key
, cast
, options
)Array SchemaType constructor
key
<String> cast
<SchemaType> options
<Object> function SchemaArray (key, cast, options) {
if (cast) {
var castOptions = {};
if ('Object' === utils.getFunctionName(cast.constructor)) {
if (cast.type) {
// support { type: Woot }
castOptions = utils.clone(cast); // do not alter user arguments
delete castOptions.type;
cast = cast.type;
} else {
cast = Mixed;
}
}
// support { type: 'String' }
var name = 'string' == typeof cast
? cast
: utils.getFunctionName(cast);
var caster = name in Types
? Types[name]
: cast;
this.casterConstructor = caster;
this.caster = new caster(null, castOptions);
if (!(this.caster instanceof EmbeddedDoc)) {
this.caster.path = key;
}
}
SchemaType.call(this, key, options, 'Array');
var self = this
, defaultArr
, fn;
if (this.defaultValue) {
defaultArr = this.defaultValue;
fn = 'function' == typeof defaultArr;
}
this.default(function(){
var arr = fn ? defaultArr() : defaultArr || [];
return new MongooseArray(arr, self.path, this);
});
}
value
, scope
)Overrides the getters application for the population special-case
show codeSchemaArray.prototype.applyGetters = function (value, scope) {
if (this.caster.options && this.caster.options.ref) {
// means the object id was populated
return value;
}
return SchemaType.prototype.applyGetters.call(this, value, scope);
};
value
, doc
, init
)Casts values for set().
SchemaArray.prototype.cast = function (value, doc, init) {
if (Array.isArray(value)) {
if (!value.length && doc) {
var indexes = doc.schema.indexedPaths();
for (var i = 0, l = indexes.length; i < l; ++i) {
var pathIndex = indexes[i][0][this.path];
if ('2dsphere' === pathIndex || '2d' === pathIndex) {
return;
}
}
}
if (!(value && value.isMongooseArray)) {
value = new MongooseArray(value, this.path, doc);
}
if (this.caster) {
try {
for (var i = 0, l = value.length; i < l; i++) {
value[i] = this.caster.cast(value[i], doc, init);
}
} catch (e) {
// rethrow
throw new CastError(e.type, value, this.path);
}
}
return value;
} else {
// gh-2442: if we're loading this from the db and its not an array, mark
// the whole array as modified.
if (!!doc && !!init) {
doc.markModified(this.path);
}
return this.cast([value], doc, init);
}
};
$conditional
, [value]
)Casts values for queries.
$conditional
<String> [value]
<T> SchemaArray.prototype.castForQuery = function ($conditional, value) {
var handler
, val;
if (arguments.length === 2) {
handler = this.$conditionalHandlers[$conditional];
if (!handler) {
throw new Error("Can't use " + $conditional + " with Array.");
}
val = handler.call(this, value);
} else {
val = $conditional;
var proto = this.casterConstructor.prototype;
var method = proto.castForQuery || proto.cast;
var caster = this.caster;
if (Array.isArray(val)) {
val = val.map(function (v) {
if (method) v = method.call(caster, v);
return isMongooseObject(v)
? v.toObject()
: v;
});
} else if (method) {
val = method.call(caster, val);
}
}
return val && isMongooseObject(val)
? val.toObject()
: val;
};
value
)Check required
value
<Array> SchemaArray.prototype.checkRequired = function (value) {
return !!(value && value.length);
};
This schema type's name, to defend against minifiers that mangle
function names.
SchemaArray.schemaName = 'Array';
key
, cast
)Buffer SchemaType constructor
key
<String> cast
<SchemaType> function SchemaBuffer (key, options) {
SchemaType.call(this, key, options, 'Buffer');
}
value
, doc
, init
)Casts contents
show codeSchemaBuffer.prototype.cast = function (value, doc, init) {
if (SchemaType._isRef(this, value, doc, init)) {
// wait! we may need to cast this to a document
if (null == value) {
return value;
}
// lazy load
Document || (Document = require('./../document'));
if (value instanceof Document) {
value.$__.wasPopulated = true;
return value;
}
// setting a populated path
if (Buffer.isBuffer(value)) {
return value;
} else if (!utils.isObject(value)) {
throw new CastError('buffer', value, this.path);
}
// Handle the case where user directly sets a populated
// path to a plain object; cast to the Model used in
// the population query.
var path = doc.$__fullPath(this.path);
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
var pop = owner.populated(path, true);
var ret = new pop.options.model(value);
ret.$__.wasPopulated = true;
return ret;
}
// documents
if (value && value._id) {
value = value._id;
}
if (value && value.isMongooseBuffer) {
return value;
}
if (Buffer.isBuffer(value)) {
if (!value || !value.isMongooseBuffer) {
value = new MongooseBuffer(value, [this.path, doc]);
}
return value;
} else if (value instanceof Binary) {
var ret = new MongooseBuffer(value.value(true), [this.path, doc]);
ret.subtype(value.sub_type);
// do not override Binary subtypes. users set this
// to whatever they want.
return ret;
}
if (null === value) return value;
var type = typeof value;
if ('string' == type || 'number' == type || Array.isArray(value)) {
var ret = new MongooseBuffer(value, [this.path, doc]);
return ret;
}
throw new CastError('buffer', value, this.path);
};
$conditional
, [value]
)Casts contents for queries.
$conditional
<String> [value]
<T> SchemaBuffer.prototype.castForQuery = function ($conditional, val) {
var handler;
if (arguments.length === 2) {
handler = this.$conditionalHandlers[$conditional];
if (!handler)
throw new Error("Can't use " + $conditional + " with Buffer.");
return handler.call(this, val);
} else {
val = $conditional;
return this.cast(val).toObject();
}
};
Check required
show codeSchemaBuffer.prototype.checkRequired = function (value, doc) {
if (SchemaType._isRef(this, value, doc, true)) {
return null != value;
} else {
return !!(value && value.length);
}
};
This schema type's name, to defend against minifiers that mangle
function names.
SchemaBuffer.schemaName = 'Buffer';
path
, options
)Boolean SchemaType constructor.
function SchemaBoolean (path, options) {
SchemaType.call(this, path, options, 'Boolean');
}
value
)Casts to boolean
value
<Object> SchemaBoolean.prototype.cast = function (value) {
if (null === value) return value;
if ('0' === value) return false;
if ('true' === value) return true;
if ('false' === value) return false;
return !! value;
}
$conditional
, val
)Casts contents for queries.
$conditional
<String> val
<T> SchemaBoolean.prototype.castForQuery = function ($conditional, val) {
var handler;
if (2 === arguments.length) {
handler = SchemaBoolean.$conditionalHandlers[$conditional];
if (handler) {
return handler.call(this, val);
}
return this.cast(val);
}
return this.cast($conditional);
};
Required validator
show codeSchemaBoolean.prototype.checkRequired = function (value) {
return value === true || value === false;
};
This schema type's name, to defend against minifiers that mangle
function names.
SchemaBoolean.schemaName = 'Boolean';
key
, options
)Date SchemaType constructor.
function SchemaDate (key, options) {
SchemaType.call(this, key, options, 'Date');
}
value
)Casts to date
value
<Object> to castSchemaDate.prototype.cast = function (value) {
// If null or undefined
if (value == null || value === '')
return value;
if (value instanceof Date)
return value;
var date;
// support for timestamps
if (value instanceof Number || 'number' == typeof value
|| String(value) == Number(value))
date = new Date(Number(value));
// support for date strings
else if (value.toString)
date = new Date(value.toString());
if (date.toString() != 'Invalid Date')
return date;
throw new CastError('date', value, this.path);
};
$conditional
, [value]
)Casts contents for queries.
$conditional
<String> [value]
<T> SchemaDate.prototype.castForQuery = function ($conditional, val) {
var handler;
if (2 !== arguments.length) {
return this.cast($conditional);
}
handler = this.$conditionalHandlers[$conditional];
if (!handler) {
throw new Error("Can't use " + $conditional + " with Date.");
}
return handler.call(this, val);
};
Required validator for date
show codeSchemaDate.prototype.checkRequired = function (value) {
return value instanceof Date;
};
when
)Declares a TTL index (rounded to the nearest second) for Date types only.
This sets the expiresAfterSeconds
index option available in MongoDB >= 2.1.2.
This index type is only compatible with Date types.
// expire in 24 hours
new Schema({ createdAt: { type: Date, expires: 60*60*24 }});
expires
utilizes the ms
module from guille allowing us to use a friendlier syntax:
// expire in 24 hours
new Schema({ createdAt: { type: Date, expires: '24h' }});
// expire in 1.5 hours
new Schema({ createdAt: { type: Date, expires: '1.5h' }});
// expire in 7 days
var schema = new Schema({ createdAt: Date });
schema.path('createdAt').expires('7d');
SchemaDate.prototype.expires = function (when) {
if (!this._index || 'Object' !== this._index.constructor.name) {
this._index = {};
}
this._index.expires = when;
utils.expires(this._index);
return this;
};
maximum
, [message]
)Sets a maximum date validator.
var s = new Schema({ d: { type: Date, max: Date('2014-01-01') })
var M = db.model('M', s)
var m = new M({ d: Date('2014-12-08') })
m.save(function (err) {
console.error(err) // validator error
m.d = Date('2013-12-31');
m.save() // success
})
// custom error messages
// We can also use the special {MAX} token which will be replaced with the invalid value
var max = [Date('2014-01-01'), 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).'];
var schema = new Schema({ d: { type: Date, max: max })
var M = mongoose.model('M', schema);
var s= new M({ d: Date('2014-12-08') });
s.validate(function (err) {
console.log(String(err)) // ValidationError: The value of path `d` (2014-12-08) exceeds the limit (2014-01-01).
})
SchemaDate.prototype.max = function (value, message) {
if (this.maxValidator) {
this.validators = this.validators.filter(function(v){
return v.validator != this.maxValidator;
}, this);
}
if (value) {
var msg = message || errorMessages.Date.max;
msg = msg.replace(/{MAX}/, (value === Date.now ? 'Date.now()' : this.cast(value).toString()));
var self = this;
this.validators.push({
validator: this.maxValidator = function(val) {
var max = (value === Date.now ? value() : self.cast(value));
return val === null || val.valueOf() <= max.valueOf();
},
message: msg,
type: 'max'
});
}
return this;
};
value
, [message]
)Sets a minimum date validator.
var s = new Schema({ d: { type: Date, min: Date('1970-01-01') })
var M = db.model('M', s)
var m = new M({ d: Date('1969-12-31') })
m.save(function (err) {
console.error(err) // validator error
m.d = Date('2014-12-08');
m.save() // success
})
// custom error messages
// We can also use the special {MIN} token which will be replaced with the invalid value
var min = [Date('1970-01-01'), 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).'];
var schema = new Schema({ d: { type: Date, min: min })
var M = mongoose.model('M', schema);
var s= new M({ d: Date('1969-12-31') });
s.validate(function (err) {
console.log(String(err)) // ValidationError: The value of path `d` (1969-12-31) is before the limit (1970-01-01).
})
SchemaDate.prototype.min = function (value, message) {
if (this.minValidator) {
this.validators = this.validators.filter(function (v) {
return v.validator != this.minValidator;
}, this);
}
if (value) {
var msg = message || errorMessages.Date.min;
msg = msg.replace(/{MIN}/, (value === Date.now ? 'Date.now()' : this.cast(value).toString()));
var self = this;
this.validators.push({
validator: this.minValidator = function (val) {
var min = (value === Date.now ? value() : self.cast(value));
return val === null || val.valueOf() >= min.valueOf();
},
message: msg,
type: 'min'
});
}
return this;
};
This schema type's name, to defend against minifiers that mangle
function names.
SchemaDate.schemaName = 'Date';
key
, options
)ObjectId SchemaType constructor.
function ObjectId (key, options) {
SchemaType.call(this, key, options, 'ObjectID');
}
turnOn
)Adds an auto-generated ObjectId default if turnOn is true.
turnOn
<Boolean> auto generated ObjectId defaultsObjectId.prototype.auto = function (turnOn) {
if (turnOn) {
this.default(defaultId);
this.set(resetId)
}
return this;
};
value
, doc
, init
)Casts to ObjectId
show codeObjectId.prototype.cast = function (value, doc, init) {
if (SchemaType._isRef(this, value, doc, init)) {
// wait! we may need to cast this to a document
if (null == value) {
return value;
}
// lazy load
Document || (Document = require('./../document'));
if (value instanceof Document) {
value.$__.wasPopulated = true;
return value;
}
// setting a populated path
if (value instanceof oid) {
return value;
} else if (Buffer.isBuffer(value) || !utils.isObject(value)) {
throw new CastError('ObjectId', value, this.path);
}
// Handle the case where user directly sets a populated
// path to a plain object; cast to the Model used in
// the population query.
var path = doc.$__fullPath(this.path);
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
var pop = owner.populated(path, true);
var ret = new pop.options.model(value);
ret.$__.wasPopulated = true;
return ret;
}
// If null or undefined
if (value == null) return value;
if (value instanceof oid)
return value;
if (value._id) {
if (value._id instanceof oid) {
return value._id;
}
if (value._id.toString instanceof Function) {
try {
return oid.createFromHexString(value._id.toString());
} catch(e) {}
}
}
if (value.toString instanceof Function) {
try {
return oid.createFromHexString(value.toString());
} catch (err) {
throw new CastError('ObjectId', value, this.path);
}
}
throw new CastError('ObjectId', value, this.path);
};
$conditional
, [val]
)Casts contents for queries.
$conditional
<String> [val]
<T> ObjectId.prototype.castForQuery = function ($conditional, val) {
var handler;
if (arguments.length === 2) {
handler = this.$conditionalHandlers[$conditional];
if (!handler)
throw new Error("Can't use " + $conditional + " with ObjectId.");
return handler.call(this, val);
} else {
return this.cast($conditional);
}
};
Check required
show codeObjectId.prototype.checkRequired = function checkRequired (value, doc) {
if (SchemaType._isRef(this, value, doc, true)) {
return null != value;
} else {
return value instanceof oid;
}
};
This schema type's name, to defend against minifiers that mangle
function names.
ObjectId.schemaName = 'ObjectId';
key
, options
)Number SchemaType constructor.
function SchemaNumber (key, options) {
SchemaType.call(this, key, options, 'Number');
}
value
, doc
, init
)Casts to number
SchemaNumber.prototype.cast = function (value, doc, init) {
if (SchemaType._isRef(this, value, doc, init)) {
// wait! we may need to cast this to a document
if (null == value) {
return value;
}
// lazy load
Document || (Document = require('./../document'));
if (value instanceof Document) {
value.$__.wasPopulated = true;
return value;
}
// setting a populated path
if ('number' == typeof value) {
return value;
} else if (Buffer.isBuffer(value) || !utils.isObject(value)) {
throw new CastError('number', value, this.path);
}
// Handle the case where user directly sets a populated
// path to a plain object; cast to the Model used in
// the population query.
var path = doc.$__fullPath(this.path);
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
var pop = owner.populated(path, true);
var ret = new pop.options.model(value);
ret.$__.wasPopulated = true;
return ret;
}
var val = value && value._id
? value._id // documents
: value;
if (!isNaN(val)){
if (null === val) return val;
if ('' === val) return null;
if ('string' == typeof val) val = Number(val);
if (val instanceof Number) return val
if ('number' == typeof val) return val;
if (val.toString && !Array.isArray(val) &&
val.toString() == Number(val)) {
return new Number(val)
}
}
throw new CastError('number', value, this.path);
};
$conditional
, [value]
)Casts contents for queries.
$conditional
<String> [value]
<T> SchemaNumber.prototype.castForQuery = function ($conditional, val) {
var handler;
if (arguments.length === 2) {
handler = this.$conditionalHandlers[$conditional];
if (!handler)
throw new Error("Can't use " + $conditional + " with Number.");
return handler.call(this, val);
} else {
val = this.cast($conditional);
return val == null ? val : val
}
};
Required validator for number
show codeSchemaNumber.prototype.checkRequired = function checkRequired (value, doc) {
if (SchemaType._isRef(this, value, doc, true)) {
return null != value;
} else {
return typeof value == 'number' || value instanceof Number;
}
};
maximum
, [message]
)Sets a maximum number validator.
var s = new Schema({ n: { type: Number, max: 10 })
var M = db.model('M', s)
var m = new M({ n: 11 })
m.save(function (err) {
console.error(err) // validator error
m.n = 10;
m.save() // success
})
// custom error messages
// We can also use the special {MAX} token which will be replaced with the invalid value
var max = [10, 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).'];
var schema = new Schema({ n: { type: Number, max: max })
var M = mongoose.model('Measurement', schema);
var s= new M({ n: 4 });
s.validate(function (err) {
console.log(String(err)) // ValidationError: The value of path `n` (4) exceeds the limit (10).
})
SchemaNumber.prototype.max = function (value, message) {
if (this.maxValidator) {
this.validators = this.validators.filter(function(v){
return v.validator != this.maxValidator;
}, this);
}
if (null != value) {
var msg = message || errorMessages.Number.max;
msg = msg.replace(/{MAX}/, value);
this.validators.push({
validator: this.maxValidator = function(v) {
return v === null || v <= value;
},
message: msg,
type: 'max'
});
}
return this;
};
value
, [message]
)Sets a minimum number validator.
var s = new Schema({ n: { type: Number, min: 10 })
var M = db.model('M', s)
var m = new M({ n: 9 })
m.save(function (err) {
console.error(err) // validator error
m.n = 10;
m.save() // success
})
// custom error messages
// We can also use the special {MIN} token which will be replaced with the invalid value
var min = [10, 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).'];
var schema = new Schema({ n: { type: Number, min: min })
var M = mongoose.model('Measurement', schema);
var s= new M({ n: 4 });
s.validate(function (err) {
console.log(String(err)) // ValidationError: The value of path `n` (4) is beneath the limit (10).
})
SchemaNumber.prototype.min = function (value, message) {
if (this.minValidator) {
this.validators = this.validators.filter(function (v) {
return v.validator != this.minValidator;
}, this);
}
if (null != value) {
var msg = message || errorMessages.Number.min;
msg = msg.replace(/{MIN}/, value);
this.validators.push({
validator: this.minValidator = function (v) {
return v === null || v >= value;
},
message: msg,
type: 'min'
});
}
return this;
};
This schema type's name, to defend against minifiers that mangle
function names.
SchemaNumber.schemaName = 'Number';
path
, options
)Mixed SchemaType constructor.
function Mixed (path, options) {
if (options && options.default) {
var def = options.default;
if (Array.isArray(def) && 0 === def.length) {
// make sure empty array defaults are handled
options.default = Array;
} else if (!options.shared &&
utils.isObject(def) &&
0 === Object.keys(def).length) {
// prevent odd "shared" objects between documents
options.default = function () {
return {}
}
}
}
SchemaType.call(this, path, options, 'Mixed');
}
value
)Casts val
for Mixed.
value
<Object> to castthis is a no-op
Mixed.prototype.cast = function (val) {
return val;
};
$cond
, [val]
)Casts contents for queries.
$cond
<String> [val]
<T> Mixed.prototype.castForQuery = function ($cond, val) {
if (arguments.length === 2) return val;
return $cond;
};
Required validator
show codeMixed.prototype.checkRequired = function (val) {
return (val !== undefined) && (val !== null);
};
This schema type's name, to defend against minifiers that mangle
function names.
Mixed.schemaName = 'Mixed';
key
, schema
, options
)SubdocsArray SchemaType constructor
function DocumentArray (key, schema, options) {
// compile an embedded document for this schema
function EmbeddedDocument () {
this.$__setSchema(schema);
// apply methods
for (var i in schema.methods) {
this[i] = schema.methods[i];
}
Subdocument.apply(this, arguments);
}
EmbeddedDocument.prototype = Subdocument.prototype;
EmbeddedDocument.schema = schema;
// apply statics
for (var i in schema.statics)
EmbeddedDocument[i] = schema.statics[i];
EmbeddedDocument.options = options;
this.schema = schema;
ArrayType.call(this, key, EmbeddedDocument, options);
this.schema = schema;
var path = this.path;
var fn = this.defaultValue;
this.default(function(){
var arr = fn.call(this);
if (!Array.isArray(arr)) arr = [arr];
return new MongooseDocumentArray(arr, path, this);
});
}
value
, document
)Casts contents
show codeDocumentArray.prototype.cast = function (value, doc, init, prev) {
var selected
, subdoc
, i
if (!Array.isArray(value)) {
// gh-2442 mark whole array as modified if we're initializing a doc from
// the db and the path isn't an array in the document
if (!!doc && init) {
doc.markModified(this.path);
}
return this.cast([value], doc, init, prev);
}
if (!(value && value.isMongooseDocumentArray)) {
value = new MongooseDocumentArray(value, this.path, doc);
if (prev && prev._handlers) {
for (var key in prev._handlers) {
doc.removeListener(key, prev._handlers[key]);
}
}
}
i = value.length;
while (i--) {
if (!(value[i] instanceof Subdocument) && value[i]) {
if (init) {
selected || (selected = scopePaths(this, doc.$__.selected, init));
subdoc = new this.casterConstructor(null, value, true, selected);
value[i] = subdoc.init(value[i]);
} else {
try {
subdoc = prev.id(value[i]._id);
} catch(e) {}
if (prev && subdoc) {
// handle resetting doc with existing id but differing data
// doc.array = [{ doc: 'val' }]
subdoc.set(value[i]);
} else {
subdoc = new this.casterConstructor(value[i], value);
}
// if set() is hooked it will have no return value
// see gh-746
value[i] = subdoc;
}
}
}
return value;
}
Performs local validations first, then validations on each embedded doc
show codeDocumentArray.prototype.doValidate = function (array, fn, scope) {
SchemaType.prototype.doValidate.call(this, array, function (err) {
if (err) return fn(err);
var count = array && array.length;
var error;
if (!count) return fn();
// handle sparse arrays, do not use array.forEach which does not
// iterate over sparse elements yet reports array.length including
// them :(
for (var i = 0, len = count; i < len; ++i) {
// sidestep sparse entries
var doc = array[i];
if (!doc) {
--count || fn(errors);
continue;
}
doc.validate(function (err) {
if (err) {
error = err;
}
--count || fn(error);
});
}
}, scope);
};
Performs local validations first, then validations on each embedded doc.
This method ignores the asynchronous validators.
DocumentArray.prototype.doValidateSync = function (array, scope) {
var schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope);
if (schemaTypeError) return schemaTypeError;
var count = array && array.length
, resultError = null;
if (!count) return;
// handle sparse arrays, do not use array.forEach which does not
// iterate over sparse elements yet reports array.length including
// them :(
for (var i = 0, len = count; i < len; ++i) {
// only first error
if ( resultError ) break;
// sidestep sparse entries
var doc = array[i];
if (!doc) continue;
var subdocValidateError = doc.validateSync();
if (subdocValidateError) {
resultError = subdocValidateError;
}
}
return resultError;
};
This schema type's name, to defend against minifiers that mangle
function names.
DocumentArray.schemaName = 'DocumentArray';
key
, options
)String SchemaType constructor.
function SchemaString (key, options) {
this.enumValues = [];
this.regExp = null;
SchemaType.call(this, key, options, 'String');
};
Casts to String
show codeSchemaString.prototype.cast = function (value, doc, init) {
if (SchemaType._isRef(this, value, doc, init)) {
// wait! we may need to cast this to a document
if (null == value) {
return value;
}
// lazy load
Document || (Document = require('./../document'));
if (value instanceof Document) {
value.$__.wasPopulated = true;
return value;
}
// setting a populated path
if ('string' == typeof value) {
return value;
} else if (Buffer.isBuffer(value) || !utils.isObject(value)) {
throw new CastError('string', value, this.path);
}
// Handle the case where user directly sets a populated
// path to a plain object; cast to the Model used in
// the population query.
var path = doc.$__fullPath(this.path);
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
var pop = owner.populated(path, true);
var ret = new pop.options.model(value);
ret.$__.wasPopulated = true;
return ret;
}
// If null or undefined
if (value == null) {
return value;
}
if ('undefined' !== typeof value) {
// handle documents being passed
if (value._id && 'string' == typeof value._id) {
return value._id;
}
if (value.toString) {
return value.toString();
}
}
throw new CastError('string', value, this.path);
};
$conditional
, [val]
)Casts contents for queries.
$conditional
<String> [val]
<T> SchemaString.prototype.castForQuery = function ($conditional, val) {
var handler;
if (arguments.length === 2) {
handler = this.$conditionalHandlers[$conditional];
if (!handler)
throw new Error("Can't use " + $conditional + " with String.");
return handler.call(this, val);
} else {
val = $conditional;
if (val instanceof RegExp) return val;
return this.cast(val);
}
};
value
)Check required
show codeSchemaString.prototype.checkRequired = function checkRequired (value, doc) {
if (SchemaType._isRef(this, value, doc, true)) {
return null != value;
} else {
return (value instanceof String || typeof value == 'string') && value.length;
}
};
[args...]
)Adds an enum validator
var states = 'opening open closing closed'.split(' ')
var s = new Schema({ state: { type: String, enum: states }})
var M = db.model('M', s)
var m = new M({ state: 'invalid' })
m.save(function (err) {
console.error(String(err)) // ValidationError: `invalid` is not a valid enum value for path `state`.
m.state = 'open'
m.save(callback) // success
})
// or with custom error messages
var enu = {
values: 'opening open closing closed'.split(' '),
message: 'enum validator failed for path `{PATH}` with value `{VALUE}`'
}
var s = new Schema({ state: { type: String, enum: enu })
var M = db.model('M', s)
var m = new M({ state: 'invalid' })
m.save(function (err) {
console.error(String(err)) // ValidationError: enum validator failed for path `state` with value `invalid`
m.state = 'open'
m.save(callback) // success
})
SchemaString.prototype.enum = function () {
if (this.enumValidator) {
this.validators = this.validators.filter(function(v) {
return v.validator != this.enumValidator;
}, this);
this.enumValidator = false;
}
if (undefined === arguments[0] || false === arguments[0]) {
return this;
}
var values;
var errorMessage;
if (utils.isObject(arguments[0])) {
values = arguments[0].values;
errorMessage = arguments[0].message;
} else {
values = arguments;
errorMessage = errorMessages.String.enum;
}
for (var i = 0; i < values.length; i++) {
if (undefined !== values[i]) {
this.enumValues.push(this.cast(values[i]));
}
}
var vals = this.enumValues;
this.enumValidator = function (v) {
return undefined === v || ~vals.indexOf(v);
};
this.validators.push({ validator: this.enumValidator, message: errorMessage, type: 'enum' });
return this;
};
Adds a lowercase setter.
var s = new Schema({ email: { type: String, lowercase: true }})
var M = db.model('M', s);
var m = new M({ email: 'SomeEmail@example.COM' });
console.log(m.email) // someemail@example.com
SchemaString.prototype.lowercase = function () {
return this.set(function (v, self) {
if ('string' != typeof v) v = self.cast(v)
if (v) return v.toLowerCase();
return v;
});
};
regExp
, [message]
)Sets a regexp validator.
Any value that does not pass regExp
.test(val) will fail validation.
var s = new Schema({ name: { type: String, match: /^a/ }})
var M = db.model('M', s)
var m = new M({ name: 'I am invalid' })
m.validate(function (err) {
console.error(String(err)) // "ValidationError: Path `name` is invalid (I am invalid)."
m.name = 'apples'
m.validate(function (err) {
assert.ok(err) // success
})
})
// using a custom error message
var match = [ /\.html$/, "That file doesn't end in .html ({VALUE})" ];
var s = new Schema({ file: { type: String, match: match }})
var M = db.model('M', s);
var m = new M({ file: 'invalid' });
m.validate(function (err) {
console.log(String(err)) // "ValidationError: That file doesn't end in .html (invalid)"
})
Empty strings, undefined
, and null
values always pass the match validator. If you require these values, enable the required
validator also.
var s = new Schema({ name: { type: String, match: /^a/, required: true }})
SchemaString.prototype.match = function match (regExp, message) {
// yes, we allow multiple match validators
var msg = message || errorMessages.String.match;
var matchValidator = function(v) {
var ret = ((null != v && '' !== v)
? regExp.test(v)
: true);
return ret;
};
this.validators.push({ validator: matchValidator, message: msg, type: 'regexp' });
return this;
};
value
, [message]
)Sets a maximum length validator.
var schema = new Schema({ postalCode: { type: String, maxlength: 9 })
var Address = db.model('Address', schema)
var address = new Address({ postalCode: '9512512345' })
address.save(function (err) {
console.error(err) // validator error
address.postalCode = '95125';
address.save() // success
})
// custom error messages
// We can also use the special {MAXLENGTH} token which will be replaced with the invalid value
var maxlength = [10, 'The value of path `{PATH}` (`{VALUE}`) exceeds the maximum allowed length ({MAXLENGTH}).'];
var schema = new Schema({ postalCode: { type: String, maxlength: maxlength })
var Address = mongoose.model('Address', schema);
var address = new Address({ postalCode: '9512512345' });
address.validate(function (err) {
console.log(String(err)) // ValidationError: The value of path `postalCode` (`9512512345`) exceeds the maximum allowed length (10).
})
SchemaString.prototype.maxlength = function (value, message) {
if (this.maxlengthValidator) {
this.validators = this.validators.filter(function(v){
return v.validator != this.maxlengthValidator;
}, this);
}
if (null != value) {
var msg = message || errorMessages.String.maxlength;
msg = msg.replace(/{MAXLENGTH}/, value);
this.validators.push({
validator: this.maxlengthValidator = function(v) {
return v === null || v.length <= value;
},
message: msg,
type: 'maxlength'
});
}
return this;
};
value
, [message]
)Sets a minimum length validator.
var schema = new Schema({ postalCode: { type: String, minlength: 5 })
var Address = db.model('Address', schema)
var address = new Address({ postalCode: '9512' })
address.save(function (err) {
console.error(err) // validator error
address.postalCode = '95125';
address.save() // success
})
// custom error messages
// We can also use the special {MINLENGTH} token which will be replaced with the invalid value
var minlength = [10, 'The value of path `{PATH}` (`{VALUE}`) is shorter than the minimum length ({MINLENGTH}).'];
var schema = new Schema({ postalCode: { type: String, minlength: minlength })
var Address = mongoose.model('Address', schema);
var address = new Address({ postalCode: '9512' });
s.validate(function (err) {
console.log(String(err)) // ValidationError: The value of path `postalCode` (`9512`) is shorter than the minimum length (5).
})
SchemaString.prototype.minlength = function (value, message) {
if (this.minlengthValidator) {
this.validators = this.validators.filter(function (v) {
return v.validator != this.minlengthValidator;
}, this);
}
if (null != value) {
var msg = message || errorMessages.String.minlength;
msg = msg.replace(/{MINLENGTH}/, value);
this.validators.push({
validator: this.minlengthValidator = function (v) {
return v === null || v.length >= value;
},
message: msg,
type: 'minlength'
});
}
return this;
};
Adds a trim setter.
The string value will be trimmed when set.
var s = new Schema({ name: { type: String, trim: true }})
var M = db.model('M', s)
var string = ' some name '
console.log(string.length) // 11
var m = new M({ name: string })
console.log(m.name.length) // 9
SchemaString.prototype.trim = function () {
return this.set(function (v, self) {
if ('string' != typeof v) v = self.cast(v)
if (v) return v.trim();
return v;
});
};
Adds an uppercase setter.
var s = new Schema({ caps: { type: String, uppercase: true }})
var M = db.model('M', s);
var m = new M({ caps: 'an example' });
console.log(m.caps) // AN EXAMPLE
SchemaString.prototype.uppercase = function () {
return this.set(function (v, self) {
if ('string' != typeof v) v = self.cast(v)
if (v) return v.toUpperCase();
return v;
});
};
This schema type's name, to defend against minifiers that mangle
function names.
SchemaString.schemaName = 'String';
query
, [options]
)Provides a Node.js 0.8 style ReadStream interface for Queries.
data
: emits a single Mongoose document
error
: emits when an error occurs during streaming. This will emit before the close
event.
close
: emits when the stream reaches the end of the cursor or an error occurs, or the stream is manually destroy
ed. After this event, no more events are emitted.
var stream = Model.find().stream();
stream.on('data', function (doc) {
// do something with the mongoose document
}).on('error', function (err) {
// handle the error
}).on('close', function () {
// the stream is closed
});
The stream interface allows us to simply "plug-in" to other Node.js 0.8 style write streams.
Model.where('created').gte(twoWeeksAgo).stream().pipe(writeStream);
transform
: optional function which accepts a mongoose document. The return value of the function will be emitted on data
.// JSON.stringify all documents before emitting
var stream = Thing.find().stream({ transform: JSON.stringify });
stream.pipe(writeStream);
NOTE: plugging into an HTTP response will *not* work out of the box. Those streams expect only strings or buffers to be emitted, so first formatting our documents as strings/buffers is necessary.
NOTE: these streams are Node.js 0.8 style read streams which differ from Node.js 0.10 style. Node.js 0.10 streams are not well tested yet and are not guaranteed to work.
function QueryStream (query, options) {
Stream.call(this);
this.query = query;
this.readable = true;
this.paused = false;
this._cursor = null;
this._destroyed = null;
this._fields = null;
this._buffer = null;
this._inline = T_INIT;
this._running = false;
this._transform = options && 'function' == typeof options.transform
? options.transform
: K;
// give time to hook up events
var self = this;
process.nextTick(function () {
self._init();
});
}
Pulls the next doc from the cursor.
show codeQueryStream.prototype.__next = function () {
if (this.paused || this._destroyed)
return this._running = false;
var self = this;
self._inline = T_INIT;
self._cursor.nextObject(function cursorcb (err, doc) {
self._onNextObject(err, doc);
});
// if onNextObject() was already called in this tick
// return ourselves to the trampoline.
if (T_CONT === this._inline) {
return true;
} else {
// onNextObject() hasn't fired yet. tell onNextObject
// that its ok to call _next b/c we are not within
// the trampoline anymore.
this._inline = T_IDLE;
}
}
Initializes the query.
show codeQueryStream.prototype._init = function () {
if (this._destroyed) return;
var query = this.query
, model = query.model
, options = query._optionsForExec(model)
, self = this
try {
query.cast(model);
} catch (err) {
return self.destroy(err);
}
self._fields = utils.clone(query._fields);
options.fields = query._castFields(self._fields);
model.collection.find(query._conditions, options, function (err, cursor) {
if (err) return self.destroy(err);
self._cursor = cursor;
self._next();
});
}
Trampoline for pulling the next doc from cursor.
show codeQueryStream.prototype._next = function _next () {
if (this.paused || this._destroyed) {
return this._running = false;
}
this._running = true;
if (this._buffer && this._buffer.length) {
var arg;
while (!this.paused && !this._destroyed && (arg = this._buffer.shift())) {
this._onNextObject.apply(this, arg);
}
}
// avoid stack overflows with large result sets.
// trampoline instead of recursion.
while (this.__next()) {}
}
err
, doc
)Transforms raw doc
s returned from the cursor into a model instance.
QueryStream.prototype._onNextObject = function _onNextObject (err, doc) {
if (this._destroyed) return;
if (this.paused) {
this._buffer || (this._buffer = []);
this._buffer.push([err, doc]);
return this._running = false;
}
if (err) return this.destroy(err);
// when doc is null we hit the end of the cursor
if (!doc) {
this.emit('end');
return this.destroy();
}
var opts = this.query._mongooseOptions;
if (!opts.populate) {
return true === opts.lean
? emit(this, doc)
: createAndEmit(this, doc);
}
var self = this;
var pop = helpers.preparePopulationOptionsMQ(self.query, self.query._mongooseOptions);
self.query.model.populate(doc, pop, function (err, doc) {
if (err) return self.destroy(err);
return true === opts.lean
? emit(self, doc)
: createAndEmit(self, doc);
})
}
function createAndEmit (self, doc) {
var instance = helpers.createModel(self.query.model, doc, self._fields);
instance.init(doc, function (err) {
if (err) return self.destroy(err);
emit(self, instance);
});
}
[err]
)Destroys the stream, closing the underlying cursor. No more events will be emitted.
[err]
<Error> QueryStream.prototype.destroy = function (err) {
if (this._destroyed) return;
this._destroyed = true;
this._running = false;
this.readable = false;
if (this._cursor) {
this._cursor.close();
}
if (err) {
this.emit('error', err);
}
this.emit('close');
}
Pauses this stream.
show codeQueryStream.prototype.pause = function () {
this.paused = true;
}
Pipes this query stream into another stream. This method is inherited from NodeJS Streams.
query.stream().pipe(writeStream [, options])
Resumes this stream.
show codeQueryStream.prototype.resume = function () {
this.paused = false;
if (!this._cursor) {
// cannot start if not initialized
return;
}
// are we within the trampoline?
if (T_INIT === this._inline) {
return;
}
if (!this._running) {
// outside QueryStream control, need manual restart
return this._next();
}
}
Flag stating whether or not this stream is paused.
show codeQueryStream.prototype.paused;
// trampoline flags
var T_INIT = 0;
var T_IDLE = 1;
var T_CONT = 2;
Flag stating whether or not this stream is readable.
show codeQueryStream.prototype.readable;
msg
)MongooseError constructor
msg
<String> Error messagefunction MongooseError (msg) {
Error.call(this);
Error.captureStackTrace && Error.captureStackTrace(this, arguments.callee);
this.message = msg;
this.name = 'MongooseError';
};
The default built-in validator error messages.
show codeMongooseError.messages = require('./error/messages');
// backward compat
MongooseError.Messages = MongooseError.messages;
fn
)Promise constructor.
fn
<Function> a function which will be called when the promise is resolved that accepts `fn(err, ...){}` as signatureerr
: Emits when the promise is rejected
complete
: Emits when the promise is fulfilled
Promises are returned from executed queries. Example:
var query = Candy.find({ bar: true });
var promise = query.exec();
function Promise (fn) {
MPromise.call(this, fn);
}
listener
)Adds a single function as a listener to both err and complete.
listener
<Function> It will be executed with traditional node.js argument position when the promise is resolved.
promise.addBack(function (err, args...) {
if (err) return handleError(err);
console.log('success');
})
Alias of mpromise#onResolve.
Deprecated. Use onResolve
instead.
listener
)Adds a listener to the complete
(success) event.
listener
<Function> Alias of mpromise#onFulfill.
Deprecated. Use onFulfill
instead.
listener
)Adds a listener to the err
(rejected) event.
listener
<Function> Alias of mpromise#onReject.
Deprecated. Use onReject
instead.
args
)Fulfills this promise with passed arguments.
args
<T> Alias of mpromise#fulfill.
Deprecated. Use fulfill
instead.
Signifies that this promise was the last in a chain of then()s
: if a handler passed to the call to then
which produced this promise throws, the exception will go uncaught.
var p = new Promise;
p.then(function(){ throw new Error('shucks') });
setTimeout(function () {
p.fulfill();
// error was caught and swallowed by the promise returned from
// p.then(). we either have to always register handlers on
// the returned promises or we can do the following...
}, 10);
// this time we use .end() which prevents catching thrown errors
var p = new Promise;
var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <--
setTimeout(function () {
p.fulfill(); // throws "shucks"
}, 10);
err
)Rejects this promise with err
.
If the promise has already been fulfilled or rejected, not action is taken.
Differs from #reject by first casting err
to an Error
if it is not instanceof Error
.
Promise.prototype.error = function (err) {
if (!(err instanceof Error)) {
if (err instanceof Object) {
err = util.inspect(err);
}
err = new Error(err);
}
return this.reject(err);
}
args
)Fulfills this promise with passed arguments.
args
<T> event
, listener
)Adds listener
to the event
.
If event
is either the success or failure event and the event has already been emitted, thelistener
is called immediately and passed the results of the original emitted event.
reason
)Rejects this promise with reason
.
If the promise has already been fulfilled or rejected, not action is taken.
[err]
, [val]
)Resolves this promise to a rejected state if err
is passed or a fulfilled state if no err
is passed.
If the promise has already been fulfilled or rejected, not action is taken.
err
will be cast to an Error if not already instanceof Error.
NOTE: overrides mpromise#resolve to provide error casting.
Promise.prototype.resolve = function (err) {
if (err) return this.error(err);
return this.fulfill.apply(this, Array.prototype.slice.call(arguments, 1));
};
onFulFill
, onReject
)Creates a new promise and returns it. If onFulfill
or onReject
are passed, they are added as SUCCESS/ERROR callbacks to this promise after the nextTick.
Conforms to promises/A+ specification.
var promise = Meetups.find({ tags: 'javascript' }).select('_id').exec();
promise.then(function (meetups) {
var ids = meetups.map(function (m) {
return m._id;
});
return People.find({ meetups: { $in: ids }).exec();
}).then(function (people) {
if (people.length < 10000) {
throw new Error('Too few people!!!');
} else {
throw new Error('Still need more people!!!');
}
}).then(null, function (err) {
assert.ok(err instanceof Error);
});
arr
, fn
)Executes a function on each element of an array (like _.each)
show codeexports.each = function(arr, fn) {
for (var i = 0; i < arr.length; ++i) {
fn(arr[i]);
}
};
to
, from
)merges to with a copy of from
show codeexports.mergeClone = function(to, from) {
var keys = Object.keys(from)
, i = keys.length
, key
while (i--) {
key = keys[i];
if ('undefined' === typeof to[key]) {
// make sure to retain key order here because of a bug handling the $each
// operator in mongodb 2.4.4
to[key] = exports.clone(from[key], { retainKeyOrder : 1});
} else {
if (exports.isObject(from[key])) {
exports.mergeClone(to[key], from[key]);
} else {
// make sure to retain key order here because of a bug handling the
// $each operator in mongodb 2.4.4
to[key] = exports.clone(from[key], { retainKeyOrder : 1});
}
}
}
};
Pluralization rules.
show codeexports.pluralization = [
[/(m)an$/gi, '$1en'],
[/(pe)rson$/gi, '$1ople'],
[/(child)$/gi, '$1ren'],
[/^(ox)$/gi, '$1en'],
[/(ax|test)is$/gi, '$1es'],
[/(octop|vir)us$/gi, '$1i'],
[/(alias|status)$/gi, '$1es'],
[/(bu)s$/gi, '$1ses'],
[/(buffal|tomat|potat)o$/gi, '$1oes'],
[/([ti])um$/gi, '$1a'],
[/sis$/gi, 'ses'],
[/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'],
[/(hive)$/gi, '$1s'],
[/([^aeiouy]|qu)y$/gi, '$1ies'],
[/(x|ch|ss|sh)$/gi, '$1es'],
[/(matr|vert|ind)ix|ex$/gi, '$1ices'],
[/([m|l])ouse$/gi, '$1ice'],
[/(kn|w|l)ife$/gi, '$1ives'],
[/(quiz)$/gi, '$1zes'],
[/s$/gi, 's'],
[/([^a-z])$/, '$1'],
[/$/gi, 's']
];
var rules = exports.pluralization;
These rules are applied while processing the argument to toCollectionName
.
Uncountable words.
show codeexports.uncountables = [
'advice',
'energy',
'excretion',
'digestion',
'cooperation',
'health',
'justice',
'labour',
'machinery',
'equipment',
'information',
'pollution',
'sewage',
'paper',
'money',
'species',
'series',
'rain',
'rice',
'fish',
'sheep',
'moose',
'deer',
'news',
'expertise',
'status',
'media'
];
var uncountables = exports.uncountables;
These words are applied while processing the argument to toCollectionName
.
definition
)Schema constructor.
definition
<Object> init
: Emitted after the schema is compiled into a Model
.
var child = new Schema({ name: String });
var schema = new Schema({ name: String, age: Number, children: [child] });
var Tree = mongoose.model('Tree', schema);
// setting schema options
new Schema({ name: String }, { _id: false, autoIndex: false })
minimize
: bool - controls document#toObject behavior when called manually - defaults to truenull
true
When nesting schemas, (children
in the example above), always declare the child schema first before passing it into its parent.
function Schema (obj, options) {
if (!(this instanceof Schema))
return new Schema(obj, options);
this.paths = {};
this.subpaths = {};
this.virtuals = {};
this.nested = {};
this.inherits = {};
this.callQueue = [];
this._indexes = [];
this.methods = {};
this.statics = {};
this.tree = {};
this._requiredpaths = undefined;
this.discriminatorMapping = undefined;
this._indexedpaths = undefined;
this.s = {
hooks: new Kareem(),
queryHooks: IS_QUERY_HOOK
};
this.options = this.defaultOptions(options);
// build paths
if (obj) {
this.add(obj);
}
// check if _id's value is a subdocument (gh-2276)
var _idSubDoc = obj && obj._id && utils.isObject(obj._id);
// ensure the documents get an auto _id unless disabled
var auto_id = !this.paths['_id'] && (!this.options.noId && this.options._id) && !_idSubDoc;
if (auto_id) {
this.add({ _id: {type: Schema.ObjectId, auto: true} });
}
// ensure the documents receive an id getter unless disabled
var autoid = !this.paths['id'] && (!this.options.noVirtualId && this.options.id);
if (autoid) {
this.virtual('id').get(idGetter);
}
this.pre('save', require('./model').prototype.$__preSave);
// adds updatedAt and createdAt timestamps to documents if enabled
var timestamps = this.options.timestamps;
if (timestamps) {
var createdAt = timestamps.createdAt || 'createdAt'
, updatedAt = timestamps.updatedAt || 'updatedAt'
, schemaAdditions = {};
schemaAdditions[updatedAt] = Date;
if (!this.paths[createdAt]) {
schemaAdditions[createdAt] = Date;
}
this.add(schemaAdditions);
this.pre('save', function (next) {
var defaultTimestamp = new Date();
if (!this[createdAt]){
this[createdAt] = auto_id ? this._id.getTimestamp() : defaultTimestamp;
}
this[updatedAt] = this.isNew ? this[createdAt] : defaultTimestamp;
next();
});
}
this.pre('validate', function(next) {
delete this.errors;
next();
});
}
obj
, prefix
)Adds key path / schema type pairs to this schema.
var ToySchema = new Schema;
ToySchema.add({ name: 'string', color: 'string', price: 'number' });
Schema.prototype.add = function add (obj, prefix) {
prefix = prefix || '';
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
if (null == obj[key]) {
throw new TypeError('Invalid value for schema path `'+ prefix + key +'`');
}
if (Array.isArray(obj[key]) && obj[key].length === 1 && null == obj[key][0]) {
throw new TypeError('Invalid value for schema Array path `'+ prefix + key +'`');
}
if (utils.isObject(obj[key]) && (!obj[key].constructor || 'Object' == utils.getFunctionName(obj[key].constructor)) && (!obj[key].type || obj[key].type.type)) {
if (Object.keys(obj[key]).length) {
// nested object { last: { name: String }}
this.nested[prefix + key] = true;
this.add(obj[key], prefix + key + '.');
} else {
this.path(prefix + key, obj[key]); // mixed type
}
} else {
this.path(prefix + key, obj[key]);
}
}
};
options
)Returns default options for this schema, merged with options
.
options
<Object> Schema.prototype.defaultOptions = function (options) {
if (options && false === options.safe) {
options.safe = { w: 0 };
}
if (options && options.safe && 0 === options.safe.w) {
// if you turn off safe writes, then versioning goes off as well
options.versionKey = false;
}
options = utils.options({
strict: true
, bufferCommands: true
, capped: false // { size, max, autoIndexId }
, versionKey: '__v'
, discriminatorKey: '__t'
, minimize: true
, autoIndex: null
, shardKey: null
, read: null
, validateBeforeSave: true
// the following are only applied at construction time
, noId: false // deprecated, use { _id: false }
, _id: true
, noVirtualId: false // deprecated, use { id: false }
, id: true
// , pluralization: true // only set this to override the global option
}, options);
if (options.read) {
options.read = utils.readPref(options.read);
}
return options;
}
fn
)Iterates the schemas paths similar to Array#forEach.
fn
<Function> callback functionThe callback is passed the pathname and schemaType as arguments on each iteration.
Schema.prototype.eachPath = function (fn) {
var keys = Object.keys(this.paths)
, len = keys.length;
for (var i = 0; i < len; ++i) {
fn(keys[i], this.paths[keys[i]]);
}
return this;
};
key
)Gets a schema option.
key
<String> option nameSchema.prototype.get = function (key) {
return this.options[key];
}
fields
, [options]
)Defines an index (most likely compound) for this schema.
schema.index({ first: 1, last: -1 })
Schema.prototype.index = function (fields, options) {
options || (options = {});
if (options.expires)
utils.expires(options);
this._indexes.push([fields, options]);
return this;
};
Returns indexes from fields and schema-level indexes (cached).
Schema.prototype.indexedPaths = function indexedPaths () {
if (this._indexedpaths) return this._indexedpaths;
return this._indexedpaths = this.indexes();
}
Compiles indexes from fields and schema-level indexes
show codeSchema.prototype.indexes = function () {
'use strict';
var indexes = [];
var seenPrefix = {};
var collectIndexes = function(schema, prefix) {
if (seenPrefix[prefix]) {
return;
}
seenPrefix[prefix] = true;
prefix = prefix || '';
var key, path, index, field, isObject, options, type;
var keys = Object.keys(schema.paths);
for (var i = 0; i < keys.length; ++i) {
key = keys[i];
path = schema.paths[key];
if (path instanceof MongooseTypes.DocumentArray) {
collectIndexes(path.schema, key + '.');
} else {
index = path._index;
if (false !== index && null != index) {
field = {};
isObject = utils.isObject(index);
options = isObject ? index : {};
type = 'string' == typeof index ? index :
isObject ? index.type :
false;
if (type && ~Schema.indexTypes.indexOf(type)) {
field[prefix + key] = type;
} else {
field[prefix + key] = 1;
}
delete options.type;
if (!('background' in options)) {
options.background = true;
}
indexes.push([field, options]);
}
}
}
if (prefix) {
fixSubIndexPaths(schema, prefix);
} else {
schema._indexes.forEach(function (index) {
if (!('background' in index[1])) index[1].background = true;
});
indexes = indexes.concat(schema._indexes);
}
};
collectIndexes(this);
return indexes;
method
, [fn]
)Adds an instance method to documents constructed from Models compiled from this schema.
var schema = kittySchema = new Schema(..);
schema.method('meow', function () {
console.log('meeeeeoooooooooooow');
})
var Kitty = mongoose.model('Kitty', schema);
var fizz = new Kitty;
fizz.meow(); // meeeeeooooooooooooow
If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
schema.method({
purr: function () {}
, scratch: function () {}
});
// later
fizz.purr();
fizz.scratch();
Schema.prototype.method = function (name, fn) {
if ('string' != typeof name)
for (var i in name)
this.methods[i] = name[i];
else
this.methods[name] = fn;
return this;
};
path
, constructor
)Gets/sets schema paths.
Sets a path (if arity 2)
Gets a path (if arity 1)
schema.path('name') // returns a SchemaType
schema.path('name', Number) // changes the schemaType of `name` to Number
Schema.prototype.path = function (path, obj) {
if (obj == undefined) {
if (this.paths[path]) return this.paths[path];
if (this.subpaths[path]) return this.subpaths[path];
// subpaths?
return /\.\d+\.?.*$/.test(path)
? getPositionalPath(this, path)
: undefined;
}
// some path names conflict with document methods
if (reserved[path]) {
throw new Error("`" + path + "` may not be used as a schema pathname");
}
// update the tree
var subpaths = path.split(/\./)
, last = subpaths.pop()
, branch = this.tree;
subpaths.forEach(function(sub, i) {
if (!branch[sub]) branch[sub] = {};
if ('object' != typeof branch[sub]) {
var msg = 'Cannot set nested path `' + path + '`. '
+ 'Parent path `'
+ subpaths.slice(0, i).concat([sub]).join('.')
+ '` already set to type ' + branch[sub].name
+ '.';
throw new Error(msg);
}
branch = branch[sub];
});
branch[last] = utils.clone(obj);
this.paths[path] = Schema.interpretAsType(path, obj);
return this;
};
path
)Returns the pathType of path
for this schema.
path
<String> Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.
Schema.prototype.pathType = function (path) {
if (path in this.paths) return 'real';
if (path in this.virtuals) return 'virtual';
if (path in this.nested) return 'nested';
if (path in this.subpaths) return 'real';
if (/\.\d+\.|\.\d+$/.test(path) && getPositionalPath(this, path)) {
return 'real';
} else {
return 'adhocOrUndefined'
}
};
plugin
, [opts]
)Registers a plugin for this schema.
Schema.prototype.plugin = function (fn, opts) {
fn(this, opts);
return this;
};
method
, fn
)Defines a post hook for the document
Post hooks fire on
the event emitted from document instances of Models compiled from this schema.
var schema = new Schema(..);
schema.post('save', function (doc) {
console.log('this fired after a document was saved');
});
var Model = mongoose.model('Model', schema);
var m = new Model(..);
m.save(function (err) {
console.log('this fires after the `post` hook');
});
Schema.prototype.post = function(method, fn) {
if (IS_QUERY_HOOK[method]) {
this.s.hooks.post.apply(this.s.hooks, arguments);
return this;
}
// assuming that all callbacks with arity < 2 are synchronous post hooks
if (fn.length < 2) {
return this.queue('on', arguments);
}
return this.queue('post', [arguments[0], function(next){
// wrap original function so that the callback goes last,
// for compatibility with old code that is using synchronous post hooks
fn.call(this, this, next);
}]);
};
method
, callback
)Defines a pre hook for the document.
var toySchema = new Schema(..);
toySchema.pre('save', function (next) {
if (!this.created) this.created = new Date;
next();
})
toySchema.pre('validate', function (next) {
if (this.name != 'Woody') this.name = 'Woody';
next();
})
Schema.prototype.pre = function() {
var name = arguments[0];
if (IS_QUERY_HOOK[name]) {
this.s.hooks.pre.apply(this.s.hooks, arguments);
return this;
}
return this.queue('pre', arguments);
};
name
, args
)Adds a method call to the queue.
Schema.prototype.queue = function(name, args){
this.callQueue.push([name, args]);
return this;
};
Returns an Array of path strings that are required by this schema.
Schema.prototype.requiredPaths = function requiredPaths () {
if (this._requiredpaths) return this._requiredpaths;
var paths = Object.keys(this.paths)
, i = paths.length
, ret = [];
while (i--) {
var path = paths[i];
if (this.paths[path].isRequired) ret.push(path);
}
return this._requiredpaths = ret;
}
key
, [value]
)Sets/gets a schema option.
Schema.prototype.set = function (key, value, _tags) {
if (1 === arguments.length) {
return this.options[key];
}
switch (key) {
case 'read':
this.options[key] = utils.readPref(value, _tags)
break;
case 'safe':
this.options[key] = false === value
? { w: 0 }
: value
break;
default:
this.options[key] = value;
}
return this;
}
name
, fn
)Adds static "class" methods to Models compiled from this schema.
var schema = new Schema(..);
schema.static('findByName', function (name, callback) {
return this.find({ name: name }, callback);
});
var Drink = mongoose.model('Drink', schema);
Drink.findByName('sanpellegrino', function (err, drinks) {
//
});
If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
Schema.prototype.static = function(name, fn) {
if ('string' != typeof name)
for (var i in name)
this.statics[i] = name[i];
else
this.statics[name] = fn;
return this;
};
name
, [options]
)Creates a virtual type with the given name.
Schema.prototype.virtual = function (name, options) {
var virtuals = this.virtuals;
var parts = name.split('.');
return virtuals[name] = parts.reduce(function (mem, part, i) {
mem[part] || (mem[part] = (i === parts.length-1)
? new VirtualType(options, name)
: {});
return mem[part];
}, this.tree);
};
name
)Returns the virtual type with the given name
.
name
<String> Schema.prototype.virtualpath = function (name) {
return this.virtuals[name];
};
The various built-in Mongoose Schema Types.
show codeSchema.Types = MongooseTypes = require('./schema/index');
var mongoose = require('mongoose');
var ObjectId = mongoose.Schema.Types.ObjectId;
Using this exposed access to the Mixed
SchemaType, we can use them in our schema.
var Mixed = mongoose.Schema.Types.Mixed;
new mongoose.Schema({ _user: Mixed })
The allowed index types
show codevar indexTypes = '2d 2dsphere hashed text'.split(' ');
Object.defineProperty(Schema, 'indexTypes', {
get: function () { return indexTypes }
, set: function () { throw new Error('Cannot overwrite Schema.indexTypes') }
})
path
, obj
)Converts type arguments into Mongoose Types.
show codeSchema.interpretAsType = function (path, obj) {
if (obj.constructor) {
var constructorName = utils.getFunctionName(obj.constructor);
if (constructorName != 'Object') {
obj = { type: obj };
}
}
// Get the type making sure to allow keys named "type"
// and default to mixed if not specified.
// { type: { type: String, default: 'freshcut' } }
var type = obj.type && !obj.type.type
? obj.type
: {};
if ('Object' == utils.getFunctionName(type.constructor) || 'mixed' == type) {
return new MongooseTypes.Mixed(path, obj);
}
if (Array.isArray(type) || Array == type || 'array' == type) {
// if it was specified through { type } look for `cast`
var cast = (Array == type || 'array' == type)
? obj.cast
: type[0];
if (cast instanceof Schema) {
return new MongooseTypes.DocumentArray(path, cast, obj);
}
if ('string' == typeof cast) {
cast = MongooseTypes[cast.charAt(0).toUpperCase() + cast.substring(1)];
} else if (cast && (!cast.type || cast.type.type)
&& 'Object' == utils.getFunctionName(cast.constructor)
&& Object.keys(cast).length) {
return new MongooseTypes.DocumentArray(path, new Schema(cast), obj);
}
return new MongooseTypes.Array(path, cast || MongooseTypes.Mixed, obj);
}
var name;
if (Buffer.isBuffer(type)) {
name = 'Buffer';
} else {
name = 'string' == typeof type
? type
// If not string, `type` is a function. Outside of IE, function.name
// gives you the function name. In IE, you need to compute it
: type.schemaName || utils.getFunctionName(type);
}
if (name) {
name = name.charAt(0).toUpperCase() + name.substring(1);
}
if (undefined == MongooseTypes[name]) {
throw new TypeError('Undefined type `' + name + '` at `' + path +
'`
Did you try nesting Schemas? ' +
'You can only nest using refs or arrays.');
}
return new MongooseTypes[name](path, obj);
};
Reserved document keys.
show codeSchema.reserved = Object.create(null);
var reserved = Schema.reserved;
reserved.on =
reserved.once =
reserved.db =
reserved.set =
reserved.get =
reserved.init =
reserved.isNew =
reserved.errors =
reserved.schema =
reserved.modelName =
reserved.collection =
reserved.toObject =
reserved.save =
reserved.validate =
reserved.emit = // EventEmitter
reserved._pres = reserved._posts = 1 // hooks.js
Keys in this object are names that are rejected in schema declarations b/c they conflict with mongoose functionality. Using these key name will throw an error.
on, emit, _events, db, get, set, init, isNew, errors, schema, options, modelName, collection, _pres, _posts, toObject
NOTE: Use of these terms as method names is permitted, but play at your own risk, as they may be existing mongoose document methods you are stomping on.
var schema = new Schema(..);
schema.methods.init = function () {} // potentially breaking
Schema as flat paths
{
'_id' : SchemaType,
, 'nested.key' : SchemaType,
}
show codeSchema.prototype.paths;
Schema as a tree
{
'_id' : ObjectId
, 'nested' : {
'key' : String
}
}
show codeSchema.prototype.tree;
obj
, [fields]
, [skipId]
)Document constructor.
init
: Emitted on a document after it has was retreived from the db and fully hydrated by Mongoose.
save
: Emitted when the document is successfully saved
function Document (obj, schema, fields, skipId, skipInit) {
if ( !(this instanceof Document) )
return new Document( obj, schema, fields, skipId, skipInit );
if (utils.isObject(schema) && !(schema instanceof Schema)) {
schema = new Schema(schema);
}
// When creating EmbeddedDocument, it already has the schema and he doesn't need the _id
schema = this.schema || schema;
// Generate ObjectId if it is missing, but it requires a scheme
if ( !this.schema && schema.options._id ){
obj = obj || {};
if ( obj._id === undefined ){
obj._id = new ObjectId();
}
}
if ( !schema ){
throw new MongooseError.MissingSchemaError();
}
this.$__setSchema(schema);
this.$__ = new InternalCache;
this.$__.emitter = new EventEmitter();
this.isNew = true;
this.errors = undefined;
//var schema = this.schema;
if ('boolean' === typeof fields) {
this.$__.strictMode = fields;
fields = undefined;
} else {
this.$__.strictMode = this.schema.options && this.schema.options.strict;
this.$__.selected = fields;
}
var required = this.schema.requiredPaths();
for (var i = 0; i < required.length; ++i) {
this.$__.activePaths.require(required[i]);
}
setMaxListeners.call(this, 0);
this._doc = this.$__buildDoc(obj, fields, skipId);
if ( !skipInit && obj ){
this.init( obj );
}
this.$__registerHooksFromSchema();
// apply methods
for ( var m in schema.methods ){
this[ m ] = schema.methods[ m ];
}
// apply statics
for ( var s in schema.statics ){
this[ s ] = schema.statics[ s ];
}
}
Depopulates stored atomic operation values as necessary for direct insertion to MongoDB.
If no atomics exist, we return all array values after conversion.
Pops the array atomically at most one time per document save()
.
Calling this mulitple times on an array before saving sends the same command as calling it once.
This update is implemented using the MongoDB $pop method which enforces this restriction.
doc.array = [1,2,3];
var popped = doc.array.$pop();
console.log(popped); // 3
console.log(doc.array); // [1,2]
// no affect
popped = doc.array.$pop();
console.log(doc.array); // [1,2]
doc.save(function (err) {
if (err) return handleError(err);
// we saved, now $pop works again
popped = doc.array.$pop();
console.log(popped); // 2
console.log(doc.array); // [1]
})
Atomically shifts the array at most one time per document save()
.
Calling this mulitple times on an array before saving sends the same command as calling it once.
This update is implemented using the MongoDB $pop method which enforces this restriction.
doc.array = [1,2,3];
var shifted = doc.array.$shift();
console.log(shifted); // 1
console.log(doc.array); // [2,3]
// no affect
shifted = doc.array.$shift();
console.log(doc.array); // [2,3]
doc.save(function (err) {
if (err) return handleError(err);
// we saved, now $shift works again
shifted = doc.array.$shift();
console.log(shifted ); // 2
console.log(doc.array); // [3]
})
values
, path
, doc
)Mongoose Array constructor.
Values always have to be passed to the constructor to initialize, otherwise MongooseArray#push
will mark the array as modified.
function MongooseArray (values, path, doc) {
var arr = [].concat(values);
utils.decorate( arr, MongooseArray.mixin );
arr.isMongooseArray = true;
arr._atomics = {};
arr.validators = [];
arr._path = path;
if (doc) {
arr._parent = doc;
arr._schema = doc.schema.path(path);
}
return arr;
}
MongooseArray.mixin = {
value
)Casts a member based on this arrays schema.
value
<T> embeddedDoc
, embeddedPath
)Marks this array as modified.
embeddedDoc
<EmbeddedDocument> the embedded doc that invoked this method on the ArrayembeddedPath
<String> the path which changed in the embeddedDocIf it bubbles up from an embedded document change, then it takes the following arguments (otherwise, takes 0 arguments)
op
, val
)Register an atomic operation with the parent.
op
<Array> operationval
<T> [args...]
)Adds values to the array if not already present.
[args...]
<T> console.log(doc.array) // [2,3,4]
var added = doc.array.addToSet(4,5);
console.log(doc.array) // [2,3,4,5]
console.log(added) // [5]
Returns the number of pending atomic operations to send to the db for this array.
obj
)Return the index of obj
or -1
if not found.
obj
<Object> the item to look for[args...]
)Pushes items to the array non-atomically.
[args...]
<T> marks the entire array as modified, which if saved, will store it as a $set
operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it.
Wraps Array#pop
with proper change tracking.
marks the entire array as modified which will pass the entire thing to $set potentially overwritting any changes that happen between when you retrieved the object and when you save it.
[args...]
)Pulls items from the array atomically.
[args...]
<T> doc.array.pull(ObjectId)
doc.array.pull({ _id: 'someId' })
doc.array.pull(36)
doc.array.pull('tag 1', 'tag 2')
To remove a document from a subdocument array we may pass an object with a matching _id
.
doc.subdocs.push({ _id: 4815162342 })
doc.subdocs.pull({ _id: 4815162342 }) // removed
Or we may passing the _id directly and let mongoose take care of it.
doc.subdocs.push({ _id: 4815162342 })
doc.subdocs.pull(4815162342); // works
[args...]
)Wraps Array#push
with proper change tracking.
[args...]
<Object> Sets the casted val
at index i
and marks the array modified.
// given documents based on the following
var Doc = mongoose.model('Doc', new Schema({ array: [Number] }));
var doc = new Doc({ array: [2,3,4] })
console.log(doc.array) // [2,3,4]
doc.array.set(1,"5");
console.log(doc.array); // [2,5,4] // properly cast to number
doc.save() // the change is saved
// VS not using array#set
doc.array[1] = "5";
console.log(doc.array); // [2,"5",4] // no casting
doc.save() // change is not saved
Wraps Array#shift
with proper change tracking.
doc.array = [2,3];
var res = doc.array.shift();
console.log(res) // 2
console.log(doc.array) // [3]
marks the entire array as modified, which if saved, will store it as a $set
operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it.
Wraps Array#sort
with proper change tracking.
marks the entire array as modified, which if saved, will store it as a $set
operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it.
Wraps Array#splice
with proper change tracking and casting.
marks the entire array as modified, which if saved, will store it as a $set
operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it.
options
)Returns a native js Array.
options
<Object> Wraps Array#unshift
with proper change tracking.
marks the entire array as modified, which if saved, will store it as a $set
operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it.
value
, encode
, offset
)Mongoose Buffer constructor.
Values always have to be passed to the constructor to initialize.
function MongooseBuffer (value, encode, offset) {
var length = arguments.length;
var val;
if (0 === length || null === arguments[0] || undefined === arguments[0]) {
val = 0;
} else {
val = value;
}
var encoding;
var path;
var doc;
if (Array.isArray(encode)) {
// internal casting
path = encode[0];
doc = encode[1];
} else {
encoding = encode;
}
var buf = new Buffer(val, encoding, offset);
utils.decorate( buf, MongooseBuffer.mixin );
buf.isMongooseBuffer = true;
// make sure these internal props don't show up in Object.keys()
Object.defineProperties(buf, {
validators: { value: [] }
, _path: { value: path }
, _parent: { value: doc }
});
if (doc && "string" === typeof path) {
Object.defineProperty(buf, '_schema', {
value: doc.schema.path(path)
});
}
buf._subtype = 0;
return buf;
}
target
)Copies the buffer.
target
<Buffer> Buffer#copy
does not mark target
as modified so you must copy from a MongooseBuffer
for it to work as expected. This is a work around since copy
modifies the target, not this.
other
)Determines if this buffer is equals to other
buffer
other
<Buffer> subtype
)Sets the subtype option and marks the buffer modified.
subtype
<Hex> var bson = require('bson')
bson.BSON_BINARY_SUBTYPE_DEFAULT
bson.BSON_BINARY_SUBTYPE_FUNCTION
bson.BSON_BINARY_SUBTYPE_BYTE_ARRAY
bson.BSON_BINARY_SUBTYPE_UUID
bson.BSON_BINARY_SUBTYPE_MD5
bson.BSON_BINARY_SUBTYPE_USER_DEFINED
doc.buffer.subtype(bson.BSON_BINARY_SUBTYPE_UUID);
[subtype]
)Converts this buffer to its Binary type representation.
[subtype]
<Hex> var bson = require('bson')
bson.BSON_BINARY_SUBTYPE_DEFAULT
bson.BSON_BINARY_SUBTYPE_FUNCTION
bson.BSON_BINARY_SUBTYPE_BYTE_ARRAY
bson.BSON_BINARY_SUBTYPE_UUID
bson.BSON_BINARY_SUBTYPE_MD5
bson.BSON_BINARY_SUBTYPE_USER_DEFINED
doc.buffer.toObject(bson.BSON_BINARY_SUBTYPE_USER_DEFINED);
Default subtype for the Binary representing this Buffer
show code_subtype: undefined,
[path]
)Returns the full path to this document. If optional path
is passed, it is appended to the full path.
[path]
<String> obj
, parentArr
, skipId
)EmbeddedDocument constructor.
obj
<Object> js object returned from the dbparentArr
<MongooseDocumentArray> the parent array of this documentskipId
<Boolean> function EmbeddedDocument (obj, parentArr, skipId, fields) {
if (parentArr) {
this.__parentArray = parentArr;
this.__parent = parentArr._parent;
} else {
this.__parentArray = undefined;
this.__parent = undefined;
}
Document.call(this, obj, fields, skipId);
var self = this;
this.on('isNew', function (val) {
self.isNew = val;
});
}
Helper for console.log
show codeEmbeddedDocument.prototype.inspect = function () {
return inspect(this.toObject());
};
path
, err
)Marks a path as invalid, causing validation to fail.
EmbeddedDocument.prototype.invalidate = function (path, err, val, first) {
if (!this.__parent) {
var msg = 'Unable to invalidate a subdocument that has not been added to an array.'
throw new Error(msg);
}
var index = this.__parentArray.indexOf(this);
var parentPath = this.__parentArray._path;
var fullPath = [parentPath, index, path].join('.');
// sniffing arguments:
// need to check if user passed a value to keep
// our error message clean.
if (2 < arguments.length) {
this.__parent.invalidate(fullPath, err, val);
} else {
this.__parent.invalidate(fullPath, err);
}
if (first)
this.$__.validationError = this.ownerDocument().$__.validationError;
return true;
}
path
)Marks the embedded doc modified.
path
<String> the path which changedvar doc = blogpost.comments.id(hexstring);
doc.mixed.type = 'changed';
doc.markModified('mixed.type');
EmbeddedDocument.prototype.markModified = function (path) {
if (!this.__parentArray) return;
this.$__.activePaths.modify(path);
if (this.isNew) {
// Mark the WHOLE parent array as modified
// if this is a new document (i.e., we are initializing
// a document),
this.__parentArray._markModified();
} else {
this.__parentArray._markModified(this, path);
}
};
Returns the top level document of this sub-document.
EmbeddedDocument.prototype.ownerDocument = function () {
if (this.$__.ownerDocument) {
return this.$__.ownerDocument;
}
var parent = this.__parent;
if (!parent) return this;
while (parent.__parent) {
parent = parent.__parent;
}
return this.$__.ownerDocument = parent;
}
Returns this sub-documents parent document.
show codeEmbeddedDocument.prototype.parent = function () {
return this.__parent;
}
Returns this sub-documents parent array.
show codeEmbeddedDocument.prototype.parentArray = function () {
return this.__parentArray;
}
[fn]
)Removes the subdocument from its parent array.
[fn]
<Function> EmbeddedDocument.prototype.remove = function (fn) {
if (!this.__parentArray) return this;
var _id;
if (!this.willRemove) {
_id = this._doc._id;
if (!_id) {
throw new Error('For your own good, Mongoose does not know ' +
'how to remove an EmbeddedDocument that has no _id');
}
this.__parentArray.pull({ _id: _id });
this.willRemove = true;
registerRemoveListener(this);
}
if (fn)
fn(null);
return this;
};
[fn]
)Used as a stub for hooks.js
[fn]
<Function> This is a no-op. Does not actually save the doc to the db.
EmbeddedDocument.prototype.save = function (fn) {
var promise = new Promise(fn);
promise.fulfill();
return promise;
}
Override #update method of parent documents.
show codeEmbeddedDocument.prototype.update = function () {
throw new Error('The #update method is not available on EmbeddedDocuments');
}
values
, path
, doc
)DocumentArray constructor
function MongooseDocumentArray (values, path, doc) {
var arr = [].concat(values);
// Values always have to be passed to the constructor to initialize, since
// otherwise MongooseArray#push will mark the array as modified to the parent.
utils.decorate( arr, MongooseDocumentArray.mixin );
arr.isMongooseArray = true;
arr.isMongooseDocumentArray = true;
arr._atomics = {};
arr.validators = [];
arr._path = path;
if (doc) {
arr._parent = doc;
arr._schema = doc.schema.path(path);
arr._handlers = {
isNew: arr.notify('isNew'),
save: arr.notify('save')
};
doc.on('save', arr._handlers.save);
doc.on('isNew', arr._handlers.isNew);
}
return arr;
}
obj
)Creates a subdocument casted to this schema.
obj
<Object> the value to cast to this arrays SubDocument schemaThis is the same subdocument constructor used for casting.
id
)Searches array items for the first document with a matching _id.
var embeddedDoc = m.array.id(some_id);
event
)Creates a fn that notifies all child docs of event
.
event
<String> [options]
)Returns a native js Array of plain js objects
[options]
<Object> optional options to pass to each documents `toObject` method call during conversionEach sub-document is converted to a plain object by calling its #toObject
method.
argument
)Creates a Query
and specifies a $where
condition.
Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via find({ $where: javascript })
, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.
Blog.$where('this.comments.length > 5').exec(function (err, docs) {});
doc
)Model constructor
doc
<Object> values with which to create the documenterror
: If listening to this event, it is emitted when a document was saved without passing a callback and an error
occurred. If not listening, the event bubbles to the connection used to create this Model.
index
: Emitted after Model#ensureIndexes
completes. If an error occurred it is passed with the event.
Provides the interface to MongoDB collections as well as creates document instances.
function Model (doc, fields, skipId) {
Document.call(this, doc, fields, skipId);
};
Signal that we desire an increment of this documents version.
Model.findById(id, function (err, doc) {
doc.increment();
doc.save(function (err) { .. })
})
Model.prototype.increment = function increment () {
this.$__.version = VERSION_ALL;
return this;
}
name
)Returns another Model instance.
name
<String> model namevar doc = new Tank;
doc.model('User').findById(id, callback);
Model.prototype.model = function model (name) {
return this.db.model(name);
};
(err,
)@description Removes this document from the db.
(err,
<function> product)} [fn] optional callbackModel.prototype.remove = function remove (options, fn) {
if ('function' == typeof options) {
fn = options;
options = undefined;
}
if (!options) {
options = {};
}
if (this.$__.removing) {
this.$__.removing.onResolve(fn);
return this;
}
var promise = this.$__.removing = new Promise(fn)
, where = this.$__where()
, self = this
;
if (!options.safe && this.schema.options.safe) {
options.safe = this.schema.options.safe;
}
this.collection.remove(where, options, function (err) {
if (!err) {
self.emit('remove', self);
}
promise.resolve(err, self);
});
return promise;
};
product,
)@description Saves this document.
product,
<function(err, > Number)} [fn] optional callbackModel.prototype.save = function (options, fn) {
if ('function' == typeof options) {
fn = options;
options = undefined;
}
if (!options) {
options = {};
}
var self = this;
var finalPromise = new Promise(fn);
// Jank to be able to use mpromise.prototype.all()
var p0 = new Promise;
p0.fulfill();
// Call save hooks on subdocs
var p1 = p0.all(function () {
var subDocs = self.$__getAllSubdocs();
return subDocs.map(function (d) {return d.save();});
});
// Handle save and resaults
p1
.then(this.$__handleSave.bind(this, options))
.then(function (result) {
self.$__reset();
self.$__storeShard();
var numAffected = 0;
if (result) {
if (Array.isArray(result)) {
numAffected = result.length;
} else if (result.result && result.result.nModified !== undefined) {
numAffected = result.result.nModified;
} else if (result.result && result.result.n !== undefined) {
numAffected = result.result.n;
} else {
numAffected = result;
}
}
// was this an update that required a version bump?
if (self.$__.version && !self.$__.inserting) {
var doIncrement = VERSION_INC === (VERSION_INC & self.$__.version);
self.$__.version = undefined;
if (numAffected <= 0) {
// the update failed. pass an error back
return finalPromise.reject(new VersionError);
}
// increment version if was successful
if (doIncrement) {
var key = self.schema.options.versionKey;
var version = self.getValue(key) | 0;
self.setValue(key, version + 1);
}
}
self.emit('save', self, numAffected);
return finalPromise.fulfill(self, numAffected);
}
, function (err) {
// If the initial insert fails provide a second chance.
// (If we did this all the time we would break updates)
if (self.$__.inserting) {
self.isNew = true;
self.emit('isNew', true);
}
finalPromise.reject(err);
})
.end();
return finalPromise;
};
path
)Finds the schema for path
. This is different than
calling schema.path
as it also resolves paths with
positional selectors (something.$.another.$.path).
Model._getSchema = function _getSchema (path) {
var schema = this.schema
, pathschema = schema.path(path);
if (pathschema)
return pathschema;
// look for arrays
return (function search (parts, schema) {
var p = parts.length + 1
, foundschema
, trypath
while (p--) {
trypath = parts.slice(0, p).join('.');
foundschema = schema.path(trypath);
if (foundschema) {
if (foundschema.caster) {
// array of Mixed?
if (foundschema.caster instanceof Types.Mixed) {
return foundschema.caster;
}
// Now that we found the array, we need to check if there
// are remaining document paths to look up for casting.
// Also we need to handle array.$.path since schema.path
// doesn't work for that.
// If there is no foundschema.schema we are dealing with
// a path like array.$
if (p !== parts.length && foundschema.schema) {
if ('$' === parts[p]) {
// comments.$.comments.$.title
return search(parts.slice(p+1), foundschema.schema);
} else {
// this is the last path of the selector
return search(parts.slice(p), foundschema.schema);
}
}
}
return foundschema;
}
}
})(path.split('.'), schema)
}
path
<String> [...]
, [callback]
)Performs aggregations on the models collection.
show codeModel.aggregate = function aggregate () {
var args = [].slice.call(arguments)
, aggregate
, callback;
if ('function' === typeof args[args.length - 1]) {
callback = args.pop();
}
if (1 === args.length && util.isArray(args[0])) {
aggregate = new Aggregate(args[0]);
} else {
aggregate = new Aggregate(args);
}
aggregate.bind(this);
if ('undefined' === typeof callback) {
return aggregate;
}
return aggregate.exec(callback);
}
If a callback
is passed, the aggregate
is executed and a Promise
is returned. If a callback is not passed, the aggregate
itself is returned.
// Find the max balance of all accounts
Users.aggregate(
{ $group: { _id: null, maxBalance: { $max: '$balance' }}}
, { $project: { _id: 0, maxBalance: 1 }}
, function (err, res) {
if (err) return handleError(err);
console.log(res); // [ { maxBalance: 98000 } ]
});
// Or use the aggregation pipeline builder.
Users.aggregate()
.group({ _id: null, maxBalance: { $max: '$balance' } })
.select('-id maxBalance')
.exec(function (err, res) {
if (err) return handleError(err);
console.log(res); // [ { maxBalance: 98 } ]
});
$project
operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format.conditions
, [callback]
)Counts number of matching documents in a database collection.
show codeModel.count = function count (conditions, callback) {
if ('function' === typeof conditions)
callback = conditions, conditions = {};
// get the mongodb collection object
var mq = new Query({}, {}, this, this.collection);
return mq.count(conditions, callback);
};
Adventure.count({ type: 'jungle' }, function (err, count) {
if (err) ..
console.log('there are %d jungle adventures', count);
});
doc(s)
, [fn]
)Shortcut for creating a new Document that is automatically saved to the db if valid.
show codeModel.create = function create (doc, fn) {
var args
, cb
if (Array.isArray(doc)) {
args = doc;
cb = fn;
} else {
var last = arguments[arguments.length - 1];
if ('function' == typeof last) {
cb = last;
args = utils.args(arguments, 0, arguments.length - 1);
} else {
args = utils.args(arguments);
}
}
var promise = new Promise(cb);
var ModelConstructor = this;
if (args.length === 0) {
process.nextTick(function() {
promise.fulfill.apply(promise, null);
});
return promise;
}
var toExecute = [];
args.forEach(function(doc) {
toExecute.push(function(callback) {
(new ModelConstructor(doc)).save(function(error, doc) {
callback(error, doc);
});
});
});
async.parallel(toExecute, function(error, savedDocs) {
if (error) {
return promise.reject(error);
}
if (doc instanceof Array) {
promise.fulfill.call(promise, savedDocs);
} else {
promise.fulfill.apply(promise, savedDocs);
}
});
return promise;
};
// pass individual docs
Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) {
if (err) // ...
});
// pass an array
var array = [{ type: 'jelly bean' }, { type: 'snickers' }];
Candy.create(array, function (err, candies) {
if (err) // ...
var jellybean = candies[0];
var snickers = candies[1];
// ...
});
// callback is optional; use the returned promise if you like:
var promise = Candy.create({ type: 'jawbreaker' });
promise.then(function (jawbreaker) {
// ...
})
name
, schema
)Adds a discriminator type.
show codeModel.discriminator = function discriminator (name, schema) {
if (!(schema instanceof Schema)) {
throw new Error("You must pass a valid discriminator Schema");
}
if (this.schema.discriminatorMapping && !this.schema.discriminatorMapping.isRoot) {
throw new Error("Discriminator \"" + name +
"\" can only be a discriminator of the root model");
}
var key = this.schema.options.discriminatorKey;
if (schema.path(key)) {
throw new Error("Discriminator \"" + name +
"\" cannot have field with name \"" + key + "\"");
}
// merges base schema into new discriminator schema and sets new type field.
(function(schema, baseSchema) {
utils.merge(schema, baseSchema);
var obj = {};
obj[key] = { type: String, default: name };
schema.add(obj);
schema.discriminatorMapping = { key: key, value: name, isRoot: false };
if (baseSchema.options.collection) {
schema.options.collection = baseSchema.options.collection;
}
// throws error if options are invalid
(function(a, b) {
a = utils.clone(a);
b = utils.clone(b);
delete a.toJSON;
delete a.toObject;
delete b.toJSON;
delete b.toObject;
if (!utils.deepEqual(a, b)) {
throw new Error("Discriminator options are not customizable " +
"(except toJSON & toObject)");
}
})(schema.options, baseSchema.options);
var toJSON = schema.options.toJSON
, toObject = schema.options.toObject;
schema.options = utils.clone(baseSchema.options);
if (toJSON) schema.options.toJSON = toJSON;
if (toObject) schema.options.toObject = toObject;
schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);
schema._requiredpaths = undefined; // reset just in case Schema#requiredPaths() was called on either schema
})(schema, this.schema);
if (!this.discriminators) {
this.discriminators = {};
}
if (!this.schema.discriminatorMapping) {
this.schema.discriminatorMapping = { key: key, value: null, isRoot: true };
}
if (this.discriminators[name]) {
throw new Error("Discriminator with name \"" + name + "\" already exists");
}
this.discriminators[name] = this.db.model(name, schema, this.collection.name);
this.discriminators[name].prototype.__proto__ = this.prototype;
// apply methods and statics
applyMethods(this.discriminators[name], schema);
applyStatics(this.discriminators[name], schema);
return this.discriminators[name];
};
// Model (class) features
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: String,
createdAt: Date
});
}
util.inherits(BaseSchema, Schema);
var PersonSchema = new BaseSchema();
var BossSchema = new BaseSchema({ department: String });
var Person = mongoose.model('Person', PersonSchema);
var Boss = Person.discriminator('Boss', BossSchema);
field
, [conditions]
, [callback]
)Creates a Query for a distinct
operation.
Model.distinct = function distinct (field, conditions, callback) {
// get the mongodb collection object
var mq = new Query({}, {}, this, this.collection);
if ('function' == typeof conditions) {
callback = conditions;
conditions = {};
}
return mq.distinct(field, conditions, callback);
};
Passing a callback
immediately executes the query.
Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) {
if (err) return handleError(err);
assert(Array.isArray(result));
console.log('unique urls with more than 100 clicks', result);
})
var query = Link.distinct('url');
query.exec(callback);
[cb]
)Sends ensureIndex
commands to mongo for each index declared in the schema.
Model.ensureIndexes = function ensureIndexes (cb) {
var promise = new Promise(cb);
var indexes = this.schema.indexes();
if (!indexes.length) {
process.nextTick(promise.fulfill.bind(promise));
return promise;
}
// Indexes are created one-by-one to support how MongoDB < 2.4 deals
// with background indexes.
var self = this
, safe = self.schema.options.safe
var done = function(err) {
self.emit('index', err);
promise.resolve(err);
}
var create = function() {
var index = indexes.shift();
if (!index) return done();
var options = index[1];
options.safe = safe;
self.collection.ensureIndex(index[0], options, tick(function (err) {
if (err) return done(err);
create();
}));
}
create();
return promise;
}
[cb]
<Function> optional callbackEvent.ensureIndexes(function (err) {
if (err) return handleError(err);
});
After completion, an index
event is emitted on this Model
passing an error if one occurred.
var eventSchema = new Schema({ thing: { type: 'string', unique: true }})
var Event = mongoose.model('Event', eventSchema);
Event.on('index', function (err) {
if (err) console.error(err); // error occurred during index creation
})
NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution.
The ensureIndex
commands are not sent in parallel. This is to avoid the MongoError: cannot add index with a background operation in progress
error. See this ticket for more information.
conditions
, [fields]
, [options]
, [callback]
)Finds documents
show codeModel.find = function find (conditions, fields, options, callback) {
if ('function' == typeof conditions) {
callback = conditions;
conditions = {};
fields = null;
options = null;
} else if ('function' == typeof fields) {
callback = fields;
fields = null;
options = null;
} else if ('function' == typeof options) {
callback = options;
options = null;
}
// get the raw mongodb collection object
var mq = new Query({}, options, this, this.collection);
mq.select(fields);
if (this.schema.discriminatorMapping && mq.selectedInclusively()) {
mq.select(this.schema.options.discriminatorKey);
}
return mq.find(conditions, callback);
};
The conditions
are cast to their respective SchemaTypes before the command is sent.
// named john and at least 18
MyModel.find({ name: 'john', age: { $gte: 18 }});
// executes immediately, passing results to callback
MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});
// name LIKE john and only selecting the "name" and "friends" fields, executing immediately
MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })
// passing options
MyModel.find({ name: /john/i }, null, { skip: 10 })
// passing options and executing immediately
MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});
// executing a query explicitly
var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
query.exec(function (err, docs) {});
// using the promise returned from executing a query
var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
var promise = query.exec();
promise.addBack(function (err, docs) {});
id
, [fields]
, [options]
, [callback]
)Finds a single document by id.
show codeModel.findById = function findById (id, fields, options, callback) {
return this.findOne({ _id: id }, fields, options, callback);
};
The id
is cast based on the Schema before sending the command.
// find adventure by id and execute immediately
Adventure.findById(id, function (err, adventure) {});
// same as above
Adventure.findById(id).exec(callback);
// select only the adventures name and length
Adventure.findById(id, 'name length', function (err, adventure) {});
// same as above
Adventure.findById(id, 'name length').exec(callback);
// include all properties except for `length`
Adventure.findById(id, '-length').exec(function (err, adventure) {});
// passing options (in this case return the raw js objects, not mongoose documents by passing `lean`
Adventure.findById(id, 'name', { lean: true }, function (err, doc) {});
// same as above
Adventure.findById(id, 'name').lean().exec(function (err, doc) {});
id
, [options]
, [callback]
)Issue a mongodb findAndModify remove command by a documents id.
show codeModel.findByIdAndRemove = function (id, options, callback) {
if (1 === arguments.length && 'function' == typeof id) {
var msg = 'Model.findByIdAndRemove(): First argument must not be a function.
'
+ ' ' + this.modelName + '.findByIdAndRemove(id, callback)
'
+ ' ' + this.modelName + '.findByIdAndRemove(id)
'
+ ' ' + this.modelName + '.findByIdAndRemove()
';
throw new TypeError(msg)
}
return this.findOneAndRemove({ _id: id }, options, callback);
}
Finds a matching document, removes it, passing the found document (if any) to the callback.
Executes immediately if callback
is passed, else a Query
object is returned.
sort
: if multiple docs are found by the conditions, sets the sort order to choose which doc to updateselect
: sets the document fields to returnA.findByIdAndRemove(id, options, callback) // executes
A.findByIdAndRemove(id, options) // return Query
A.findByIdAndRemove(id, callback) // executes
A.findByIdAndRemove(id) // returns Query
A.findByIdAndRemove() // returns Query
id
, [update]
, [options]
, [callback]
)Issues a mongodb findAndModify update command by a documents id.
show codeModel.findByIdAndUpdate = function (id, update, options, callback) {
var args;
if (1 === arguments.length) {
if ('function' == typeof id) {
var msg = 'Model.findByIdAndUpdate(): First argument must not be a function.
'
+ ' ' + this.modelName + '.findByIdAndUpdate(id, callback)
'
+ ' ' + this.modelName + '.findByIdAndUpdate(id)
'
+ ' ' + this.modelName + '.findByIdAndUpdate()
';
throw new TypeError(msg)
}
return this.findOneAndUpdate({_id: id }, undefined);
}
args = utils.args(arguments, 1);
// if a model is passed in instead of an id
if (id && id._id) {
id = id._id;
}
if (id) {
args.unshift({ _id: id });
}
return this.findOneAndUpdate.apply(this, args);
}
Finds a matching document, updates it according to the update
arg, passing any options
, and returns the found document (if any) to the callback. The query executes immediately if callback
is passed else a Query object is returned.
new
: bool - true to return the modified document rather than the original. defaults to trueupsert
: bool - creates the object if it doesn't exist. defaults to false.sort
: if multiple docs are found by the conditions, sets the sort order to choose which doc to updateselect
: sets the document fields to returnA.findByIdAndUpdate(id, update, options, callback) // executes
A.findByIdAndUpdate(id, update, options) // returns Query
A.findByIdAndUpdate(id, update, callback) // executes
A.findByIdAndUpdate(id, update) // returns Query
A.findByIdAndUpdate() // returns Query
Finds a matching document, updates it according to the update
arg, passing any options
, and returns the found document (if any) to the callback. The query executes immediately if callback
is passed else a Query object is returned.
new
: bool - true to return the modified document rather than the original. defaults to trueupsert
: bool - creates the object if it doesn't exist. defaults to false.sort
: if multiple docs are found by the conditions, sets the sort order to choose which doc to updateAll top level update keys which are not atomic
operation names are treated as set operations:
Model.findByIdAndUpdate(id, { name: 'jason borne' }, options, callback)
// is sent as
Model.findByIdAndUpdate(id, { $set: { name: 'jason borne' }}, options, callback)
This helps prevent accidentally overwriting your document with { name: 'jason borne' }
.
Although values are cast to their appropriate types when using the findAndModify helpers, the following are not applied:
If you need those features, use the traditional approach of first retrieving the document.
Model.findById(id, function (err, doc) {
if (err) ..
doc.name = 'jason borne';
doc.save(callback);
})
[conditions]
, [fields]
, [options]
, [callback]
)Finds one document.
show codeModel.findOne = function findOne (conditions, fields, options, callback) {
if ('function' == typeof options) {
callback = options;
options = null;
} else if ('function' == typeof fields) {
callback = fields;
fields = null;
options = null;
} else if ('function' == typeof conditions) {
callback = conditions;
conditions = {};
fields = null;
options = null;
}
// get the mongodb collection object
var mq = new Query({}, options, this, this.collection);
mq.select(fields);
if (this.schema.discriminatorMapping && mq.selectedInclusively()) {
mq.select(this.schema.options.discriminatorKey);
}
return mq.findOne(conditions, callback);
};
The conditions
are cast to their respective SchemaTypes before the command is sent.
// find one iphone adventures - iphone adventures??
Adventure.findOne({ type: 'iphone' }, function (err, adventure) {});
// same as above
Adventure.findOne({ type: 'iphone' }).exec(function (err, adventure) {});
// select only the adventures name
Adventure.findOne({ type: 'iphone' }, 'name', function (err, adventure) {});
// same as above
Adventure.findOne({ type: 'iphone' }, 'name').exec(function (err, adventure) {});
// specify options, in this case lean
Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }, callback);
// same as above
Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }).exec(callback);
// chaining findOne queries (same as above)
Adventure.findOne({ type: 'iphone' }).select('name').lean().exec(callback);
conditions
, [options]
, [callback]
)Issue a mongodb findAndModify remove command.
show codeModel.findOneAndRemove = function (conditions, options, callback) {
if (1 === arguments.length && 'function' == typeof conditions) {
var msg = 'Model.findOneAndRemove(): First argument must not be a function.
'
+ ' ' + this.modelName + '.findOneAndRemove(conditions, callback)
'
+ ' ' + this.modelName + '.findOneAndRemove(conditions)
'
+ ' ' + this.modelName + '.findOneAndRemove()
';
throw new TypeError(msg)
}
if ('function' == typeof options) {
callback = options;
options = undefined;
}
var fields;
if (options) {
fields = options.select;
options.select = undefined;
}
var mq = new Query({}, {}, this, this.collection);
mq.select(fields);
return mq.findOneAndRemove(conditions, options, callback);
}
Finds a matching document, removes it, passing the found document (if any) to the callback.
Executes immediately if callback
is passed else a Query object is returned.
sort
: if multiple docs are found by the conditions, sets the sort order to choose which doc to updateselect
: sets the document fields to returnA.findOneAndRemove(conditions, options, callback) // executes
A.findOneAndRemove(conditions, options) // return Query
A.findOneAndRemove(conditions, callback) // executes
A.findOneAndRemove(conditions) // returns Query
A.findOneAndRemove() // returns Query
Although values are cast to their appropriate types when using the findAndModify helpers, the following are not applied:
If you need those features, use the traditional approach of first retrieving the document.
Model.findById(id, function (err, doc) {
if (err) ..
doc.remove(callback);
})
[conditions]
, [update]
, [options]
, [callback]
)Issues a mongodb findAndModify update command.
show codeModel.findOneAndUpdate = function (conditions, update, options, callback) {
if ('function' == typeof options) {
callback = options;
options = null;
}
else if (1 === arguments.length) {
if ('function' == typeof conditions) {
var msg = 'Model.findOneAndUpdate(): First argument must not be a function.
'
+ ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options, callback)
'
+ ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options)
'
+ ' ' + this.modelName + '.findOneAndUpdate(conditions, update)
'
+ ' ' + this.modelName + '.findOneAndUpdate(update)
'
+ ' ' + this.modelName + '.findOneAndUpdate()
';
throw new TypeError(msg)
}
update = conditions;
conditions = undefined;
}
var fields;
if (options && options.fields) {
fields = options.fields;
options.fields = undefined;
}
update = utils.clone(update, { depopulate: 1 });
if (this.schema.options.versionKey && options && options.upsert) {
if (!update.$setOnInsert) {
update.$setOnInsert = {};
}
update.$setOnInsert[this.schema.options.versionKey] = 0;
}
var mq = new Query({}, {}, this, this.collection);
mq.select(fields);
return mq.findOneAndUpdate(conditions, update, options, callback);
}
Finds a matching document, updates it according to the update
arg, passing any options
, and returns the found document (if any) to the callback. The query executes immediately if callback
is passed else a Query object is returned.
new
: bool - true to return the modified document rather than the original. defaults to trueupsert
: bool - creates the object if it doesn't exist. defaults to false.sort
: if multiple docs are found by the conditions, sets the sort order to choose which doc to updateselect
: sets the document fields to returnA.findOneAndUpdate(conditions, update, options, callback) // executes
A.findOneAndUpdate(conditions, update, options) // returns Query
A.findOneAndUpdate(conditions, update, callback) // executes
A.findOneAndUpdate(conditions, update) // returns Query
A.findOneAndUpdate() // returns Query
All top level update keys which are not atomic
operation names are treated as set operations:
var query = { name: 'borne' };
Model.findOneAndUpdate(query, { name: 'jason borne' }, options, callback)
// is sent as
Model.findOneAndUpdate(query, { $set: { name: 'jason borne' }}, options, callback)
This helps prevent accidentally overwriting your document with { name: 'jason borne' }
.
Although values are cast to their appropriate types when using the findAndModify helpers, the following are not applied:
If you need those features, use the traditional approach of first retrieving the document.
Model.findOne({ name: 'borne' }, function (err, doc) {
if (err) ..
doc.name = 'jason borne';
doc.save(callback);
})
GeoJSON
, options
, [callback]
)geoNear support for Mongoose
show codeModel.geoNear = function (near, options, callback) {
if ('function' == typeof options) {
callback = options;
options = {};
}
var self = this;
var promise = new Promise(callback);
if (!near) {
promise.error(new Error("Must pass a near option to geoNear"));
return promise;
}
var x,y;
var handler = function (err, res) {
if (err) return promise.error(err);
if (options.lean) return promise.fulfill(res.results, res.stats);
var count = res.results.length;
// if there are no results, fulfill the promise now
if (count == 0) {
return promise.fulfill(res.results, res.stats);
}
var errSeen = false;
for (var i=0; i < res.results.length; i++) {
var temp = res.results[i].obj;
res.results[i].obj = new self();
res.results[i].obj.init(temp, function (err) {
if (err && !errSeen) {
errSeen = true;
return promise.error(err);
}
--count || promise.fulfill(res.results, res.stats);
});
}
};
if (Array.isArray(near)) {
if (near.length != 2) {
promise.error(new Error("If using legacy coordinates, must be an array of size 2 for geoNear"));
return promise;
}
x = near[0];
y = near[1];
this.collection.geoNear(x, y, options, handler);
} else {
if (near.type != "Point" || !Array.isArray(near.coordinates)) {
promise.error(new Error("Must pass either a legacy coordinate array or GeoJSON Point to geoNear"));
return promise;
}
this.collection.geoNear(near, options, handler);
}
return promise;
};
lean
{Boolean} return the raw object// Legacy point
Model.geoNear([1,3], { maxDistance : 5, spherical : true }, function(err, results, stats) {
console.log(results);
});
// geoJson
var point = { type : "Point", coordinates : [9,9] };
Model.geoNear(point, { maxDistance : 5, spherical : true }, function(err, results, stats) {
console.log(results);
});
condition
, options
, [callback]
)Implements $geoSearch
functionality for Mongoose
Model.geoSearch = function (conditions, options, callback) {
if ('function' == typeof options) {
callback = options;
options = {};
}
var promise = new Promise(callback);
if (conditions == undefined || !utils.isObject(conditions)) {
return promise.error(new Error("Must pass conditions to geoSearch"));
}
if (!options.near) {
return promise.error(new Error("Must specify the near option in geoSearch"));
}
if (!Array.isArray(options.near)) {
return promise.error(new Error("near option must be an array [x, y]"));
}
// send the conditions in the options object
options.search = conditions;
var self = this;
this.collection.geoHaystackSearch(options.near[0], options.near[1], options, function (err, res) {
// have to deal with driver problem. Should be fixed in a soon-ish release
// (7/8/2013)
if (err || res.errmsg) {
if (!err) err = new Error(res.errmsg);
if (res && res.code !== undefined) err.code = res.code;
return promise.error(err);
}
var count = res.results.length;
if (options.lean || (count == 0)) return promise.fulfill(res.results, res.stats);
var errSeen = false;
for (var i=0; i < res.results.length; i++) {
var temp = res.results[i];
res.results[i] = new self();
res.results[i].init(temp, {}, function (err) {
if (err && !errSeen) {
errSeen = true;
return promise.error(err);
}
--count || (!errSeen && promise.fulfill(res.results, res.stats));
});
}
});
return promise;
};
var options = { near: [10, 10], maxDistance: 5 };
Locations.geoSearch({ type : "house" }, options, function(err, res) {
console.log(res);
});
near
{Array} x,y point to search formaxDistance
{Number} the maximum distance from the point near that a result can belimit
{Number} The maximum number of results to returnlean
{Boolean} return the raw object instead of the Mongoose Modelobj
)Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
The document returned has no paths marked as modified initially.
Model.hydrate = function (obj) {
var model = require('./queryhelpers').createModel(this, obj);
model.init(obj);
return model;
};
obj
<Object> // hydrate previous data into a Mongoose document
var mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });
Called when the model compiles.
show codeModel.init = function init () {
if ((this.schema.options.autoIndex) ||
(this.schema.options.autoIndex === null && this.db.config.autoIndex)) {
this.ensureIndexes();
}
this.schema.emit('init', this);
};
o
, [callback]
)Executes a mapReduce command.
show codeModel.mapReduce = function mapReduce (o, callback) {
var promise = new Promise(callback);
var self = this;
if (!Model.mapReduce.schema) {
var opts = { noId: true, noVirtualId: true, strict: false }
Model.mapReduce.schema = new Schema({}, opts);
}
if (!o.out) o.out = { inline: 1 };
if (false !== o.verbose) o.verbose = true;
o.map = String(o.map);
o.reduce = String(o.reduce);
if (o.query) {
var q = new Query(o.query);
q.cast(this);
o.query = q._conditions;
q = undefined;
}
this.collection.mapReduce(null, null, o, function (err, ret, stats) {
if (err) return promise.error(err);
if (ret.findOne && ret.mapReduce) {
// returned a collection, convert to Model
var model = Model.compile(
'_mapreduce_' + ret.collectionName
, Model.mapReduce.schema
, ret.collectionName
, self.db
, self.base);
model._mapreduce = true;
return promise.fulfill(model, stats);
}
promise.fulfill(ret, stats);
});
return promise;
}
o
is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. See node-mongodb-native mapReduce() documentation for more detail about options.
var o = {};
o.map = function () { emit(this.name, 1) }
o.reduce = function (k, vals) { return vals.length }
User.mapReduce(o, function (err, results) {
console.log(results)
})
query
{Object} query filter object.sort
{Object} sort input objects using this keylimit
{Number} max number of documentskeeptemp
{Boolean, default:false} keep temporary datafinalize
{Function} finalize functionscope
{Object} scope variables exposed to map/reduce/finalize during executionjsMode
{Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.Xverbose
{Boolean, default:false} provide statistics on job execution time.readPreference
{String}out*
{Object, default: {inline:1}} sets the output target for the map reduce job.{inline:1}
the results are returned in an array{replace: 'collectionName'}
add the results to collectionName: the results replace the collection{reduce: 'collectionName'}
add the results to collectionName: if dups are detected, uses the reducer / finalize functions{merge: 'collectionName'}
add the results to collectionName: if dups exist the new docs overwrite the oldIf options.out
is set to replace
, merge
, or reduce
, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the lean
option; meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).
var o = {};
o.map = function () { emit(this.name, 1) }
o.reduce = function (k, vals) { return vals.length }
o.out = { replace: 'createdCollectionNameForResults' }
o.verbose = true;
User.mapReduce(o, function (err, model, stats) {
console.log('map reduce took %d ms', stats.processtime)
model.find().where('value').gt(10).exec(function (err, docs) {
console.log(docs);
});
})
// a promise is returned so you may instead write
var promise = User.mapReduce(o);
promise.then(function (model, stats) {
console.log('map reduce took %d ms', stats.processtime)
return model.find().where('value').gt(10).exec();
}).then(function (docs) {
console.log(docs);
}).then(null, handleError).end()
docs
, options
, [cb(err,doc)]
)Populates document references.
show codeModel.populate = function (docs, paths, cb) {
var promise = new Promise(cb);
// always resolve on nextTick for consistent async behavior
function resolve () {
var args = utils.args(arguments);
process.nextTick(function () {
promise.resolve.apply(promise, args);
});
}
// normalized paths
var paths = utils.populate(paths);
var pending = paths.length;
if (0 === pending) {
resolve(null, docs);
return promise;
}
// each path has its own query options and must be executed separately
var i = pending;
var path;
var model = this;
while (i--) {
path = paths[i];
if ('function' === typeof path.model) model = path.model;
populate(model, docs, path, next);
}
return promise;
function next (err) {
if (err) return resolve(err);
if (--pending) return;
resolve(null, docs);
}
}
// populates a single object
User.findById(id, function (err, user) {
var opts = [
{ path: 'company', match: { x: 1 }, select: 'name' }
, { path: 'notes', options: { limit: 10 }, model: 'override' }
]
User.populate(user, opts, function (err, user) {
console.log(user);
})
})
// populates an array of objects
User.find(match, function (err, users) {
var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }]
var promise = User.populate(users, opts);
promise.then(console.log).end();
})
// imagine a Weapon model exists with two saved documents:
// { _id: 389, name: 'whip' }
// { _id: 8921, name: 'boomerang' }
var user = { name: 'Indiana Jones', weapon: 389 }
Weapon.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, user) {
console.log(user.weapon.name) // whip
})
// populate many plain objects
var users = [{ name: 'Indiana Jones', weapon: 389 }]
users.push({ name: 'Batman', weapon: 8921 })
Weapon.populate(users, { path: 'weapon' }, function (err, users) {
users.forEach(function (user) {
console.log('%s uses a %s', users.name, user.weapon.name)
// Indiana Jones uses a whip
// Batman uses a boomerang
})
})
// Note that we didn't need to specify the Weapon model because
// we were already using it's populate() method.
conditions
, [callback]
)Removes documents from the collection.
show codeModel.remove = function remove (conditions, callback) {
if ('function' === typeof conditions) {
callback = conditions;
conditions = {};
}
// get the mongodb collection object
var mq = new Query(conditions, {}, this, this.collection);
return mq.remove(callback);
};
Comment.remove({ title: 'baby born from alien father' }, function (err) {
});
To remove documents without waiting for a response from MongoDB, do not pass a callback
, then call exec
on the returned Query:
var query = Comment.remove({ _id: id });
query.exec();
This method sends a remove command directly to MongoDB, no Mongoose documents are involved. Because no Mongoose documents are involved, no middleware (hooks) are executed.
conditions
, doc
, [options]
, [callback]
)Updates documents in the database without returning them.
show codeModel.update = function update (conditions, doc, options, callback) {
var mq = new Query({}, {}, this, this.collection);
// gh-2406
// make local deep copy of conditions
conditions = utils.clone(conditions);
options = typeof options === 'function' ? options : utils.clone(options);
return mq.update(conditions, doc, options, callback);
};
MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, function (err, numberAffected, raw) {
if (err) return handleError(err);
console.log('The number of updated documents was %d', numberAffected);
console.log('The raw response from Mongo was ', raw);
});
safe
(boolean) safe mode (defaults to value set in schema (true))upsert
(boolean) whether to create the doc if it doesn't match (false)multi
(boolean) whether multiple documents should be updated (false)strict
(boolean) overrides the strict
option for this updateoverwrite
(boolean) disables update-only mode, allowing you to overwrite the doc (false)All update
values are cast to their appropriate SchemaTypes before being sent.
The callback
function receives (err, numberAffected, rawResponse)
.
err
is the error if any occurrednumberAffected
is the count of updated documents Mongo reportedrawResponse
is the full response from MongoAll top level keys which are not atomic
operation names are treated as set operations:
var query = { name: 'borne' };
Model.update(query, { name: 'jason borne' }, options, callback)
// is sent as
Model.update(query, { $set: { name: 'jason borne' }}, options, callback)
// if overwrite option is false. If overwrite is true, sent without the $set wrapper.
This helps prevent accidentally overwriting all documents in your collection with { name: 'jason borne' }
.
Be careful to not use an existing model instance for the update clause (this won't work and can cause weird behavior like infinite loops). Also, ensure that the update clause does not have an _id property, which causes Mongo to return a "Mod on _id not allowed" error.
To update documents without waiting for a response from MongoDB, do not pass a callback
, then call exec
on the returned Query:
Comment.update({ _id: id }, { $set: { text: 'changed' }}).exec();
Although values are casted to their appropriate types when using update, the following are not applied:
If you need those features, use the traditional approach of first retrieving the document.
Model.findOne({ name: 'borne' }, function (err, doc) {
if (err) ..
doc.name = 'jason borne';
doc.save(callback);
})
path
, [val]
)Creates a Query, applies the passed conditions, and returns the Query.
show codeModel.where = function where (path, val) {
// get the mongodb collection object
var mq = new Query({}, {}, this, this.collection).find({});
return mq.where.apply(mq, arguments);
};
For example, instead of writing:
User.find({age: {$gte: 21, $lte: 65}}, callback);
we can instead write:
User.where('age').gte(21).lte(65).exec(callback);
Since the Query class also supports where
you can continue chaining
User
.where('age').gte(21).lte(65)
.where('name', /^b/i)
... etc
The name of the model
show codeModel.prototype.modelName;
Model.prototype.$__handleSave = function $__handleSave(options) {
var self = this;
var innerPromise = new Promise;
if (!options.safe && this.schema.options.safe) {
options.safe = this.schema.options.safe;
}
if (this.isNew) {
// send entire doc
var toObjectOptions = {};
if (this.schema.options.toObject &&
this.schema.options.toObject.retainKeyOrder) {
toObjectOptions.retainKeyOrder = true;
}
toObjectOptions.depopulate = 1;
toObjectOptions._skipDepopulateTopLevel = true;
toObjectOptions.transform = false;
var obj = this.toObject(toObjectOptions);
if (!utils.object.hasOwnProperty(obj || {}, '_id')) {
// documents must have an _id else mongoose won't know
// what to update later if more changes are made. the user
// wouldn't know what _id was generated by mongodb either
// nor would the ObjectId generated my mongodb necessarily
// match the schema definition.
innerPromise.reject(new Error('document must have an _id before saving'));
return innerPromise;
}
this.$__version(true, obj);
this.collection.insert(obj, options.safe, function (err, ret) {
innerPromise.resolve(err, ret);
});
this.$__reset();
this.isNew = false;
this.emit('isNew', false);
// Make it possible to retry the insert
this.$__.inserting = true;
} else {
// Make sure we don't treat it as a new object on error,
// since it already exists
this.$__.inserting = false;
var delta = this.$__delta();
if (delta) {
if (delta instanceof Error) {
innerPromise.reject(delta);
return innerPromise;
}
var where = this.$__where(delta[0]);
this.collection.update(where, delta[1], options.safe, function (err, ret) {
innerPromise.resolve(err, ret);
});
} else {
this.$__reset();
innerPromise.fulfill(this);
}
this.emit('isNew', false);
}
return innerPromise;
};
Model.prototype.$__preSave = function(next) {
// Nested docs have their own presave
if (this.ownerDocument) {
return next();
}
// Check for preSave errors if we're not validating
if (!this.schema.options.validateBeforeSave) {
var preSaveErr = this.$__presaveValidate();
if (preSaveErr) {
return next(preSaveErr);
}
}
// Validate
if (this.schema.options.validateBeforeSave) {
this.validate(next);
} else {
next();
}
};
[ops]
)Aggregate constructor used for building aggregation pipelines.
new Aggregate();
new Aggregate({ $project: { a: 1, b: 1 } });
new Aggregate({ $project: { a: 1, b: 1 } }, { $skip: 5 });
new Aggregate([{ $project: { a: 1, b: 1 } }, { $skip: 5 }]);
Returned when calling Model.aggregate().
Model
.aggregate({ $match: { age: { $gte: 21 }}})
.unwind('tags')
.exec(callback)
function Aggregate () {
this._pipeline = [];
this._model = undefined;
this.options = undefined;
if (1 === arguments.length && util.isArray(arguments[0])) {
this.append.apply(this, arguments[0]);
} else {
this.append.apply(this, arguments);
}
}
value
, [tags]
)Sets the allowDiskUse option for the aggregation query (ignored for < 2.6.0)
Model.aggregate(..).allowDiskUse(true).exec(callback)
Aggregate.prototype.allowDiskUse = function(value) {
if (!this.options) this.options = {};
this.options.allowDiskUse = value;
return this;
};
ops
)Appends new operators to this aggregate pipeline
ops
<Object> operator(s) to appendaggregate.append({ $project: { field: 1 }}, { $limit: 2 });
// or pass an array
var pipeline = [{ $match: { daw: 'Logic Audio X' }} ];
aggregate.append(pipeline);
Aggregate.prototype.append = function () {
var args = utils.args(arguments)
, arg;
if (!args.every(isOperator)) {
throw new Error("Arguments must be aggregate pipeline operators");
}
this._pipeline = this._pipeline.concat(args);
return this;
}
model
)Binds this aggregate to a model.
model
<Model> the model to which the aggregate is to be boundAggregate.prototype.bind = function (model) {
this._model = model;
return this;
}
options
)Sets the cursor option option for the aggregation query (ignored for < 2.6.0)
options
<Object> set the cursor batch sizeModel.aggregate(..).cursor({ batchSize: 1000 }).exec(callback)
Aggregate.prototype.cursor = function(options) {
if (!this.options) this.options = {};
this.options.cursor = options;
return this;
};
[callback]
)Executes the aggregate pipeline on the currently bound Model.
[callback]
<Function> aggregate.exec(callback);
// Because a promise is returned, the `callback` is optional.
var promise = aggregate.exec();
promise.then(..);
Aggregate.prototype.exec = function (callback) {
var promise = new Promise();
if (callback) {
promise.addBack(callback);
}
if (!this._pipeline.length) {
promise.error(new Error("Aggregate has empty pipeline"));
return promise;
}
if (!this._model) {
promise.error(new Error("Aggregate not bound to any Model"));
return promise;
}
prepareDiscriminatorPipeline(this);
if (this.options && this.options.cursor) {
return this._model.collection.aggregate(this._pipeline, this.options || {});
}
this._model
.collection
.aggregate(this._pipeline, this.options || {}, promise.resolve.bind(promise));
return promise;
};
arg
)Appends a new custom $group operator to this aggregate pipeline.
arg
<Object> $group operator contentsaggregate.group({ _id: "$department" });
obj
)Checks whether an object is likely a pipeline operator
obj
<Object> object to checkfunction isOperator (obj) {
var k;
if ('object' !== typeof obj) {
return false;
}
k = Object.keys(obj);
return 1 === k.length && k.some(function (key) {
return '$' === key[0];
});
}
num
)Appends a new $limit operator to this aggregate pipeline.
num
<Number> maximum number of records to pass to the next stageaggregate.limit(10);
arg
)Appends a new custom $match operator to this aggregate pipeline.
arg
<Object> $match operator contentsaggregate.match({ department: { $in: [ "sales", "engineering" } } });
parameters
)Appends a new $geoNear operator to this aggregate pipeline.
parameters
<Object> MUST be used as the first operator in the pipeline.
aggregate.near({
near: [40.724, -73.997],
distanceField: "dist.calculated", // required
maxDistance: 0.008,
query: { type: "public" },
includeLocs: "dist.location",
uniqueDocs: true,
num: 5
});
arg
)Appends a new $project operator to this aggregate pipeline.
Mongoose query selection syntax is also supported.
// include a, include b, exclude _id
aggregate.project("a b -_id");
// or you may use object notation, useful when
// you have keys already prefixed with a "-"
aggregate.project({a: 1, b: 1, _id: 0});
// reshaping documents
aggregate.project({
newField: '$b.nested'
, plusTen: { $add: ['$val', 10]}
, sub: {
name: '$a'
}
})
// etc
aggregate.project({ salary_k: { $divide: [ "$salary", 1000 ] } });
Aggregate.prototype.project = function (arg) {
var fields = {};
if ('object' === typeof arg && !util.isArray(arg)) {
Object.keys(arg).forEach(function (field) {
fields[field] = arg[field];
});
} else if (1 === arguments.length && 'string' === typeof arg) {
arg.split(/\s+/).forEach(function (field) {
if (!field) return;
var include = '-' == field[0] ? 0 : 1;
if (include === 0) field = field.substring(1);
fields[field] = include;
});
} else {
throw new Error("Invalid project() argument. Must be string or object");
}
return this.append({ $project: fields });
}
pref
, [tags]
)Sets the readPreference option for the aggregation query.
Model.aggregate(..).read('primaryPreferred').exec(callback)
Aggregate.prototype.read = function (pref) {
if (!this.options) this.options = {};
read.apply(this, arguments);
return this;
};
num
)Appends a new $skip operator to this aggregate pipeline.
num
<Number> number of records to skip before next stageaggregate.skip(10);
arg
)Appends a new $sort operator to this aggregate pipeline.
If an object is passed, values allowed are asc
, desc
, ascending
, descending
, 1
, and -1
.
If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with -
which will be treated as descending.
// these are equivalent
aggregate.sort({ field: 'asc', test: -1 });
aggregate.sort('field -test');
Aggregate.prototype.sort = function (arg) {
// TODO refactor to reuse the query builder logic
var sort = {};
if ('Object' === arg.constructor.name) {
var desc = ['desc', 'descending', -1];
Object.keys(arg).forEach(function (field) {
sort[field] = desc.indexOf(arg[field]) === -1 ? 1 : -1;
});
} else if (1 === arguments.length && 'string' == typeof arg) {
arg.split(/\s+/).forEach(function (field) {
if (!field) return;
var ascend = '-' == field[0] ? -1 : 1;
if (ascend === -1) field = field.substring(1);
sort[field] = ascend;
});
} else {
throw new TypeError('Invalid sort() argument. Must be a string or object.');
}
return this.append({ $sort: sort });
}
fields
)Appends new custom $unwind operator(s) to this aggregate pipeline.
fields
<String> the field(s) to unwindaggregate.unwind("tags");
aggregate.unwind("a", "b", "c");
Aggregate.prototype.unwind = function () {
var args = utils.args(arguments);
return this.append.apply(this, args.map(function (arg) {
return { $unwind: '$' + arg };
}));
}
name
, conn
, opts
)Abstract Collection constructor
name
<String> name of the collectionconn
<Connection> A MongooseConnection instanceopts
<Object> optional collection optionsThis is the base class that drivers inherit from and implement.
function Collection (name, conn, opts) {
if (undefined === opts) opts = {};
if (undefined === opts.capped) opts.capped = {};
opts.bufferCommands = undefined === opts.bufferCommands
? true
: opts.bufferCommands;
if ('number' == typeof opts.capped) {
opts.capped = { size: opts.capped };
}
this.opts = opts;
this.name = name;
this.conn = conn;
this.queue = [];
this.buffer = this.opts.bufferCommands;
if (STATES.connected == this.conn.readyState) {
this.onOpen();
}
};
name
, args
)Queues a method for later execution when its
database connection opens.
Collection.prototype.addQueue = function (name, args) {
this.queue.push([name, args]);
return this;
};
Executes all queued methods and clears the queue.
show codeCollection.prototype.doQueue = function () {
for (var i = 0, l = this.queue.length; i < l; i++){
this[this.queue[i][0]].apply(this, this.queue[i][1]);
}
this.queue = [];
return this;
};
Abstract method that drivers must implement.
show codeCollection.prototype.ensureIndex = function(){
throw new Error('Collection#ensureIndex unimplemented by driver');
};
Abstract method that drivers must implement.
show codeCollection.prototype.find = function(){
throw new Error('Collection#find unimplemented by driver');
};
Abstract method that drivers must implement.
show codeCollection.prototype.findAndModify = function(){
throw new Error('Collection#findAndModify unimplemented by driver');
};
Abstract method that drivers must implement.
show codeCollection.prototype.findOne = function(){
throw new Error('Collection#findOne unimplemented by driver');
};
Abstract method that drivers must implement.
show codeCollection.prototype.getIndexes = function(){
throw new Error('Collection#getIndexes unimplemented by driver');
};
Abstract method that drivers must implement.
show codeCollection.prototype.insert = function(){
throw new Error('Collection#insert unimplemented by driver');
};
Abstract method that drivers must implement.
show codeCollection.prototype.mapReduce = function(){
throw new Error('Collection#mapReduce unimplemented by driver');
};
Called when the database disconnects
show codeCollection.prototype.onClose = function () {
if (this.opts.bufferCommands) {
this.buffer = true;
}
};
Called when the database connects
show codeCollection.prototype.onOpen = function () {
var self = this;
this.buffer = false;
self.doQueue();
};
Abstract method that drivers must implement.
show codeCollection.prototype.save = function(){
throw new Error('Collection#save unimplemented by driver');
};
Abstract method that drivers must implement.
show codeCollection.prototype.update = function(){
throw new Error('Collection#update unimplemented by driver');
};