2015-11-24 22:08:58 -08:00
/* eslint no-unused-vars: 1 */
2014-09-14 07:04:16 -04:00
/ * !
* Module dependencies .
* /
2015-11-24 22:08:58 -08:00
var Document = require ( './document' ) ;
var MongooseError = require ( './error' ) ;
var VersionError = MongooseError . VersionError ;
var DivergentArrayError = MongooseError . DivergentArrayError ;
var Query = require ( './query' ) ;
var Aggregate = require ( './aggregate' ) ;
var Schema = require ( './schema' ) ;
var Types = require ( './schema/index' ) ;
var utils = require ( './utils' ) ;
var hasOwnProperty = utils . object . hasOwnProperty ;
var isMongooseObject = utils . isMongooseObject ;
var EventEmitter = require ( 'events' ) . EventEmitter ;
var Promise = require ( './promise' ) ;
var util = require ( 'util' ) ;
var tick = utils . tick ;
var async = require ( 'async' ) ;
var PromiseProvider = require ( './promise_provider' ) ;
var VERSION _WHERE = 1 ,
VERSION _INC = 2 ,
VERSION _ALL = VERSION _WHERE | VERSION _INC ;
2014-09-14 07:04:16 -04:00
/ * *
* Model constructor
*
2015-11-24 22:08:58 -08:00
* Provides the interface to MongoDB collections as well as creates document instances .
*
* @ param { Object } doc values with which to create the document
2014-09-14 07:04:16 -04:00
* @ inherits Document
2015-11-24 22:08:58 -08:00
* @ event ` error ` : 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 .
2014-09-14 07:04:16 -04:00
* @ event ` index ` : Emitted after ` Model#ensureIndexes ` completes . If an error occurred it is passed with the event .
2015-11-24 22:08:58 -08:00
* @ event ` index-single-start ` : Emitted when an individual index starts within ` Model#ensureIndexes ` . The fields and options being used to build the index are also passed with the event .
* @ event ` index-single-done ` : Emitted when an individual index finishes within ` Model#ensureIndexes ` . If an error occurred it is passed with the event . The fields , options , and index name are also passed .
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
function Model ( doc , fields , skipId ) {
2014-09-14 07:04:16 -04:00
Document . call ( this , doc , fields , skipId ) ;
2015-11-24 22:08:58 -08:00
}
2014-09-14 07:04:16 -04:00
/ * !
* Inherits from Document .
*
* All Model . prototype features are available on
* top level ( non - sub ) documents .
* /
Model . prototype . _ _proto _ _ = Document . prototype ;
/ * *
* Connection the model uses .
*
* @ api public
* @ property db
* /
Model . prototype . db ;
/ * *
* Collection the model uses .
*
* @ api public
* @ property collection
* /
Model . prototype . collection ;
/ * *
* The name of the model
*
* @ api public
* @ property modelName
* /
Model . prototype . modelName ;
2015-11-24 22:08:58 -08:00
/ * *
* If this is a discriminator model , ` baseModelName ` is the name of
* the base model .
*
* @ api public
* @ property baseModelName
2014-09-14 07:04:16 -04:00
* /
2015-11-24 22:08:58 -08:00
Model . prototype . baseModelName ;
Model . prototype . $ _ _handleSave = function ( options , callback ) {
var _this = this ;
if ( ! options . safe && this . schema . options . safe ) {
options . safe = this . schema . options . safe ;
}
if ( typeof options . safe === 'boolean' ) {
options . safe = null ;
}
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.
setTimeout ( function ( ) {
callback ( new Error ( 'document must have an _id before saving' ) ) ;
} , 0 ) ;
return ;
}
this . $ _ _version ( true , obj ) ;
this . collection . insert ( obj , options . safe , function ( err , ret ) {
if ( err ) {
_this . isNew = true ;
_this . emit ( 'isNew' , true ) ;
callback ( err ) ;
return ;
}
callback ( null , 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 ) {
callback ( delta ) ;
return ;
}
var where = this . $ _ _where ( delta [ 0 ] ) ;
if ( where instanceof Error ) {
callback ( where ) ;
return ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
this . collection . update ( where , delta [ 1 ] , options . safe , function ( err , ret ) {
if ( err ) {
callback ( err ) ;
return ;
}
callback ( null , ret ) ;
} ) ;
} else {
this . $ _ _reset ( ) ;
callback ( ) ;
2014-09-14 07:04:16 -04:00
return ;
}
2015-11-24 22:08:58 -08:00
this . emit ( 'isNew' , false ) ;
}
} ;
/ * !
* ignore
* /
Model . prototype . $ _ _save = function ( options , callback ) {
var _this = this ;
_this . $ _ _handleSave ( options , function ( error , result ) {
if ( error ) {
return callback ( error ) ;
}
_this . $ _ _reset ( ) ;
_this . $ _ _storeShard ( ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var numAffected = 0 ;
2014-09-14 07:04:16 -04:00
if ( result ) {
2015-11-24 22:08:58 -08:00
if ( Array . isArray ( result ) ) {
numAffected = result . length ;
} else if ( result . result && result . result . n !== undefined ) {
numAffected = result . result . n ;
} else if ( result . result && result . result . nModified !== undefined ) {
numAffected = result . result . nModified ;
} else {
numAffected = result ;
}
2014-09-14 07:04:16 -04:00
}
// was this an update that required a version bump?
2015-11-24 22:08:58 -08:00
if ( _this . $ _ _ . version && ! _this . $ _ _ . inserting ) {
var doIncrement = VERSION _INC === ( VERSION _INC & _this . $ _ _ . version ) ;
_this . $ _ _ . version = undefined ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
if ( numAffected <= 0 ) {
2014-09-14 07:04:16 -04:00
// the update failed. pass an error back
2015-11-24 22:08:58 -08:00
var err = new VersionError ( ) ;
return callback ( err ) ;
}
// increment version if was successful
if ( doIncrement ) {
var key = _this . schema . options . versionKey ;
var version = _this . getValue ( key ) | 0 ;
_this . setValue ( key , version + 1 ) ;
2014-09-14 07:04:16 -04:00
}
}
2015-11-24 22:08:58 -08:00
_this . emit ( 'save' , _this , numAffected ) ;
callback ( null , _this , numAffected ) ;
2014-09-14 07:04:16 -04:00
} ) ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Saves this document .
*
* # # # # Example :
*
* product . sold = Date . now ( ) ;
2015-11-24 22:08:58 -08:00
* product . save ( function ( err , product , numAffected ) {
2014-09-14 07:04:16 -04:00
* if ( err ) . .
* } )
*
2015-11-24 22:08:58 -08:00
* The callback will receive three parameters
2014-09-14 07:04:16 -04:00
*
2015-11-24 22:08:58 -08:00
* 1. ` err ` if an error occurred
* 2. ` product ` which is the saved ` product `
* 3. ` numAffected ` will be 1 when the document was successfully persisted to MongoDB , otherwise 0. Unless you tweak mongoose 's internals, you don' t need to worry about checking this parameter for errors - checking ` err ` is sufficient to make sure your document was properly saved .
2014-09-14 07:04:16 -04:00
*
2015-11-24 22:08:58 -08:00
* As an extra measure of flow control , save will return a Promise .
* # # # # Example :
* product . save ( ) . then ( function ( product ) {
* ...
* } ) ;
2014-09-14 07:04:16 -04:00
*
2015-11-24 22:08:58 -08:00
* For legacy reasons , mongoose stores object keys in reverse order on initial
* save . That is , ` { a: 1, b: 2 } ` will be saved as ` { b: 2, a: 1 } ` in
* MongoDB . To override this behavior , set
* [ the ` toObject.retainKeyOrder ` option ] ( http : //mongoosejs.com/docs/api.html#document_Document-toObject)
* to true on your schema .
2014-09-14 07:04:16 -04:00
*
2015-11-24 22:08:58 -08:00
* @ param { Object } [ options ] options optional options
* @ param { Object } [ options . safe ] overrides [ schema ' s safe option ] ( http : //mongoosejs.com//docs/guide.html#safe)
* @ param { Boolean } [ options . validateBeforeSave ] set to false to save without validating .
2014-09-14 07:04:16 -04:00
* @ param { Function } [ fn ] optional callback
2015-11-24 22:08:58 -08:00
* @ return { Promise } Promise
2014-09-14 07:04:16 -04:00
* @ api public
* @ see middleware http : //mongoosejs.com/docs/middleware.html
* /
2015-11-24 22:08:58 -08:00
Model . prototype . save = function ( options , fn ) {
if ( 'function' == typeof options ) {
fn = options ;
options = undefined ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
if ( ! options ) {
options = { } ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
if ( options . _ _noPromise ) {
return this . $ _ _save ( options , fn ) ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var _this = this ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
return new Promise . ES6 ( function ( resolve , reject ) {
_this . $ _ _save ( options , function ( error , doc , numAffected ) {
if ( error ) {
fn && fn ( error ) ;
reject ( error ) ;
return ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
fn && fn ( null , doc , numAffected ) ;
resolve ( doc , numAffected ) ;
} ) ;
} ) ;
2014-09-14 07:04:16 -04:00
} ;
2015-11-24 22:08:58 -08:00
/ * *
* Determines whether versioning should be skipped for the given path
*
* @ param { Document } self
* @ param { String } path
* @ return { Boolean } true if versioning should be skipped for the given path
* /
function shouldSkipVersioning ( self , path ) {
var skipVersioning = self . schema . options . skipVersioning ;
if ( ! skipVersioning ) return false ;
// Remove any array indexes from the path
path = path . replace ( /\.\d+\./ , '.' ) ;
return skipVersioning [ path ] ;
}
2014-09-14 07:04:16 -04:00
/ * !
* Apply the operation to the delta ( update ) clause as
* well as track versioning for our where clause .
*
* @ param { Document } self
* @ param { Object } where
* @ param { Object } delta
* @ param { Object } data
* @ param { Mixed } val
* @ param { String } [ operation ]
* /
2015-11-24 22:08:58 -08:00
function operand ( self , where , delta , data , val , op ) {
2014-09-14 07:04:16 -04:00
// delta
op || ( op = '$set' ) ;
if ( ! delta [ op ] ) delta [ op ] = { } ;
delta [ op ] [ data . path ] = val ;
// disabled versioning?
if ( false === self . schema . options . versionKey ) return ;
2015-11-24 22:08:58 -08:00
// path excluded from versioning?
if ( shouldSkipVersioning ( self , data . path ) ) return ;
2014-09-14 07:04:16 -04:00
// already marked for versioning?
if ( VERSION _ALL === ( VERSION _ALL & self . $ _ _ . version ) ) return ;
switch ( op ) {
case '$set' :
case '$unset' :
case '$pop' :
case '$pull' :
case '$pullAll' :
case '$push' :
case '$pushAll' :
case '$addToSet' :
break ;
default :
// nothing to do
return ;
}
// ensure updates sent with positional notation are
// editing the correct array element.
// only increment the version if an array position changes.
// modifying elements of an array is ok if position does not change.
if ( '$push' == op || '$pushAll' == op || '$addToSet' == op ) {
self . $ _ _ . version = VERSION _INC ;
}
else if ( /^\$p/ . test ( op ) ) {
// potentially changing array positions
self . increment ( ) ;
}
else if ( Array . isArray ( val ) ) {
// $set an array
self . increment ( ) ;
}
// now handling $set, $unset
else if ( /\.\d+\.|\.\d+$/ . test ( data . path ) ) {
// subpath of array
self . $ _ _ . version = VERSION _WHERE ;
}
}
/ * !
* Compiles an update and where clause for a ` val ` with _atomics .
*
* @ param { Document } self
* @ param { Object } where
* @ param { Object } delta
* @ param { Object } data
* @ param { Array } value
* /
2015-11-24 22:08:58 -08:00
function handleAtomics ( self , where , delta , data , value ) {
2014-09-14 07:04:16 -04:00
if ( delta . $set && delta . $set [ data . path ] ) {
// $set has precedence over other atomics
return ;
}
if ( 'function' == typeof value . $ _ _getAtomics ) {
2015-11-24 22:08:58 -08:00
value . $ _ _getAtomics ( ) . forEach ( function ( atomic ) {
2014-09-14 07:04:16 -04:00
var op = atomic [ 0 ] ;
var val = atomic [ 1 ] ;
operand ( self , where , delta , data , val , op ) ;
2015-11-24 22:08:58 -08:00
} ) ;
2014-09-14 07:04:16 -04:00
return ;
}
// legacy support for plugins
2015-11-24 22:08:58 -08:00
var atomics = value . _atomics ,
ops = Object . keys ( atomics ) ,
i = ops . length ,
val ,
op ;
2014-09-14 07:04:16 -04:00
if ( 0 === i ) {
// $set
if ( isMongooseObject ( value ) ) {
value = value . toObject ( { depopulate : 1 } ) ;
} else if ( value . valueOf ) {
value = value . valueOf ( ) ;
}
return operand ( self , where , delta , data , value ) ;
}
while ( i -- ) {
op = ops [ i ] ;
val = atomics [ op ] ;
if ( isMongooseObject ( val ) ) {
2015-11-24 22:08:58 -08:00
val = val . toObject ( { depopulate : 1 } ) ;
2014-09-14 07:04:16 -04:00
} else if ( Array . isArray ( val ) ) {
2015-11-24 22:08:58 -08:00
val = val . map ( function ( mem ) {
2014-09-14 07:04:16 -04:00
return isMongooseObject ( mem )
? mem . toObject ( { depopulate : 1 } )
: mem ;
2015-11-24 22:08:58 -08:00
} ) ;
2014-09-14 07:04:16 -04:00
} else if ( val . valueOf ) {
2015-11-24 22:08:58 -08:00
val = val . valueOf ( ) ;
2014-09-14 07:04:16 -04:00
}
if ( '$addToSet' === op )
val = { $each : val } ;
operand ( self , where , delta , data , val , op ) ;
}
}
/ * *
* Produces a special query document of the modified properties used in updates .
*
* @ api private
* @ method $ _ _delta
* @ memberOf Model
* /
2015-11-24 22:08:58 -08:00
Model . prototype . $ _ _delta = function ( ) {
2014-09-14 07:04:16 -04:00
var dirty = this . $ _ _dirty ( ) ;
if ( ! dirty . length && VERSION _ALL != this . $ _ _ . version ) return ;
2015-11-24 22:08:58 -08:00
var where = { } ,
delta = { } ,
len = dirty . length ,
divergent = [ ] ,
d = 0 ;
where . _id = this . _doc . _id ;
2014-09-14 07:04:16 -04:00
for ( ; d < len ; ++ d ) {
2015-11-24 22:08:58 -08:00
var data = dirty [ d ] ;
var value = data . value ;
2014-09-14 07:04:16 -04:00
var match = checkDivergentArray ( this , data . path , value ) ;
if ( match ) {
divergent . push ( match ) ;
continue ;
}
if ( divergent . length ) continue ;
if ( undefined === value ) {
operand ( this , where , delta , data , 1 , '$unset' ) ;
} else if ( null === value ) {
operand ( this , where , delta , data , null ) ;
} else if ( value . _path && value . _atomics ) {
// arrays and other custom types (support plugins etc)
handleAtomics ( this , where , delta , data , value ) ;
} else if ( value . _path && Buffer . isBuffer ( value ) ) {
// MongooseBuffer
value = value . toObject ( ) ;
operand ( this , where , delta , data , value ) ;
} else {
value = utils . clone ( value , { depopulate : 1 } ) ;
operand ( this , where , delta , data , value ) ;
}
}
if ( divergent . length ) {
return new DivergentArrayError ( divergent ) ;
}
if ( this . $ _ _ . version ) {
this . $ _ _version ( where , delta ) ;
}
return [ where , delta ] ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * !
* Determine if array was populated with some form of filter and is now
* being updated in a manner which could overwrite data unintentionally .
*
2015-11-24 22:08:58 -08:00
* @ see https : //github.com/Automattic/mongoose/issues/1334
2014-09-14 07:04:16 -04:00
* @ param { Document } doc
* @ param { String } path
* @ return { String | undefined }
* /
2015-11-24 22:08:58 -08:00
function checkDivergentArray ( doc , path , array ) {
2014-09-14 07:04:16 -04:00
// see if we populated this path
var pop = doc . populated ( path , true ) ;
if ( ! pop && doc . $ _ _ . selected ) {
// If any array was selected using an $elemMatch projection, we deny the update.
// NOTE: MongoDB only supports projected $elemMatch on top level array.
var top = path . split ( '.' ) [ 0 ] ;
2015-11-24 22:08:58 -08:00
if ( ( doc . $ _ _ . selected [ top ] && doc . $ _ _ . selected [ top ] . $elemMatch ) ||
doc . $ _ _ . selected [ top + '.$' ] ) {
2014-09-14 07:04:16 -04:00
return top ;
}
}
2015-11-24 22:08:58 -08:00
if ( ! ( pop && array && array . isMongooseArray ) ) return ;
if ( ! pop . options ) {
return ;
}
2014-09-14 07:04:16 -04:00
// If the array was populated using options that prevented all
// documents from being returned (match, skip, limit) or they
// deselected the _id field, $pop and $set of the array are
// not safe operations. If _id was deselected, we do not know
// how to remove elements. $pop will pop off the _id from the end
// of the array in the db which is not guaranteed to be the
// same as the last element we have here. $set of the entire array
// would be similarily destructive as we never received all
// elements of the array and potentially would overwrite data.
var check = pop . options . match ||
pop . options . options && hasOwnProperty ( pop . options . options , 'limit' ) || // 0 is not permitted
pop . options . options && pop . options . options . skip || // 0 is permitted
pop . options . select && // deselected _id?
( 0 === pop . options . select . _id ||
2015-11-24 22:08:58 -08:00
/\s?-_id\s?/ . test ( pop . options . select ) ) ;
2014-09-14 07:04:16 -04:00
if ( check ) {
var atomics = array . _atomics ;
if ( 0 === Object . keys ( atomics ) . length || atomics . $set || atomics . $pop ) {
return path ;
}
}
}
/ * *
* Appends versioning to the where and update clauses .
*
* @ api private
* @ method $ _ _version
* @ memberOf Model
* /
2015-11-24 22:08:58 -08:00
Model . prototype . $ _ _version = function ( where , delta ) {
2014-09-14 07:04:16 -04:00
var key = this . schema . options . versionKey ;
if ( true === where ) {
// this is an insert
if ( key ) this . setValue ( key , delta [ key ] = 0 ) ;
return ;
}
// updates
// only apply versioning if our versionKey was selected. else
// there is no way to select the correct version. we could fail
// fast here and force them to include the versionKey but
// thats a bit intrusive. can we do this automatically?
if ( ! this . isSelected ( key ) ) {
return ;
}
// $push $addToSet don't need the where clause set
if ( VERSION _WHERE === ( VERSION _WHERE & this . $ _ _ . version ) ) {
where [ key ] = this . getValue ( key ) ;
}
if ( VERSION _INC === ( VERSION _INC & this . $ _ _ . version ) ) {
2015-11-24 22:08:58 -08:00
if ( ! delta . $set || typeof delta . $set [ key ] === 'undefined' ) {
delta . $inc || ( delta . $inc = { } ) ;
delta . $inc [ key ] = 1 ;
}
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Signal that we desire an increment of this documents version .
*
* # # # # Example :
*
* Model . findById ( id , function ( err , doc ) {
* doc . increment ( ) ;
* doc . save ( function ( err ) { . . } )
* } )
*
* @ see versionKeys http : //mongoosejs.com/docs/guide.html#versionKey
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . prototype . increment = function increment ( ) {
2014-09-14 07:04:16 -04:00
this . $ _ _ . version = VERSION _ALL ;
return this ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Returns a query object which applies shardkeys if they exist .
*
* @ api private
* @ method $ _ _where
* @ memberOf Model
* /
2015-11-24 22:08:58 -08:00
Model . prototype . $ _ _where = function _where ( where ) {
2014-09-14 07:04:16 -04:00
where || ( where = { } ) ;
2015-11-24 22:08:58 -08:00
var paths ,
len ;
if ( ! where . _id ) {
where . _id = this . _doc . _id ;
}
2014-09-14 07:04:16 -04:00
if ( this . $ _ _ . shardval ) {
2015-11-24 22:08:58 -08:00
paths = Object . keys ( this . $ _ _ . shardval ) ;
len = paths . length ;
2014-09-14 07:04:16 -04:00
for ( var i = 0 ; i < len ; ++ i ) {
where [ paths [ i ] ] = this . $ _ _ . shardval [ paths [ i ] ] ;
}
}
2015-11-24 22:08:58 -08:00
if ( ! this . _doc . _id ) {
return new Error ( 'No _id found on document!' ) ;
}
2014-09-14 07:04:16 -04:00
return where ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Removes this document from the db .
*
* # # # # Example :
* product . remove ( function ( err , product ) {
* if ( err ) return handleError ( err ) ;
* Product . findById ( product . _id , function ( err , product ) {
* console . log ( product ) // null
* } )
* } )
*
2015-11-24 22:08:58 -08:00
*
* As an extra measure of flow control , remove will return a Promise ( bound to ` fn ` if passed ) so it could be chained , or hooked to recive errors
*
* # # # # Example :
* product . remove ( ) . then ( function ( product ) {
* ...
* } ) . onRejected ( function ( err ) {
* assert . ok ( err )
* } )
*
* @ param { function ( err , product ) } [ fn ] optional callback
* @ return { Promise } Promise
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . prototype . remove = function remove ( options , fn ) {
var Promise = PromiseProvider . get ( ) ;
if ( 'function' == typeof options ) {
fn = options ;
options = undefined ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
if ( ! options ) {
options = { } ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
if ( this . $ _ _ . removing ) {
if ( fn ) {
this . $ _ _ . removing . then (
function ( res ) { fn ( null , res ) ; } ,
function ( err ) { fn ( err ) ; } ) ;
}
return this ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
var _this = this ;
var promise = this . $ _ _ . removing = new Promise . ES6 ( function ( resolve , reject ) {
var where = _this . $ _ _where ( ) ;
if ( where instanceof Error ) {
reject ( where ) ;
fn && fn ( where ) ;
2014-09-14 07:04:16 -04:00
return ;
}
2015-11-24 22:08:58 -08:00
if ( ! options . safe && _this . schema . options . safe ) {
options . safe = _this . schema . options . safe ;
}
_this . collection . remove ( where , options , function ( err ) {
if ( ! err ) {
_this . emit ( 'remove' , _this ) ;
resolve ( _this ) ;
fn && fn ( null , _this ) ;
return ;
}
reject ( err ) ;
fn && fn ( err ) ;
return ;
} ) ;
} ) ;
return promise ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Returns another Model instance .
*
* # # # # Example :
*
* var doc = new Tank ;
* doc . model ( 'User' ) . findById ( id , callback ) ;
*
* @ param { String } name model name
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . prototype . model = function model ( name ) {
2014-09-14 07:04:16 -04:00
return this . db . model ( name ) ;
} ;
2015-11-24 22:08:58 -08:00
/ * *
* Adds a discriminator type .
*
* # # # # Example :
*
* 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 ) ;
*
* @ param { String } name discriminator model name
* @ param { Schema } schema discriminator model schema
* @ api public
* /
Model . 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 ;
delete a . _id ;
delete b . _id ;
if ( ! utils . deepEqual ( a , b ) ) {
throw new Error ( "Discriminator options are not customizable " +
"(except toJSON, toObject, _id)" ) ;
}
} ) ( schema . options , baseSchema . options ) ;
var toJSON = schema . options . toJSON ;
var toObject = schema . options . toObject ;
var _id = schema . options . _id ;
schema . options = utils . clone ( baseSchema . options ) ;
if ( toJSON ) schema . options . toJSON = toJSON ;
if ( toObject ) schema . options . toObject = toObject ;
if ( typeof _id !== 'undefined' ) {
schema . options . _id = _id ;
}
schema . callQueue = baseSchema . callQueue . concat ( schema . callQueue . slice ( schema . _defaultMiddleware . length ) ) ;
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 ;
Object . defineProperty ( this . discriminators [ name ] , 'baseModelName' , {
value : this . modelName ,
configurable : true ,
writable : false
} ) ;
// apply methods and statics
applyMethods ( this . discriminators [ name ] , schema ) ;
applyStatics ( this . discriminators [ name ] , schema ) ;
return this . discriminators [ name ] ;
} ;
2014-09-14 07:04:16 -04:00
// Model (class) features
/ * !
* Give the constructor the ability to emit events .
* /
for ( var i in EventEmitter . prototype )
Model [ i ] = EventEmitter . prototype [ i ] ;
/ * *
* Called when the model compiles .
*
* @ api private
* /
2015-11-24 22:08:58 -08:00
Model . init = function init ( ) {
if ( ( this . schema . options . autoIndex ) ||
( this . schema . options . autoIndex === null && this . db . config . autoIndex ) ) {
2014-09-14 07:04:16 -04:00
this . ensureIndexes ( ) ;
}
this . schema . emit ( 'init' , this ) ;
} ;
/ * *
* Sends ` ensureIndex ` commands to mongo for each index declared in the schema .
*
* # # # # Example :
*
* Event . ensureIndexes ( function ( err ) {
* if ( err ) return handleError ( err ) ;
* } ) ;
*
* After completion , an ` index ` event is emitted on this ` Model ` passing an error if one occurred .
*
* # # # # Example :
*
* 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 . _
*
2015-11-24 22:08:58 -08:00
* 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 ] ( https : //github.com/Automattic/mongoose/issues/1365) for more information.
2014-09-14 07:04:16 -04:00
*
* @ param { Function } [ cb ] optional callback
2015-11-24 22:08:58 -08:00
* @ return { Promise }
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . ensureIndexes = function ensureIndexes ( cb ) {
var promise = new Promise ( cb ) ;
2014-09-14 07:04:16 -04:00
var indexes = this . schema . indexes ( ) ;
if ( ! indexes . length ) {
2015-11-24 22:08:58 -08:00
process . nextTick ( promise . fulfill . bind ( promise ) ) ;
return promise ;
2014-09-14 07:04:16 -04:00
}
// Indexes are created one-by-one to support how MongoDB < 2.4 deals
// with background indexes.
2015-11-24 22:08:58 -08:00
var self = this ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var done = function ( err ) {
if ( err && self . schema . options . emitIndexErrors ) {
self . emit ( 'error' , err ) ;
}
2014-09-14 07:04:16 -04:00
self . emit ( 'index' , err ) ;
2015-11-24 22:08:58 -08:00
promise . resolve ( err ) ;
} ;
var indexSingleDone = function ( err , fields , options , name ) {
self . emit ( 'index-single-done' , err , fields , options , name ) ;
} ;
var indexSingleStart = function ( fields , options ) {
self . emit ( 'index-single-start' , fields , options ) ;
} ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var create = function ( ) {
2014-09-14 07:04:16 -04:00
var index = indexes . shift ( ) ;
if ( ! index ) return done ( ) ;
2015-11-24 22:08:58 -08:00
var indexFields = index [ 0 ] ;
2014-09-14 07:04:16 -04:00
var options = index [ 1 ] ;
2015-11-24 22:08:58 -08:00
_handleSafe ( options ) ;
indexSingleStart ( indexFields , options ) ;
self . collection . ensureIndex ( indexFields , options , tick ( function ( err , name ) {
indexSingleDone ( err , indexFields , options , name ) ;
if ( err ) {
return done ( err ) ;
} else {
create ( ) ;
}
2014-09-14 07:04:16 -04:00
} ) ) ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
create ( ) ;
2015-11-24 22:08:58 -08:00
return promise ;
} ;
function _handleSafe ( options ) {
if ( options . safe ) {
if ( typeof options . safe === 'boolean' ) {
options . w = options . safe ;
delete options . safe ;
}
if ( typeof options . safe === 'object' ) {
options . w = options . safe . w ;
options . j = options . safe . j ;
options . wtimeout = options . safe . wtimeout ;
delete options . safe ;
}
}
2014-09-14 07:04:16 -04:00
}
/ * *
* Schema the model uses .
*
* @ property schema
* @ receiver Model
* @ api public
* /
Model . schema ;
/ * !
* Connection instance the model uses .
*
* @ property db
* @ receiver Model
* @ api public
* /
Model . db ;
/ * !
* Collection the model uses .
*
* @ property collection
* @ receiver Model
* @ api public
* /
Model . collection ;
/ * *
* Base Mongoose instance the model uses .
*
* @ property base
* @ receiver Model
* @ api public
* /
Model . base ;
2015-11-24 22:08:58 -08:00
/ * *
* Registered discriminators for this model .
*
* @ property discriminators
* @ receiver Model
* @ api public
* /
Model . discriminators ;
2014-09-14 07:04:16 -04:00
/ * *
* Removes documents from the collection .
*
* # # # # Example :
*
* Comment . remove ( { title : 'baby born from alien father' } , function ( err ) {
*
* } ) ;
*
* # # # # Note :
*
* To remove documents without waiting for a response from MongoDB , do not pass a ` callback ` , then call ` exec ` on the returned [ Query ] ( # query - js ) :
*
* var query = Comment . remove ( { _id : id } ) ;
* query . exec ( ) ;
*
* # # # # Note :
*
* 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 _ .
*
* @ param { Object } conditions
* @ param { Function } [ callback ]
2015-11-24 22:08:58 -08:00
* @ return { Promise } Promise
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . remove = function remove ( conditions , callback ) {
2014-09-14 07:04:16 -04:00
if ( 'function' === typeof conditions ) {
callback = conditions ;
conditions = { } ;
}
2015-11-24 22:08:58 -08:00
// get the mongodb collection object
var mq = new Query ( conditions , { } , this , this . collection ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
return mq . remove ( callback ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Finds documents
*
* The ` conditions ` are cast to their respective SchemaTypes before the command is sent .
*
* # # # # Examples :
*
* // 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 ) { } ) ;
*
* @ param { Object } conditions
2015-11-24 22:08:58 -08:00
* @ param { Object } [ projection ] optional fields to return ( http : //bit.ly/1HotzBo)
2014-09-14 07:04:16 -04:00
* @ param { Object } [ options ] optional
* @ param { Function } [ callback ]
* @ return { Query }
* @ see field selection # query _Query - select
* @ see promise # promise - js
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . find = function find ( conditions , projection , options , callback ) {
2014-09-14 07:04:16 -04:00
if ( 'function' == typeof conditions ) {
callback = conditions ;
conditions = { } ;
2015-11-24 22:08:58 -08:00
projection = null ;
2014-09-14 07:04:16 -04:00
options = null ;
2015-11-24 22:08:58 -08:00
} else if ( 'function' == typeof projection ) {
callback = projection ;
projection = null ;
2014-09-14 07:04:16 -04:00
options = null ;
} else if ( 'function' == typeof options ) {
callback = options ;
options = null ;
}
2015-11-24 22:08:58 -08:00
// get the raw mongodb collection object
var mq = new Query ( { } , { } , this , this . collection ) ;
mq . select ( projection ) ;
mq . setOptions ( options ) ;
if ( this . schema . discriminatorMapping && mq . selectedInclusively ( ) ) {
mq . select ( this . schema . options . discriminatorKey ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
return mq . find ( conditions , callback ) ;
} ;
2014-09-14 07:04:16 -04:00
/ * *
2015-11-24 22:08:58 -08:00
* Finds a single document by its _id field . ` findById(id) ` is almost *
* equivalent to ` findOne({ _id: id }) ` .
2014-09-14 07:04:16 -04:00
*
* The ` id ` is cast based on the Schema before sending the command .
*
2015-11-24 22:08:58 -08:00
* Note : ` findById() ` triggers ` findOne ` hooks .
*
* * Except for how it treats ` undefined ` . Because the MongoDB driver
* deletes keys that have value ` undefined ` , ` findById(undefined) ` gets
* translated to ` findById({ _id: null }) ` .
*
2014-09-14 07:04:16 -04:00
* # # # # Example :
*
* // 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 ) { } ) ;
*
2015-11-24 22:08:58 -08:00
* @ param { Object | String | Number } id value of ` _id ` to query by
* @ param { Object } [ projection ] optional fields to return ( http : //bit.ly/1HotzBo)
2014-09-14 07:04:16 -04:00
* @ param { Object } [ options ] optional
* @ param { Function } [ callback ]
* @ return { Query }
* @ see field selection # query _Query - select
* @ see lean queries # query _Query - lean
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . findById = function findById ( id , projection , options , callback ) {
if ( typeof id === 'undefined' ) {
id = null ;
}
return this . findOne ( { _id : id } , projection , options , callback ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Finds one document .
*
* The ` conditions ` are cast to their respective SchemaTypes before the command is sent .
*
* # # # # Example :
*
* // 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 ) ;
*
2015-11-24 22:08:58 -08:00
* @ param { Object } [ conditions ]
* @ param { Object } [ projection ] optional fields to return ( http : //bit.ly/1HotzBo)
2014-09-14 07:04:16 -04:00
* @ param { Object } [ options ] optional
* @ param { Function } [ callback ]
* @ return { Query }
* @ see field selection # query _Query - select
* @ see lean queries # query _Query - lean
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . findOne = function findOne ( conditions , projection , options , callback ) {
2014-09-14 07:04:16 -04:00
if ( 'function' == typeof options ) {
callback = options ;
options = null ;
2015-11-24 22:08:58 -08:00
} else if ( 'function' == typeof projection ) {
callback = projection ;
projection = null ;
2014-09-14 07:04:16 -04:00
options = null ;
} else if ( 'function' == typeof conditions ) {
callback = conditions ;
conditions = { } ;
2015-11-24 22:08:58 -08:00
projection = null ;
2014-09-14 07:04:16 -04:00
options = null ;
}
2015-11-24 22:08:58 -08:00
// get the mongodb collection object
var mq = new Query ( { } , { } , this , this . collection ) ;
mq . select ( projection ) ;
mq . setOptions ( options ) ;
if ( this . schema . discriminatorMapping && mq . selectedInclusively ( ) ) {
mq . select ( this . schema . options . discriminatorKey ) ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
return mq . findOne ( conditions , callback ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Counts number of matching documents in a database collection .
*
* # # # # Example :
*
* Adventure . count ( { type : 'jungle' } , function ( err , count ) {
* if ( err ) . .
* console . log ( 'there are %d jungle adventures' , count ) ;
* } ) ;
*
* @ param { Object } conditions
* @ param { Function } [ callback ]
* @ return { Query }
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . count = function count ( conditions , callback ) {
2014-09-14 07:04:16 -04:00
if ( 'function' === typeof conditions )
callback = conditions , conditions = { } ;
2015-11-24 22:08:58 -08:00
// get the mongodb collection object
var mq = new Query ( { } , { } , this , this . collection ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
return mq . count ( conditions , callback ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
2015-11-24 22:08:58 -08:00
* Creates a Query for a ` distinct ` operation .
*
* Passing a ` callback ` immediately executes the query .
2014-09-14 07:04:16 -04:00
*
* # # # # Example
*
* 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 ) ;
* } )
*
2015-11-24 22:08:58 -08:00
* var query = Link . distinct ( 'url' ) ;
* query . exec ( callback ) ;
*
2014-09-14 07:04:16 -04:00
* @ param { String } field
* @ param { Object } [ conditions ] optional
* @ param { Function } [ callback ]
* @ return { Query }
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . distinct = function distinct ( field , conditions , callback ) {
// get the mongodb collection object
var mq = new Query ( { } , { } , this , this . collection ) ;
2014-09-14 07:04:16 -04:00
if ( 'function' == typeof conditions ) {
callback = conditions ;
conditions = { } ;
}
2015-11-24 22:08:58 -08:00
return mq . distinct ( field , conditions , callback ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Creates a Query , applies the passed conditions , and returns the Query .
*
* 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
*
* @ param { String } path
* @ param { Object } [ val ] optional value
* @ return { Query }
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . where = function where ( path , val ) {
// get the mongodb collection object
var mq = new Query ( { } , { } , this , this . collection ) . find ( { } ) ;
return mq . where . apply ( mq , arguments ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* 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 ) { } ) ;
*
* @ param { String | Function } argument is a javascript string or anonymous function
* @ method $where
* @ memberOf Model
* @ return { Query }
* @ see Query . $where # query _Query - % 24 where
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . $where = function $where ( ) {
var mq = new Query ( { } , { } , this , this . collection ) . find ( { } ) ;
return mq . $where . apply ( mq , arguments ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* 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 else a Query object is returned .
*
* # # # # Options :
*
2015-11-24 22:08:58 -08:00
* - ` new ` : bool - if true , return the modified document rather than the original . defaults to false ( changed in 4.0 )
2014-09-14 07:04:16 -04:00
* - ` upsert ` : 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 update
* - ` select ` : sets the document fields to return
*
* # # # # Examples :
*
* A . 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
*
* # # # # Note :
*
* All top level update keys which are not ` atomic ` operation names are treated as set operations :
*
* # # # # Example :
*
* 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' } ` .
*
* # # # # Note :
*
2015-11-24 22:08:58 -08:00
* Values are cast to their appropriate types when using the findAndModify helpers .
* However , the below are never executed .
2014-09-14 07:04:16 -04:00
*
* - defaults
* - setters
*
2015-11-24 22:08:58 -08:00
* ` findAndModify ` helpers support limited defaults and validation . You can
* enable these by setting the ` setDefaultsOnInsert ` and ` runValidators ` options ,
* respectively .
2014-09-14 07:04:16 -04:00
*
2015-11-24 22:08:58 -08:00
* If you need full - fledged validation , use the traditional approach of first
* retrieving the document .
*
* Model . findById ( id , function ( err , doc ) {
2014-09-14 07:04:16 -04:00
* if ( err ) . .
* doc . name = 'jason borne' ;
* doc . save ( callback ) ;
2015-11-24 22:08:58 -08:00
* } ) ;
2014-09-14 07:04:16 -04:00
*
* @ param { Object } [ conditions ]
* @ param { Object } [ update ]
* @ param { Object } [ options ]
* @ param { Function } [ callback ]
* @ return { Query }
* @ see mongodb http : //www.mongodb.org/display/DOCS/findAndModify+Command
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . findOneAndUpdate = function ( conditions , update , options , callback ) {
2014-09-14 07:04:16 -04:00
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.\n\n'
+ ' ' + this . modelName + '.findOneAndUpdate(conditions, update, options, callback)\n'
+ ' ' + this . modelName + '.findOneAndUpdate(conditions, update, options)\n'
+ ' ' + this . modelName + '.findOneAndUpdate(conditions, update)\n'
+ ' ' + this . modelName + '.findOneAndUpdate(update)\n'
+ ' ' + this . modelName + '.findOneAndUpdate()\n' ;
2015-11-24 22:08:58 -08:00
throw new TypeError ( msg ) ;
2014-09-14 07:04:16 -04:00
}
update = conditions ;
conditions = undefined ;
}
var fields ;
if ( options && options . fields ) {
fields = options . fields ;
options . fields = undefined ;
}
2015-11-24 22:08:58 -08:00
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 ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var mq = new Query ( { } , { } , this , this . collection ) ;
mq . select ( fields ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
return mq . findOneAndUpdate ( conditions , update , options , callback ) ;
} ;
2014-09-14 07:04:16 -04:00
/ * *
2015-11-24 22:08:58 -08:00
* Issues a mongodb findAndModify update command by a document ' s _id field .
* ` findByIdAndUpdate(id, ...) ` is equivalent to ` findOneAndUpdate({ _id: id }, ...) ` .
2014-09-14 07:04:16 -04:00
*
2015-11-24 22:08:58 -08:00
* 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 .
*
* This function triggers ` findOneAndUpdate ` middleware .
2014-09-14 07:04:16 -04:00
*
* # # # # Options :
*
2015-11-24 22:08:58 -08:00
* - ` new ` : bool - true to return the modified document rather than the original . defaults to false
2014-09-14 07:04:16 -04:00
* - ` upsert ` : 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 update
* - ` select ` : sets the document fields to return
*
* # # # # Examples :
*
* A . 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
*
* # # # # Note :
*
* All top level update keys which are not ` atomic ` operation names are treated as set operations :
*
* # # # # Example :
*
* 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' } ` .
*
* # # # # Note :
*
2015-11-24 22:08:58 -08:00
* Values are cast to their appropriate types when using the findAndModify helpers .
* However , the below are never executed .
2014-09-14 07:04:16 -04:00
*
* - defaults
* - setters
*
2015-11-24 22:08:58 -08:00
* ` findAndModify ` helpers support limited defaults and validation . You can
* enable these by setting the ` setDefaultsOnInsert ` and ` runValidators ` options ,
* respectively .
*
* If you need full - fledged validation , use the traditional approach of first
* retrieving the document .
2014-09-14 07:04:16 -04:00
*
* Model . findById ( id , function ( err , doc ) {
* if ( err ) . .
* doc . name = 'jason borne' ;
* doc . save ( callback ) ;
2015-11-24 22:08:58 -08:00
* } ) ;
2014-09-14 07:04:16 -04:00
*
2015-11-24 22:08:58 -08:00
* @ param { Object | Number | String } id value of ` _id ` to query by
2014-09-14 07:04:16 -04:00
* @ param { Object } [ update ]
* @ param { Object } [ options ]
* @ param { Function } [ callback ]
* @ return { Query }
* @ see Model . findOneAndUpdate # model _Model . findOneAndUpdate
* @ see mongodb http : //www.mongodb.org/display/DOCS/findAndModify+Command
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . findByIdAndUpdate = function ( id , update , options , callback ) {
2014-09-14 07:04:16 -04:00
var args ;
if ( 1 === arguments . length ) {
if ( 'function' == typeof id ) {
var msg = 'Model.findByIdAndUpdate(): First argument must not be a function.\n\n'
+ ' ' + this . modelName + '.findByIdAndUpdate(id, callback)\n'
+ ' ' + this . modelName + '.findByIdAndUpdate(id)\n'
+ ' ' + this . modelName + '.findByIdAndUpdate()\n' ;
2015-11-24 22:08:58 -08:00
throw new TypeError ( msg ) ;
2014-09-14 07:04:16 -04:00
}
return this . findOneAndUpdate ( { _id : id } , undefined ) ;
}
args = utils . args ( arguments , 1 ) ;
2015-11-24 22:08:58 -08:00
// if a model is passed in instead of an id
if ( id instanceof Document ) {
id = id . _id ;
}
if ( id ) {
args . unshift ( { _id : id } ) ;
}
2014-09-14 07:04:16 -04:00
return this . findOneAndUpdate . apply ( this , args ) ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Issue 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 else a Query object is returned .
*
* # # # # Options :
*
* - ` sort ` : if multiple docs are found by the conditions , sets the sort order to choose which doc to update
* - ` select ` : sets the document fields to return
*
* # # # # Examples :
*
* A . 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
*
2015-11-24 22:08:58 -08:00
* Values are cast to their appropriate types when using the findAndModify helpers .
* However , the below are never executed .
2014-09-14 07:04:16 -04:00
*
* - defaults
* - setters
*
2015-11-24 22:08:58 -08:00
* ` findAndModify ` helpers support limited defaults and validation . You can
* enable these by setting the ` setDefaultsOnInsert ` and ` runValidators ` options ,
* respectively .
*
* If you need full - fledged validation , use the traditional approach of first
* retrieving the document .
2014-09-14 07:04:16 -04:00
*
* Model . findById ( id , function ( err , doc ) {
* if ( err ) . .
2015-11-24 22:08:58 -08:00
* doc . name = 'jason borne' ;
* doc . save ( callback ) ;
* } ) ;
2014-09-14 07:04:16 -04:00
*
* @ param { Object } conditions
* @ param { Object } [ options ]
* @ param { Function } [ callback ]
* @ return { Query }
* @ see mongodb http : //www.mongodb.org/display/DOCS/findAndModify+Command
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . findOneAndRemove = function ( conditions , options , callback ) {
2014-09-14 07:04:16 -04:00
if ( 1 === arguments . length && 'function' == typeof conditions ) {
var msg = 'Model.findOneAndRemove(): First argument must not be a function.\n\n'
+ ' ' + this . modelName + '.findOneAndRemove(conditions, callback)\n'
+ ' ' + this . modelName + '.findOneAndRemove(conditions)\n'
+ ' ' + this . modelName + '.findOneAndRemove()\n' ;
2015-11-24 22:08:58 -08:00
throw new TypeError ( msg ) ;
2014-09-14 07:04:16 -04:00
}
if ( 'function' == typeof options ) {
callback = options ;
options = undefined ;
}
var fields ;
if ( options ) {
fields = options . select ;
options . select = undefined ;
}
2015-11-24 22:08:58 -08:00
var mq = new Query ( { } , { } , this , this . collection ) ;
mq . select ( fields ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
return mq . findOneAndRemove ( conditions , options , callback ) ;
} ;
2014-09-14 07:04:16 -04:00
/ * *
2015-11-24 22:08:58 -08:00
* Issue a mongodb findAndModify remove command by a document ' s _id field . ` findByIdAndRemove(id, ...) ` is equivalent to ` findOneAndRemove({ _id: id }, ...) ` .
2014-09-14 07:04:16 -04:00
*
* 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 .
*
* # # # # Options :
*
* - ` sort ` : if multiple docs are found by the conditions , sets the sort order to choose which doc to update
* - ` select ` : sets the document fields to return
*
* # # # # Examples :
*
* A . 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
*
2015-11-24 22:08:58 -08:00
* @ param { Object | Number | String } id value of ` _id ` to query by
2014-09-14 07:04:16 -04:00
* @ param { Object } [ options ]
* @ param { Function } [ callback ]
* @ return { Query }
* @ see Model . findOneAndRemove # model _Model . findOneAndRemove
* @ see mongodb http : //www.mongodb.org/display/DOCS/findAndModify+Command
* /
2015-11-24 22:08:58 -08:00
Model . findByIdAndRemove = function ( id , options , callback ) {
2014-09-14 07:04:16 -04:00
if ( 1 === arguments . length && 'function' == typeof id ) {
var msg = 'Model.findByIdAndRemove(): First argument must not be a function.\n\n'
+ ' ' + this . modelName + '.findByIdAndRemove(id, callback)\n'
+ ' ' + this . modelName + '.findByIdAndRemove(id)\n'
+ ' ' + this . modelName + '.findByIdAndRemove()\n' ;
2015-11-24 22:08:58 -08:00
throw new TypeError ( msg ) ;
2014-09-14 07:04:16 -04:00
}
return this . findOneAndRemove ( { _id : id } , options , callback ) ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Shortcut for creating a new Document that is automatically saved to the db if valid .
*
* # # # # Example :
*
2015-11-24 22:08:58 -08:00
* // pass individual docs
2014-09-14 07:04:16 -04:00
* Candy . create ( { type : 'jelly bean' } , { type : 'snickers' } , function ( err , jellybean , snickers ) {
* if ( err ) // ...
* } ) ;
*
2015-11-24 22:08:58 -08:00
* // pass an array
2014-09-14 07:04:16 -04:00
* var array = [ { type : 'jelly bean' } , { type : 'snickers' } ] ;
2015-11-24 22:08:58 -08:00
* Candy . create ( array , function ( err , candies ) {
2014-09-14 07:04:16 -04:00
* if ( err ) // ...
2015-11-24 22:08:58 -08:00
*
* var jellybean = candies [ 0 ] ;
* var snickers = candies [ 1 ] ;
* // ...
2014-09-14 07:04:16 -04:00
* } ) ;
*
2015-11-24 22:08:58 -08:00
* // callback is optional; use the returned promise if you like:
* var promise = Candy . create ( { type : 'jawbreaker' } ) ;
* promise . then ( function ( jawbreaker ) {
* // ...
* } )
*
* @ param { Array | Object ... } doc ( s )
* @ param { Function } [ callback ] callback
* @ return { Promise }
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . create = function create ( doc , callback ) {
var args ,
cb ;
2014-09-14 07:04:16 -04:00
if ( Array . isArray ( doc ) ) {
args = doc ;
2015-11-24 22:08:58 -08:00
cb = callback ;
2014-09-14 07:04:16 -04:00
} else {
2015-11-24 22:08:58 -08:00
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 ) ;
}
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
var Promise = PromiseProvider . get ( ) ;
var ModelConstructor = this ;
var promise = new Promise . ES6 ( function ( resolve , reject ) {
if ( args . length === 0 ) {
process . nextTick ( function ( ) {
cb && cb ( null ) ;
resolve ( null ) ;
} ) ;
return ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var toExecute = [ ] ;
args . forEach ( function ( doc ) {
toExecute . push ( function ( callback ) {
var toSave = new ModelConstructor ( doc ) ;
var callbackWrapper = function ( error , doc ) {
if ( error ) {
return callback ( error ) ;
}
callback ( null , doc ) ;
} ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
// Hack to avoid getting a promise because of
// $__registerHooksFromSchema
if ( toSave . $ _ _original _save ) {
toSave . $ _ _original _save ( { _ _noPromise : true } , callbackWrapper ) ;
} else {
toSave . save ( { _ _noPromise : true } , callbackWrapper ) ;
}
} ) ;
} ) ;
async . parallel ( toExecute , function ( error , savedDocs ) {
if ( error ) {
cb && cb ( error ) ;
reject ( error ) ;
return ;
}
if ( doc instanceof Array ) {
resolve ( savedDocs ) ;
cb && cb . call ( ModelConstructor , null , savedDocs ) ;
} else {
resolve . apply ( promise , savedDocs ) ;
if ( cb ) {
savedDocs . unshift ( null ) ;
cb . apply ( ModelConstructor , savedDocs ) ;
}
}
2014-09-14 07:04:16 -04:00
} ) ;
} ) ;
2015-11-24 22:08:58 -08:00
return promise ;
} ;
/ * *
* 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 .
*
* # # # # Example :
*
* // hydrate previous data into a Mongoose document
* var mongooseCandy = Candy . hydrate ( { _id : '54108337212ffb6d459f854c' , type : 'jelly bean' } ) ;
*
* @ param { Object } obj
* @ return { Document }
* @ api public
* /
Model . hydrate = function ( obj ) {
var model = require ( './queryhelpers' ) . createModel ( this , obj ) ;
model . init ( obj ) ;
return model ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Updates documents in the database without returning them .
*
* # # # # Examples :
*
* MyModel . update ( { age : { $gt : 18 } } , { oldEnough : true } , fn ) ;
2015-11-24 22:08:58 -08:00
* MyModel . update ( { name : 'Tobi' } , { ferret : true } , { multi : true } , function ( err , raw ) {
2014-09-14 07:04:16 -04:00
* if ( err ) return handleError ( err ) ;
* console . log ( 'The raw response from Mongo was ' , raw ) ;
* } ) ;
*
* # # # # Valid options :
*
* - ` 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 update
2015-11-24 22:08:58 -08:00
* - ` overwrite ` ( boolean ) disables update - only mode , allowing you to overwrite the doc ( false )
2014-09-14 07:04:16 -04:00
*
* All ` update ` values are cast to their appropriate SchemaTypes before being sent .
*
2015-11-24 22:08:58 -08:00
* The ` callback ` function receives ` (err, rawResponse) ` .
2014-09-14 07:04:16 -04:00
*
* - ` err ` is the error if any occurred
* - ` rawResponse ` is the full response from Mongo
*
* # # # # Note :
*
* All top level keys which are not ` atomic ` operation names are treated as set operations :
*
* # # # # Example :
*
* var query = { name : 'borne' } ;
* Model . update ( query , { name : 'jason borne' } , options , callback )
*
* // is sent as
* Model . update ( query , { $set : { name : 'jason borne' } } , options , callback )
2015-11-24 22:08:58 -08:00
* // if overwrite option is false. If overwrite is true, sent without the $set wrapper.
2014-09-14 07:04:16 -04:00
*
* This helps prevent accidentally overwriting all documents in your collection with ` { name: 'jason borne' } ` .
*
* # # # # Note :
*
* 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 .
*
* # # # # Note :
*
* To update documents without waiting for a response from MongoDB , do not pass a ` callback ` , then call ` exec ` on the returned [ Query ] ( # query - js ) :
*
* Comment . update ( { _id : id } , { $set : { text : 'changed' } } ) . exec ( ) ;
*
* # # # # Note :
*
* Although values are casted to their appropriate types when using update , the following are * not * applied :
*
* - defaults
* - setters
* - validators
* - middleware
*
* 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 ) ;
* } )
*
2015-11-24 22:08:58 -08:00
* @ see strict http : //mongoosejs.com/docs/guide.html#strict
* @ see response http : //docs.mongodb.org/v2.6/reference/command/update/#output
2014-09-14 07:04:16 -04:00
* @ param { Object } conditions
2015-11-24 22:08:58 -08:00
* @ param { Object } doc
2014-09-14 07:04:16 -04:00
* @ param { Object } [ options ]
* @ param { Function } [ callback ]
* @ return { Query }
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . update = function update ( conditions , doc , options , callback ) {
var mq = new Query ( { } , { } , this , this . collection ) ;
// gh-2406
// make local deep copy of conditions
if ( conditions instanceof Document ) {
conditions = conditions . toObject ( ) ;
} else {
conditions = utils . clone ( conditions , { retainKeyOrder : true } ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
options = typeof options === 'function' ? options : utils . clone ( options ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
return mq . update ( conditions , doc , options , callback ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Executes a mapReduce command .
*
2015-11-24 22:08:58 -08:00
* ` 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 ] ( http : //mongodb.github.io/node-mongodb-native/api-generated/collection.html#mapreduce) for more detail about options.
2014-09-14 07:04:16 -04:00
*
* # # # # Example :
*
* 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 )
* } )
*
* # # # # Other options :
*
* - ` query ` { Object } query filter object .
2015-11-24 22:08:58 -08:00
* - ` sort ` { Object } sort input objects using this key
2014-09-14 07:04:16 -04:00
* - ` limit ` { Number } max number of documents
* - ` keeptemp ` { Boolean , default : false } keep temporary data
* - ` finalize ` { Function } finalize function
* - ` scope ` { Object } scope variables exposed to map / reduce / finalize during execution
* - ` jsMode ` { Boolean , default : false } it is possible to make the execution stay in JS . Provided in MongoDB > 2.0 . X
* - ` verbose ` { Boolean , default : false } provide statistics on job execution time .
2015-11-24 22:08:58 -08:00
* - ` readPreference ` { String }
2014-09-14 07:04:16 -04:00
* - ` out* ` { Object , default : { inline : 1 } } sets the output target for the map reduce job .
*
* # # # # * out options :
*
* - ` {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 old
*
* If ` 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 ) .
*
* # # # # Example :
*
* var o = { } ;
* o . map = function ( ) { emit ( this . name , 1 ) }
* o . reduce = function ( k , vals ) { return vals . length }
* o . out = { replace : 'createdCollectionNameForResults' }
* o . verbose = true ;
2015-11-24 22:08:58 -08:00
*
2014-09-14 07:04:16 -04:00
* 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 ) ;
* } ) ;
* } )
*
2015-11-24 22:08:58 -08:00
* // 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 ( )
*
2014-09-14 07:04:16 -04:00
* @ param { Object } o an object specifying map - reduce options
2015-11-24 22:08:58 -08:00
* @ param { Function } [ callback ] optional callback
2014-09-14 07:04:16 -04:00
* @ see http : //www.mongodb.org/display/DOCS/MapReduce
2015-11-24 22:08:58 -08:00
* @ return { Promise }
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . mapReduce = function mapReduce ( o , callback ) {
var promise = new Promise ( callback ) ;
2014-09-14 07:04:16 -04:00
var self = this ;
if ( ! Model . mapReduce . schema ) {
2015-11-24 22:08:58 -08:00
var opts = { noId : true , noVirtualId : true , strict : false } ;
2014-09-14 07:04:16 -04:00
Model . mapReduce . schema = new Schema ( { } , opts ) ;
}
if ( ! o . out ) o . out = { inline : 1 } ;
2015-11-24 22:08:58 -08:00
if ( false !== o . verbose ) o . verbose = true ;
2014-09-14 07:04:16 -04:00
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 ;
}
2015-11-24 22:08:58 -08:00
this . collection . mapReduce ( null , null , o , function ( err , ret , stats ) {
if ( err ) return promise . error ( err ) ;
2014-09-14 07:04:16 -04:00
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 ;
2015-11-24 22:08:58 -08:00
return promise . fulfill ( model , stats ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
promise . fulfill ( ret , stats ) ;
2014-09-14 07:04:16 -04:00
} ) ;
2015-11-24 22:08:58 -08:00
return promise ;
} ;
/ * *
* geoNear support for Mongoose
*
* # # # # Options :
* - ` lean ` { Boolean } return the raw object
* - All options supported by the driver are also supported
*
* # # # # Example :
*
* // 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 ) ;
* } ) ;
*
* @ param { Object / Array } GeoJSON point or legacy coordinate pair [ x , y ] to search near
* @ param { Object } options for the qurery
* @ param { Function } [ callback ] optional callback for the query
* @ return { Promise }
* @ see http : //docs.mongodb.org/manual/core/2dsphere/
* @ see http : //mongodb.github.io/node-mongodb-native/api-generated/collection.html?highlight=geonear#geoNear
* @ api public
* /
Model . geoNear = function ( near , options , callback ) {
if ( 'function' == typeof options ) {
callback = options ;
options = { } ;
}
var self = this ;
var Promise = PromiseProvider . get ( ) ;
if ( ! near ) {
return new Promise . ES6 ( function ( resolve , reject ) {
var error = new Error ( 'Must pass a near option to geoNear' ) ;
reject ( error ) ;
callback && callback ( error ) ;
return ;
} ) ;
}
var x , y ;
var _this = this ;
return new Promise . ES6 ( function ( resolve , reject ) {
var handler = function ( err , res ) {
if ( err ) {
reject ( err ) ;
callback && callback ( err ) ;
return ;
}
if ( options . lean ) {
resolve ( res . results , res . stats ) ;
callback && callback ( null , res . results , res . stats ) ;
return ;
}
var count = res . results . length ;
// if there are no results, fulfill the promise now
if ( count == 0 ) {
resolve ( res . results , res . stats ) ;
callback && callback ( null , res . results , res . stats ) ;
return ;
}
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 ;
reject ( err ) ;
callback && callback ( err ) ;
return ;
}
if ( -- count <= 0 ) {
resolve ( res . results , res . stats ) ;
callback && callback ( null , res . results , res . stats ) ;
}
} ) ;
}
} ;
if ( Array . isArray ( near ) ) {
if ( near . length !== 2 ) {
var error = new Error ( 'If using legacy coordinates, must be an array ' +
'of size 2 for geoNear' ) ;
reject ( error ) ;
callback && callback ( error ) ;
return ;
}
x = near [ 0 ] ;
y = near [ 1 ] ;
_this . collection . geoNear ( x , y , options , handler ) ;
} else {
if ( near . type != 'Point' || ! Array . isArray ( near . coordinates ) ) {
error = new Error ( 'Must pass either a legacy coordinate array or ' +
'GeoJSON Point to geoNear' ) ;
reject ( error ) ;
callback && callback ( error ) ;
return ;
}
_this . collection . geoNear ( near , options , handler ) ;
}
} ) ;
} ;
2014-09-14 07:04:16 -04:00
/ * *
2015-11-24 22:08:58 -08:00
* Performs [ aggregations ] ( http : //docs.mongodb.org/manual/applications/aggregation/) on the models collection.
*
* 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 .
2014-09-14 07:04:16 -04:00
*
* # # # # Example :
*
2015-11-24 22:08:58 -08:00
* // Find the max balance of all accounts
2014-09-14 07:04:16 -04:00
* Users . aggregate (
2015-11-24 22:08:58 -08:00
* { $group : { _id : null , maxBalance : { $max : '$balance' } } }
* , { $project : { _id : 0 , maxBalance : 1 } }
2014-09-14 07:04:16 -04:00
* , function ( err , res ) {
* if ( err ) return handleError ( err ) ;
2015-11-24 22:08:58 -08:00
* 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 } ]
2014-09-14 07:04:16 -04:00
* } ) ;
*
* # # # # NOTE :
*
2015-11-24 22:08:58 -08:00
* - Arguments are not cast to the model ' s schema because ` $ project ` operators allow redefining the "shape" of the documents at any stage of the pipeline , which may leave documents in an incompatible format .
* - The documents returned are plain javascript objects , not mongoose documents ( since any shape of document can be returned ) .
2014-09-14 07:04:16 -04:00
* - Requires MongoDB >= 2.1
*
2015-11-24 22:08:58 -08:00
* @ see Aggregate # aggregate _Aggregate
* @ see MongoDB http : //docs.mongodb.org/manual/applications/aggregation/
* @ param { Object | Array } [ ... ] aggregation pipeline operator ( s ) or operator array
* @ param { Function } [ callback ]
* @ return { Aggregate | Promise }
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . 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 . model ( this ) ;
if ( 'undefined' === typeof callback ) {
return aggregate ;
}
return aggregate . exec ( callback ) ;
} ;
/ * *
* Implements ` $ geoSearch ` functionality for Mongoose
*
* # # # # Example :
*
* var options = { near : [ 10 , 10 ] , maxDistance : 5 } ;
* Locations . geoSearch ( { type : "house" } , options , function ( err , res ) {
* console . log ( res ) ;
* } ) ;
*
* # # # # Options :
* - ` near ` { Array } x , y point to search for
* - ` maxDistance ` { Number } the maximum distance from the point near that a result can be
* - ` limit ` { Number } The maximum number of results to return
* - ` lean ` { Boolean } return the raw object instead of the Mongoose Model
*
* @ param { Object } condition an object that specifies the match condition ( required )
* @ param { Object } options for the geoSearch , some ( near , maxDistance ) are required
* @ param { Function } [ callback ] optional callback
* @ return { Promise }
* @ see http : //docs.mongodb.org/manual/reference/command/geoSearch/
* @ see http : //docs.mongodb.org/manual/core/geohaystack/
* @ api public
* /
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 ;
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Populates document references .
*
* # # # # Available options :
*
* - path : space delimited path ( s ) to populate
* - select : optional fields to select
* - match : optional query conditions to match
* - model : optional name of the model to use for population
* - options : optional query options like sort , limit , etc
*
* # # # # Examples :
*
* // 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' } ]
*
2015-11-24 22:08:58 -08:00
* var promise = User . populate ( users , opts ) ;
* promise . then ( console . log ) . end ( ) ;
2014-09-14 07:04:16 -04:00
* } )
*
* // 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.
*
* @ param { Document | Array } docs Either a single document or array of documents to populate .
* @ param { Object } options A hash of key / val ( path , options ) used for population .
2015-11-24 22:08:58 -08:00
* @ param { Function } [ cb ( err , doc ) ] Optional callback , executed upon completion . Receives ` err ` and the ` doc(s) ` .
* @ return { Promise }
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Model . populate = function ( docs , paths , cb ) {
var promise = new Promise ( cb ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
// always resolve on nextTick for consistent async behavior
function resolve ( ) {
2014-09-14 07:04:16 -04:00
var args = utils . args ( arguments ) ;
2015-11-24 22:08:58 -08:00
process . nextTick ( function ( ) {
promise . resolve . apply ( promise , args ) ;
2014-09-14 07:04:16 -04:00
} ) ;
}
// normalized paths
2015-11-24 22:08:58 -08:00
paths = utils . populate ( paths ) ;
2014-09-14 07:04:16 -04:00
var pending = paths . length ;
if ( 0 === pending ) {
2015-11-24 22:08:58 -08:00
resolve ( null , docs ) ;
return promise ;
2014-09-14 07:04:16 -04:00
}
// each path has its own query options and must be executed separately
var i = pending ;
var path ;
2015-11-24 22:08:58 -08:00
var model = this ;
2014-09-14 07:04:16 -04:00
while ( i -- ) {
path = paths [ i ] ;
2015-11-24 22:08:58 -08:00
if ( 'function' === typeof path . model ) model = path . model ;
populate ( model , docs , path , subPopulate . call ( model , docs , path , next ) ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
return promise ;
function next ( err ) {
if ( err ) return resolve ( err ) ;
2014-09-14 07:04:16 -04:00
if ( -- pending ) return ;
2015-11-24 22:08:58 -08:00
resolve ( null , docs ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * !
2015-11-24 22:08:58 -08:00
* Populates deeply if ` populate ` option is present .
*
* @ param { Document | Array } docs
* @ param { Object } options
* @ param { Function } cb
* @ return { Function }
* @ api private
2014-09-14 07:04:16 -04:00
* /
2015-11-24 22:08:58 -08:00
function subPopulate ( docs , options , cb ) {
var model = this ;
var prefix = options . path + '.' ;
var pop = options . populate ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
if ( ! pop ) {
return cb ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
// normalize as array
if ( ! Array . isArray ( pop ) ) {
pop = [ pop ] ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
return function ( err ) {
var pending = pop . length ;
function next ( err ) {
if ( err ) return cb ( err ) ;
if ( -- pending ) return ;
cb ( ) ;
}
if ( err || ! pending ) return cb ( err ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
pop . forEach ( function ( subOptions ) {
// path needs parent's path prefixed to it
if ( ! subOptions . _originalPath ) {
subOptions . _originalPath = subOptions . path ;
subOptions . path = prefix + subOptions . path ;
}
var schema = model . schema . _getSchema ( prefix ) ;
if ( typeof subOptions . model === 'string' ) {
subOptions . model = model . model ( subOptions . model ) ;
} else if ( schema && schema . caster && schema . caster . options &&
schema . caster . options . ref ) {
var subSchema = model . model ( schema . caster . options . ref ) . schema .
_getSchema ( subOptions . _originalPath ) ;
if ( subSchema && subSchema . options && subSchema . options . ref ) {
subOptions . model = model . model ( subSchema . options . ref ) ;
}
}
Model . populate . call ( subOptions . model || model , docs , subOptions , next ) ;
} ) ;
} ;
}
/ * !
* Populates ` docs `
* /
var excludeIdReg = /\s?-_id\s?/ ,
excludeIdRegGlobal = /\s?-_id\s?/g ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
function populate ( model , docs , options , cb ) {
var modelsMap , rawIds ;
2014-09-14 07:04:16 -04:00
// normalize single / multiple docs passed
if ( ! Array . isArray ( docs ) ) {
docs = [ docs ] ;
}
if ( 0 === docs . length || docs . every ( utils . isNullOrUndefined ) ) {
return cb ( ) ;
}
2015-11-24 22:08:58 -08:00
modelsMap = getModelsMapForPopulate ( model , docs , options ) ;
rawIds = getIdsForAndAddIdsInMapPopulate ( modelsMap ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var i , len = modelsMap . length ,
mod , match , select , promise , vals = [ ] ;
promise = new Promise ( function ( err , vals , options , assignmentOpts ) {
if ( err ) return cb ( err ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var lean = options . options && options . options . lean ,
len = vals . length ,
rawOrder = { } , rawDocs = { } , key , val ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
// optimization:
// record the document positions as returned by
// the query result.
for ( var i = 0 ; i < len ; i ++ ) {
val = vals [ i ] ;
key = String ( utils . getValue ( '_id' , val ) ) ;
rawDocs [ key ] = val ;
rawOrder [ key ] = i ;
// flag each as result of population
if ( ! lean ) val . $ _ _ . wasPopulated = true ;
}
assignVals ( {
rawIds : rawIds ,
rawDocs : rawDocs ,
rawOrder : rawOrder ,
docs : docs ,
path : options . path ,
options : assignmentOpts
} ) ;
cb ( ) ;
} ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var _remaining = len ;
2014-09-14 07:04:16 -04:00
for ( i = 0 ; i < len ; i ++ ) {
2015-11-24 22:08:58 -08:00
mod = modelsMap [ i ] ;
select = mod . options . select ;
if ( mod . options . match ) {
match = utils . object . shallowCopy ( mod . options . match ) ;
} else {
match = { } ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
var ids = utils . array . flatten ( mod . ids , function ( item ) {
// no need to include undefined values in our query
return undefined !== item ;
} ) ;
ids = utils . array . unique ( ids ) ;
if ( 0 === ids . length || ids . every ( utils . isNullOrUndefined ) ) {
return cb ( ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
match . _id || ( match . _id = {
$in : ids
} ) ;
var assignmentOpts = { } ;
assignmentOpts . sort = mod . options . options && mod . options . options . sort || undefined ;
assignmentOpts . excludeId = excludeIdReg . test ( select ) || ( select && 0 === select . _id ) ;
if ( assignmentOpts . excludeId ) {
// override the exclusion from the query so we can use the _id
// for document matching during assignment. we'll delete the
// _id back off before returning the result.
if ( 'string' == typeof select ) {
select = select . replace ( excludeIdRegGlobal , ' ' ) ;
} else {
// preserve original select conditions by copying
select = utils . object . shallowCopy ( select ) ;
delete select . _id ;
}
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
if ( mod . options . options && mod . options . options . limit ) {
assignmentOpts . originalLimit = mod . options . options . limit ;
mod . options . options . limit = mod . options . options . limit * ids . length ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
mod . Model . find ( match , select , mod . options . options , next . bind ( this , mod . options , assignmentOpts ) ) ;
}
function next ( options , assignmentOpts , err , valsFromDb ) {
if ( err ) return promise . resolve ( err ) ;
vals = vals . concat ( valsFromDb ) ;
if ( -- _remaining === 0 ) {
promise . resolve ( err , vals , options , assignmentOpts ) ;
2014-09-14 07:04:16 -04:00
}
}
2015-11-24 22:08:58 -08:00
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
function getModelsMapForPopulate ( model , docs , options ) {
var i , doc , len = docs . length ,
available = { } ,
map = [ ] ,
modelNameFromQuery = options . model && options . model . modelName || options . model ,
schema , refPath , Model , currentOptions , modelNames , modelName , discriminatorKey , modelForFindSchema ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
schema = model . _getSchema ( options . path ) ;
if ( schema && schema . caster ) {
schema = schema . caster ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
if ( ! schema && model . discriminators ) {
discriminatorKey = model . schema . discriminatorMapping . key ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
refPath = schema && schema . options && schema . options . refPath ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
for ( i = 0 ; i < len ; i ++ ) {
doc = docs [ i ] ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
if ( refPath ) {
modelNames = utils . getValue ( refPath , doc ) ;
2014-09-14 07:04:16 -04:00
} else {
2015-11-24 22:08:58 -08:00
if ( ! modelNameFromQuery ) {
var schemaForCurrentDoc ;
if ( ! schema && discriminatorKey ) {
modelForFindSchema = utils . getValue ( discriminatorKey , doc ) ;
if ( modelForFindSchema ) {
schemaForCurrentDoc = model . db . model ( modelForFindSchema ) . _getSchema ( options . path ) ;
if ( schemaForCurrentDoc && schemaForCurrentDoc . caster ) {
schemaForCurrentDoc = schemaForCurrentDoc . caster ;
}
}
} else {
schemaForCurrentDoc = schema ;
}
modelNames = [
schemaForCurrentDoc && schemaForCurrentDoc . options && schemaForCurrentDoc . options . ref // declared in schema
|| model . modelName // an ad-hoc structure
] ;
} else {
modelNames = [ modelNameFromQuery ] ; // query options
}
}
if ( ! modelNames )
continue ;
if ( ! Array . isArray ( modelNames ) ) {
modelNames = [ modelNames ] ;
}
var k = modelNames . length ;
while ( k -- ) {
modelName = modelNames [ k ] ;
if ( ! available [ modelName ] ) {
Model = model . db . model ( modelName ) ;
currentOptions = {
model : Model
} ;
if ( schema && ! discriminatorKey ) {
options . model = Model ;
}
utils . merge ( currentOptions , options ) ;
available [ modelName ] = {
Model : Model ,
options : currentOptions ,
docs : [ doc ] ,
ids : [ ]
} ;
map . push ( available [ modelName ] ) ;
} else {
available [ modelName ] . docs . push ( doc ) ;
}
2014-09-14 07:04:16 -04:00
}
}
2015-11-24 22:08:58 -08:00
return map ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
function getIdsForAndAddIdsInMapPopulate ( modelsMap ) {
var rawIds = [ ] , // for the correct position
i , j , doc , docs , id , len , len2 , ret , isDocument , options , path ;
len2 = modelsMap . length ;
for ( j = 0 ; j < len2 ; j ++ ) {
docs = modelsMap [ j ] . docs ;
len = docs . length ;
options = modelsMap [ j ] . options ;
path = options . path ;
for ( i = 0 ; i < len ; i ++ ) {
ret = undefined ;
doc = docs [ i ] ;
id = String ( utils . getValue ( "_id" , doc ) ) ;
isDocument = ! ! doc . $ _ _ ;
if ( ! ret || Array . isArray ( ret ) && 0 === ret . length ) {
ret = utils . getValue ( path , doc ) ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
if ( ret ) {
ret = convertTo _id ( ret ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
options . _docs [ id ] = Array . isArray ( ret ) ? ret . slice ( ) : ret ;
}
rawIds . push ( ret ) ;
modelsMap [ j ] . ids . push ( ret ) ;
if ( isDocument ) {
// cache original populated _ids and model used
doc . populated ( path , options . _docs [ id ] , options ) ;
}
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
return rawIds ;
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
/ * !
* Retrieve the _id of ` val ` if a Document or Array of Documents .
*
* @ param { Array | Document | Any } val
* @ return { Array | Document | Any }
* /
function convertTo _id ( val ) {
if ( val instanceof Model ) return val . _id ;
if ( Array . isArray ( val ) ) {
for ( var i = 0 ; i < val . length ; ++ i ) {
if ( val [ i ] instanceof Model ) {
val [ i ] = val [ i ] . _id ;
}
}
return val ;
}
return val ;
2014-09-14 07:04:16 -04:00
}
/ * !
* Assigns documents returned from a population query back
* to the original document path .
* /
2015-11-24 22:08:58 -08:00
function assignVals ( o ) {
2014-09-14 07:04:16 -04:00
// replace the original ids in our intermediate _ids structure
// with the documents found by query
assignRawDocsToIdStructure ( o . rawIds , o . rawDocs , o . rawOrder , o . options ) ;
// now update the original documents being populated using the
// result structure that contains real documents.
var docs = o . docs ;
var path = o . path ;
var rawIds = o . rawIds ;
var options = o . options ;
for ( var i = 0 ; i < docs . length ; ++ i ) {
2015-11-24 22:08:58 -08:00
if ( utils . getValue ( path , docs [ i ] ) == null )
continue ;
utils . setValue ( path , rawIds [ i ] , docs [ i ] , function ( val ) {
2014-09-14 07:04:16 -04:00
return valueFilter ( val , options ) ;
} ) ;
}
}
/ * !
* 1 ) Apply backwards compatible find / findOne behavior to sub documents
*
* find logic :
* a ) filter out non - documents
* b ) remove _id from sub docs when user specified
*
* findOne
* a ) if no doc found , set to null
* b ) remove _id from sub docs when user specified
*
* 2 ) Remove _ids when specified by users query .
*
* background :
* _ids are left in the query even when user excludes them so
* that population mapping can occur .
* /
2015-11-24 22:08:58 -08:00
function valueFilter ( val , assignmentOpts ) {
2014-09-14 07:04:16 -04:00
if ( Array . isArray ( val ) ) {
// find logic
var ret = [ ] ;
2015-11-24 22:08:58 -08:00
var numValues = val . length ;
for ( var i = 0 ; i < numValues ; ++ i ) {
2014-09-14 07:04:16 -04:00
var subdoc = val [ i ] ;
if ( ! isDoc ( subdoc ) ) continue ;
maybeRemoveId ( subdoc , assignmentOpts ) ;
ret . push ( subdoc ) ;
2015-11-24 22:08:58 -08:00
if ( assignmentOpts . originalLimit &&
ret . length >= assignmentOpts . originalLimit ) {
break ;
}
}
// Since we don't want to have to create a new mongoosearray, make sure to
// modify the array in place
while ( val . length > ret . length ) {
Array . prototype . pop . apply ( val , [ ] ) ;
}
for ( i = 0 ; i < ret . length ; ++ i ) {
val [ i ] = ret [ i ] ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
return val ;
2014-09-14 07:04:16 -04:00
}
// findOne
if ( isDoc ( val ) ) {
maybeRemoveId ( val , assignmentOpts ) ;
return val ;
}
return null ;
}
/ * !
* Remove _id from ` subdoc ` if user specified "lean" query option
* /
2015-11-24 22:08:58 -08:00
function maybeRemoveId ( subdoc , assignmentOpts ) {
2014-09-14 07:04:16 -04:00
if ( assignmentOpts . excludeId ) {
if ( 'function' == typeof subdoc . setValue ) {
2015-11-24 22:08:58 -08:00
delete subdoc . _doc . _id ;
2014-09-14 07:04:16 -04:00
} else {
delete subdoc . _id ;
}
}
}
/ * !
* Determine if ` doc ` is a document returned
* by a populate query .
* /
2015-11-24 22:08:58 -08:00
function isDoc ( doc ) {
2014-09-14 07:04:16 -04:00
if ( null == doc )
return false ;
var type = typeof doc ;
if ( 'string' == type )
return false ;
if ( 'number' == type )
return false ;
if ( Buffer . isBuffer ( doc ) )
return false ;
if ( 'ObjectID' == doc . constructor . name )
return false ;
// only docs
return true ;
}
/ * !
* Assign ` vals ` returned by mongo query to the ` rawIds `
* structure returned from utils . getVals ( ) honoring
* query sort order if specified by user .
*
* This can be optimized .
*
* Rules :
*
* if the value of the path is not an array , use findOne rules , else find .
* for findOne the results are assigned directly to doc path ( including null results ) .
* for find , if user specified sort order , results are assigned directly
* else documents are put back in original order of array if found in results
*
* @ param { Array } rawIds
* @ param { Array } vals
* @ param { Boolean } sort
* @ api private
* /
2015-11-24 22:08:58 -08:00
function assignRawDocsToIdStructure ( rawIds , resultDocs , resultOrder , options , recursed ) {
2014-09-14 07:04:16 -04:00
// honor user specified sort order
var newOrder = [ ] ;
var sorting = options . sort && rawIds . length > 1 ;
var doc ;
var sid ;
var id ;
for ( var i = 0 ; i < rawIds . length ; ++ i ) {
id = rawIds [ i ] ;
if ( Array . isArray ( id ) ) {
// handle [ [id0, id2], [id3] ]
assignRawDocsToIdStructure ( id , resultDocs , resultOrder , options , true ) ;
newOrder . push ( id ) ;
continue ;
}
if ( null === id && ! sorting ) {
// keep nulls for findOne unless sorting, which always
// removes them (backward compat)
newOrder . push ( id ) ;
continue ;
}
sid = String ( id ) ;
if ( recursed ) {
// apply find behavior
// assign matching documents in original order unless sorting
doc = resultDocs [ sid ] ;
if ( doc ) {
if ( sorting ) {
newOrder [ resultOrder [ sid ] ] = doc ;
} else {
newOrder . push ( doc ) ;
}
} else {
newOrder . push ( id ) ;
}
} else {
// apply findOne behavior - if document in results, assign, else assign null
newOrder [ i ] = doc = resultDocs [ sid ] || null ;
}
}
rawIds . length = 0 ;
if ( newOrder . length ) {
// reassign the documents based on corrected order
// forEach skips over sparse entries in arrays so we
// can safely use this to our advantage dealing with sorted
// result sets too.
2015-11-24 22:08:58 -08:00
newOrder . forEach ( function ( doc , i ) {
2014-09-14 07:04:16 -04:00
rawIds [ i ] = doc ;
} ) ;
}
}
/ * *
* Finds the schema for ` path ` . This is different than
* calling ` schema.path ` as it also resolves paths with
* positional selectors ( something . $ . another . $ . path ) .
*
* @ param { String } path
* @ return { Schema }
* @ api private
* /
2015-11-24 22:08:58 -08:00
Model . _getSchema = function _getSchema ( path ) {
return this . schema . _getSchema ( path ) ;
} ;
2014-09-14 07:04:16 -04:00
/ * !
* Compiler utility .
*
* @ param { String } name model name
* @ param { Schema } schema
* @ param { String } collectionName
* @ param { Connection } connection
* @ param { Mongoose } base mongoose instance
* /
2015-11-24 22:08:58 -08:00
Model . compile = function compile ( name , schema , collectionName , connection , base ) {
2014-09-14 07:04:16 -04:00
var versioningEnabled = false !== schema . options . versionKey ;
if ( versioningEnabled && ! schema . paths [ schema . options . versionKey ] ) {
// add versioning to top level documents only
var o = { } ;
o [ schema . options . versionKey ] = Number ;
schema . add ( o ) ;
}
// generate new class
2015-11-24 22:08:58 -08:00
function model ( doc , fields , skipId ) {
2014-09-14 07:04:16 -04:00
if ( ! ( this instanceof model ) )
return new model ( doc , fields , skipId ) ;
Model . call ( this , doc , fields , skipId ) ;
2015-11-24 22:08:58 -08:00
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
model . hooks = schema . s . hooks . clone ( ) ;
2014-09-14 07:04:16 -04:00
model . base = base ;
model . modelName = name ;
model . _ _proto _ _ = Model ;
model . prototype . _ _proto _ _ = Model . prototype ;
model . model = Model . prototype . model ;
model . db = model . prototype . db = connection ;
2015-11-24 22:08:58 -08:00
model . discriminators = model . prototype . discriminators = undefined ;
2014-09-14 07:04:16 -04:00
model . prototype . $ _ _setSchema ( schema ) ;
var collectionOptions = {
2015-11-24 22:08:58 -08:00
bufferCommands : schema . options . bufferCommands ,
capped : schema . options . capped
2014-09-14 07:04:16 -04:00
} ;
model . prototype . collection = connection . collection (
collectionName
, collectionOptions
) ;
2015-11-24 22:08:58 -08:00
// apply methods and statics
applyMethods ( model , schema ) ;
applyStatics ( model , schema ) ;
2014-09-14 07:04:16 -04:00
model . schema = model . prototype . schema ;
model . collection = model . prototype . collection ;
return model ;
} ;
2015-11-24 22:08:58 -08:00
/ * !
* Register methods for this model
*
* @ param { Model } model
* @ param { Schema } schema
* /
var applyMethods = function ( model , schema ) {
for ( var i in schema . methods ) {
if ( typeof schema . methods [ i ] === 'function' ) {
model . prototype [ i ] = schema . methods [ i ] ;
} else {
( function ( _i ) {
Object . defineProperty ( model . prototype , _i , {
get : function ( ) {
var h = { } ;
for ( var k in schema . methods [ _i ] ) {
h [ k ] = schema . methods [ _i ] [ k ] . bind ( this ) ;
}
return h ;
} ,
configurable : true
} ) ;
} ) ( i ) ;
}
}
} ;
/ * !
* Register statics for this model
* @ param { Model } model
* @ param { Schema } schema
* /
var applyStatics = function ( model , schema ) {
for ( var i in schema . statics ) {
model [ i ] = schema . statics [ i ] ;
}
} ;
2014-09-14 07:04:16 -04:00
/ * !
* Subclass this model with ` conn ` , ` schema ` , and ` collection ` settings .
*
* @ param { Connection } conn
* @ param { Schema } [ schema ]
* @ param { String } [ collection ]
* @ return { Model }
* /
2015-11-24 22:08:58 -08:00
Model . _ _subclass = function subclass ( conn , schema , collection ) {
2014-09-14 07:04:16 -04:00
// subclass model using this connection and collection name
var model = this ;
2015-11-24 22:08:58 -08:00
var Model = function Model ( doc , fields , skipId ) {
2014-09-14 07:04:16 -04:00
if ( ! ( this instanceof Model ) ) {
return new Model ( doc , fields , skipId ) ;
}
model . call ( this , doc , fields , skipId ) ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
Model . _ _proto _ _ = model ;
Model . prototype . _ _proto _ _ = model . prototype ;
Model . db = Model . prototype . db = conn ;
var s = schema && 'string' != typeof schema
? schema
: model . prototype . schema ;
2015-11-24 22:08:58 -08:00
var options = s . options || { } ;
2014-09-14 07:04:16 -04:00
if ( ! collection ) {
collection = model . prototype . schema . get ( 'collection' )
2015-11-24 22:08:58 -08:00
|| utils . toCollectionName ( model . modelName , options ) ;
2014-09-14 07:04:16 -04:00
}
var collectionOptions = {
2015-11-24 22:08:58 -08:00
bufferCommands : s ? options . bufferCommands : true ,
capped : s && options . capped
2014-09-14 07:04:16 -04:00
} ;
Model . prototype . collection = conn . collection ( collection , collectionOptions ) ;
Model . collection = Model . prototype . collection ;
Model . init ( ) ;
return Model ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * !
* Module exports .
* /
module . exports = exports = Model ;