2014-09-14 07:04:16 -04:00
/ * !
* Module dependencies .
* /
2015-11-24 22:08:58 -08:00
var readPref = require ( './drivers' ) . ReadPreference ;
var EventEmitter = require ( 'events' ) . EventEmitter ;
var VirtualType = require ( './virtualtype' ) ;
var utils = require ( './utils' ) ;
var MongooseTypes ;
var Kareem = require ( 'kareem' ) ;
var async = require ( 'async' ) ;
var PromiseProvider = require ( './promise_provider' ) ;
var IS _QUERY _HOOK = {
count : true ,
find : true ,
findOne : true ,
findOneAndUpdate : true ,
findOneAndRemove : true ,
update : true
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Schema constructor .
*
* # # # # Example :
*
* var child = new Schema ( { name : String } ) ;
* var schema = new Schema ( { name : String , age : Number , children : [ child ] } ) ;
* var Tree = mongoose . model ( 'Tree' , schema ) ;
*
* // setting schema options
* new Schema ( { name : String } , { _id : false , autoIndex : false } )
*
* # # # # Options :
*
2015-11-24 22:08:58 -08:00
* - [ autoIndex ] ( / d o c s / g u i d e . h t m l # a u t o I n d e x ) : b o o l - d e f a u l t s t o n u l l ( w h i c h m e a n s u s e t h e c o n n e c t i o n ' s a u t o I n d e x o p t i o n )
2014-09-14 07:04:16 -04:00
* - [ bufferCommands ] ( / d o c s / g u i d e . h t m l # b u f f e r C o m m a n d s ) : b o o l - d e f a u l t s t o t r u e
* - [ capped ] ( / d o c s / g u i d e . h t m l # c a p p e d ) : b o o l - d e f a u l t s t o f a l s e
* - [ collection ] ( / d o c s / g u i d e . h t m l # c o l l e c t i o n ) : s t r i n g - n o d e f a u l t
2015-11-24 22:08:58 -08:00
* - [ emitIndexErrors ] ( / d o c s / g u i d e . h t m l # e m i t I n d e x E r r o r s ) : b o o l - d e f a u l t s t o f a l s e .
2014-09-14 07:04:16 -04:00
* - [ id ] ( / d o c s / g u i d e . h t m l # i d ) : b o o l - d e f a u l t s t o t r u e
* - [ _id ] ( / d o c s / g u i d e . h t m l # _ i d ) : b o o l - d e f a u l t s t o t r u e
* - ` minimize ` : bool - controls [ document # toObject ] ( # document _Document - toObject ) behavior when called manually - defaults to true
* - [ read ] ( / d o c s / g u i d e . h t m l # r e a d ) : s t r i n g
* - [ safe ] ( / d o c s / g u i d e . h t m l # s a f e ) : b o o l - d e f a u l t s t o t r u e .
* - [ shardKey ] ( / d o c s / g u i d e . h t m l # s h a r d K e y ) : b o o l - d e f a u l t s t o ` n u l l `
* - [ strict ] ( / d o c s / g u i d e . h t m l # s t r i c t ) : b o o l - d e f a u l t s t o t r u e
* - [ toJSON ] ( / d o c s / g u i d e . h t m l # t o J S O N ) - o b j e c t - n o d e f a u l t
* - [ toObject ] ( / d o c s / g u i d e . h t m l # t o O b j e c t ) - o b j e c t - n o d e f a u l t
2015-11-24 22:08:58 -08:00
* - [ typeKey ] ( / d o c s / g u i d e . h t m l # t y p e K e y ) - s t r i n g - d e f a u l t s t o ' t y p e '
* - [ validateBeforeSave ] ( / d o c s / g u i d e . h t m l # v a l i d a t e B e f o r e S a v e ) - b o o l - d e f a u l t s t o ` t r u e `
2014-09-14 07:04:16 -04:00
* - [ versionKey ] ( / d o c s / g u i d e . h t m l # v e r s i o n K e y ) : b o o l - d e f a u l t s t o " _ _ v "
*
* # # # # Note :
*
2015-11-24 22:08:58 -08:00
* _When nesting schemas , ( ` children ` in the example above ) , always declare the child schema first before passing it into its parent . _
2014-09-14 07:04:16 -04:00
*
* @ param { Object } definition
* @ inherits NodeJS EventEmitter http : //nodejs.org/api/events.html#events_class_events_eventemitter
* @ event ` init ` : Emitted after the schema is compiled into a ` Model ` .
* @ api public
* /
2015-11-24 22:08:58 -08:00
function Schema ( obj , options ) {
2014-09-14 07:04:16 -04:00
if ( ! ( this instanceof Schema ) )
return new Schema ( obj , options ) ;
this . paths = { } ;
this . subpaths = { } ;
this . virtuals = { } ;
this . nested = { } ;
this . inherits = { } ;
this . callQueue = [ ] ;
this . _indexes = [ ] ;
this . methods = { } ;
this . statics = { } ;
this . tree = { } ;
this . _requiredpaths = undefined ;
2015-11-24 22:08:58 -08:00
this . discriminatorMapping = undefined ;
this . _indexedpaths = undefined ;
this . s = {
hooks : new Kareem ( ) ,
queryHooks : IS _QUERY _HOOK
} ;
2014-09-14 07:04:16 -04:00
this . options = this . defaultOptions ( options ) ;
// build paths
if ( obj ) {
this . add ( obj ) ;
}
2015-11-24 22:08:58 -08:00
// check if _id's value is a subdocument (gh-2276)
var _idSubDoc = obj && obj . _id && utils . isObject ( obj . _id ) ;
2014-09-14 07:04:16 -04:00
// ensure the documents get an auto _id unless disabled
2015-11-24 22:08:58 -08:00
var auto _id = ! this . paths [ '_id' ] &&
( ! this . options . noId && this . options . _id ) && ! _idSubDoc ;
2014-09-14 07:04:16 -04:00
if ( auto _id ) {
2015-11-24 22:08:58 -08:00
obj = { _id : { auto : true } } ;
obj . _id [ this . options . typeKey ] = Schema . ObjectId ;
this . add ( obj ) ;
2014-09-14 07:04:16 -04:00
}
// ensure the documents receive an id getter unless disabled
2015-11-24 22:08:58 -08:00
var autoid = ! this . paths [ 'id' ] &&
( ! this . options . noVirtualId && this . options . id ) ;
2014-09-14 07:04:16 -04:00
if ( autoid ) {
this . virtual ( 'id' ) . get ( idGetter ) ;
}
2015-11-24 22:08:58 -08:00
for ( var i = 0 ; i < this . _defaultMiddleware . length ; ++ i ) {
var m = this . _defaultMiddleware [ i ] ;
this [ m . kind ] ( m . hook , ! ! m . isAsync , m . fn ) ;
}
// adds updatedAt and createdAt timestamps to documents if enabled
var timestamps = this . options . timestamps ;
if ( timestamps ) {
var createdAt = timestamps . createdAt || 'createdAt' ,
updatedAt = timestamps . updatedAt || 'updatedAt' ,
schemaAdditions = { } ;
schemaAdditions [ updatedAt ] = Date ;
if ( ! this . paths [ createdAt ] ) {
schemaAdditions [ createdAt ] = Date ;
}
this . add ( schemaAdditions ) ;
this . pre ( 'save' , function ( next ) {
var defaultTimestamp = new Date ( ) ;
if ( ! this [ createdAt ] ) {
this [ createdAt ] = auto _id ? this . _id . getTimestamp ( ) : defaultTimestamp ;
}
this [ updatedAt ] = this . isNew ? this [ createdAt ] : defaultTimestamp ;
next ( ) ;
} ) ;
var genUpdates = function ( ) {
var now = new Date ( ) ;
var updates = { $set : { } , $setOnInsert : { } } ;
updates . $set [ updatedAt ] = now ;
updates . $setOnInsert [ createdAt ] = now ;
return updates ;
} ;
this . pre ( 'findOneAndUpdate' , function ( next ) {
this . findOneAndUpdate ( { } , genUpdates ( ) ) ;
next ( ) ;
} ) ;
this . pre ( 'update' , function ( next ) {
this . update ( { } , genUpdates ( ) ) ;
next ( ) ;
} ) ;
}
2014-09-14 07:04:16 -04:00
}
/ * !
* Returns this documents _id cast to a string .
* /
2015-11-24 22:08:58 -08:00
function idGetter ( ) {
2014-09-14 07:04:16 -04:00
if ( this . $ _ _ . _id ) {
return this . $ _ _ . _id ;
}
return this . $ _ _ . _id = null == this . _id
? null
: String ( this . _id ) ;
}
/ * !
* Inherit from EventEmitter .
* /
2015-11-24 22:08:58 -08:00
Schema . prototype = Object . create ( EventEmitter . prototype ) ;
Schema . prototype . constructor = Schema ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
/ * *
* Default middleware attached to a schema . Cannot be changed .
*
* This field is used to make sure discriminators don ' t get multiple copies of
* built - in middleware . Declared as a constant because changing this at runtime
* may lead to instability with Model . prototype . discriminator ( ) .
*
* @ api private
* @ property _defaultMiddleware
* /
Object . defineProperty ( Schema . prototype , '_defaultMiddleware' , {
configurable : false ,
enumerable : false ,
writable : false ,
value : [ {
kind : 'pre' ,
hook : 'save' ,
fn : function ( next , options ) {
// Nested docs have their own presave
if ( this . ownerDocument ) {
return next ( ) ;
}
var hasValidateBeforeSaveOption = options &&
( typeof options === 'object' ) &&
( 'validateBeforeSave' in options ) ;
var shouldValidate ;
if ( hasValidateBeforeSaveOption ) {
shouldValidate = ! ! options . validateBeforeSave ;
} else {
shouldValidate = this . schema . options . validateBeforeSave ;
}
// Validate
if ( shouldValidate ) {
// HACK: use $__original_validate to avoid promises so bluebird doesn't
// complain
if ( this . $ _ _original _validate ) {
this . $ _ _original _validate ( { _ _noPromise : true } , function ( error ) {
next ( error ) ;
} ) ;
} else {
this . validate ( { _ _noPromise : true } , function ( error ) {
next ( error ) ;
} ) ;
}
} else {
next ( ) ;
}
}
} , {
kind : 'pre' ,
hook : 'save' ,
isAsync : true ,
fn : function ( next , done ) {
var Promise = PromiseProvider . get ( ) ,
subdocs = this . $ _ _getAllSubdocs ( ) ;
if ( ! subdocs . length || this . $ _ _preSavingFromParent ) {
done ( ) ;
next ( ) ;
return ;
}
new Promise . ES6 ( function ( resolve , reject ) {
async . each ( subdocs , function ( subdoc , cb ) {
subdoc . $ _ _preSavingFromParent = true ;
subdoc . save ( function ( err ) {
cb ( err ) ;
} ) ;
} , function ( error ) {
for ( var i = 0 ; i < subdocs . length ; ++ i ) {
delete subdocs [ i ] . $ _ _preSavingFromParent ;
}
if ( error ) {
reject ( error ) ;
return ;
}
resolve ( ) ;
} ) ;
} ) . then ( function ( ) {
next ( ) ;
done ( ) ;
} , done ) ;
}
} ]
} ) ;
2014-09-14 07:04:16 -04:00
/ * *
* Schema as flat paths
*
* # # # # Example :
* {
* '_id' : SchemaType ,
* , 'nested.key' : SchemaType ,
* }
*
* @ api private
* @ property paths
* /
Schema . prototype . paths ;
/ * *
* Schema as a tree
*
* # # # # Example :
* {
* '_id' : ObjectId
* , 'nested' : {
* 'key' : String
* }
* }
*
* @ api private
* @ property tree
* /
Schema . prototype . tree ;
/ * *
* Returns default options for this schema , merged with ` options ` .
*
* @ param { Object } options
* @ return { Object }
* @ api private
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . defaultOptions = function ( options ) {
2014-09-14 07:04:16 -04:00
if ( options && false === options . safe ) {
options . safe = { w : 0 } ;
}
2015-11-24 22:08:58 -08:00
if ( options && options . safe && 0 === options . safe . w ) {
// if you turn off safe writes, then versioning goes off as well
options . versionKey = false ;
}
2014-09-14 07:04:16 -04:00
options = utils . options ( {
2015-11-24 22:08:58 -08:00
strict : true ,
bufferCommands : true ,
capped : false , // { size, max, autoIndexId }
versionKey : '__v' ,
discriminatorKey : '__t' ,
minimize : true ,
autoIndex : null ,
shardKey : null ,
read : null ,
validateBeforeSave : true ,
2014-09-14 07:04:16 -04:00
// the following are only applied at construction time
2015-11-24 22:08:58 -08:00
noId : false , // deprecated, use { _id: false }
_id : true ,
noVirtualId : false , // deprecated, use { id: false }
id : true ,
typeKey : 'type'
2014-09-14 07:04:16 -04:00
} , options ) ;
2015-11-24 22:08:58 -08:00
if ( options . read ) {
options . read = readPref ( options . read ) ;
}
2014-09-14 07:04:16 -04:00
return options ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Adds key path / schema type pairs to this schema .
*
* # # # # Example :
*
* var ToySchema = new Schema ;
* ToySchema . add ( { name : 'string' , color : 'string' , price : 'number' } ) ;
*
* @ param { Object } obj
* @ param { String } prefix
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . add = function add ( obj , prefix ) {
2014-09-14 07:04:16 -04:00
prefix = prefix || '' ;
var keys = Object . keys ( obj ) ;
for ( var i = 0 ; i < keys . length ; ++ i ) {
var key = keys [ i ] ;
if ( null == obj [ key ] ) {
2015-11-24 22:08:58 -08:00
throw new TypeError ( 'Invalid value for schema path `' + prefix + key + '`' ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
if ( Array . isArray ( obj [ key ] ) && obj [ key ] . length === 1 && null == obj [ key ] [ 0 ] ) {
throw new TypeError ( 'Invalid value for schema Array path `' + prefix + key + '`' ) ;
}
if ( utils . isObject ( obj [ key ] ) &&
( ! obj [ key ] . constructor || 'Object' == utils . getFunctionName ( obj [ key ] . constructor ) ) &&
( ! obj [ key ] [ this . options . typeKey ] || ( this . options . typeKey === 'type' && obj [ key ] . type . type ) ) ) {
2014-09-14 07:04:16 -04:00
if ( Object . keys ( obj [ key ] ) . length ) {
// nested object { last: { name: String }}
this . nested [ prefix + key ] = true ;
this . add ( obj [ key ] , prefix + key + '.' ) ;
} else {
this . path ( prefix + key , obj [ key ] ) ; // mixed type
}
} else {
this . path ( prefix + key , obj [ key ] ) ;
}
}
} ;
/ * *
* Reserved document keys .
*
* Keys in this object are names that are rejected in schema declarations b / c they conflict with mongoose functionality . Using these key name will throw an error .
*
2015-11-24 22:08:58 -08:00
* on , emit , _events , db , get , set , init , isNew , errors , schema , options , modelName , collection , _pres , _posts , toObject
2014-09-14 07:04:16 -04:00
*
* _NOTE : _ Use of these terms as method names is permitted , but play at your own risk , as they may be existing mongoose document methods you are stomping on .
*
* var schema = new Schema ( . . ) ;
* schema . methods . init = function ( ) { } // potentially breaking
* /
Schema . reserved = Object . create ( null ) ;
var reserved = Schema . reserved ;
2015-11-24 22:08:58 -08:00
// EventEmitter
reserved . emit =
2014-09-14 07:04:16 -04:00
reserved . on =
2015-11-24 22:08:58 -08:00
reserved . once =
// document properties and functions
reserved . collection =
2014-09-14 07:04:16 -04:00
reserved . db =
2015-11-24 22:08:58 -08:00
reserved . errors =
2014-09-14 07:04:16 -04:00
reserved . init =
2015-11-24 22:08:58 -08:00
reserved . isModified =
2014-09-14 07:04:16 -04:00
reserved . isNew =
2015-11-24 22:08:58 -08:00
reserved . get =
2014-09-14 07:04:16 -04:00
reserved . modelName =
2015-11-24 22:08:58 -08:00
reserved . save =
reserved . schema =
reserved . set =
2014-09-14 07:04:16 -04:00
reserved . toObject =
2015-11-24 22:08:58 -08:00
reserved . validate =
// hooks.js
reserved . _pres = reserved . _posts = 1 ;
/ * *
* Document keys to print warnings for
* /
var warnings = { } ;
warnings . increment = '`increment` should not be used as a schema path name ' +
'unless you have disabled versioning.' ;
2014-09-14 07:04:16 -04:00
/ * *
* Gets / sets schema paths .
*
* Sets a path ( if arity 2 )
* Gets a path ( if arity 1 )
*
* # # # # Example
*
* schema . path ( 'name' ) // returns a SchemaType
* schema . path ( 'name' , Number ) // changes the schemaType of `name` to Number
*
* @ param { String } path
* @ param { Object } constructor
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . path = function ( path , obj ) {
2014-09-14 07:04:16 -04:00
if ( obj == undefined ) {
if ( this . paths [ path ] ) return this . paths [ path ] ;
if ( this . subpaths [ path ] ) return this . subpaths [ path ] ;
// subpaths?
return /\.\d+\.?.*$/ . test ( path )
? getPositionalPath ( this , path )
: undefined ;
}
// some path names conflict with document methods
if ( reserved [ path ] ) {
throw new Error ( "`" + path + "` may not be used as a schema pathname" ) ;
}
2015-11-24 22:08:58 -08:00
if ( warnings [ path ] ) {
console . log ( 'WARN: ' + warnings [ path ] ) ;
}
2014-09-14 07:04:16 -04:00
// update the tree
2015-11-24 22:08:58 -08:00
var subpaths = path . split ( /\./ ) ,
last = subpaths . pop ( ) ,
branch = this . tree ;
2014-09-14 07:04:16 -04:00
subpaths . forEach ( function ( sub , i ) {
if ( ! branch [ sub ] ) branch [ sub ] = { } ;
if ( 'object' != typeof branch [ sub ] ) {
var msg = 'Cannot set nested path `' + path + '`. '
+ 'Parent path `'
+ subpaths . slice ( 0 , i ) . concat ( [ sub ] ) . join ( '.' )
+ '` already set to type ' + branch [ sub ] . name
+ '.' ;
throw new Error ( msg ) ;
}
branch = branch [ sub ] ;
} ) ;
branch [ last ] = utils . clone ( obj ) ;
2015-11-24 22:08:58 -08:00
this . paths [ path ] = Schema . interpretAsType ( path , obj , this . options ) ;
2014-09-14 07:04:16 -04:00
return this ;
} ;
/ * *
* Converts type arguments into Mongoose Types .
*
* @ param { String } path
* @ param { Object } obj constructor
* @ api private
* /
2015-11-24 22:08:58 -08:00
Schema . interpretAsType = function ( path , obj , options ) {
if ( obj . constructor ) {
var constructorName = utils . getFunctionName ( obj . constructor ) ;
if ( constructorName != 'Object' ) {
var oldObj = obj ;
obj = { } ;
obj [ options . typeKey ] = oldObj ;
}
}
2014-09-14 07:04:16 -04:00
// Get the type making sure to allow keys named "type"
// and default to mixed if not specified.
// { type: { type: String, default: 'freshcut' } }
2015-11-24 22:08:58 -08:00
var type = obj [ options . typeKey ] && ( options . typeKey !== 'type' || ! obj . type . type )
? obj [ options . typeKey ]
2014-09-14 07:04:16 -04:00
: { } ;
2015-11-24 22:08:58 -08:00
if ( 'Object' == utils . getFunctionName ( type . constructor ) || 'mixed' == type ) {
return new MongooseTypes . Mixed ( path , obj ) ;
2014-09-14 07:04:16 -04:00
}
if ( Array . isArray ( type ) || Array == type || 'array' == type ) {
// if it was specified through { type } look for `cast`
var cast = ( Array == type || 'array' == type )
? obj . cast
: type [ 0 ] ;
if ( cast instanceof Schema ) {
2015-11-24 22:08:58 -08:00
return new MongooseTypes . DocumentArray ( path , cast , obj ) ;
2014-09-14 07:04:16 -04:00
}
if ( 'string' == typeof cast ) {
2015-11-24 22:08:58 -08:00
cast = MongooseTypes [ cast . charAt ( 0 ) . toUpperCase ( ) + cast . substring ( 1 ) ] ;
} else if ( cast && ( ! cast [ options . typeKey ] || ( options . typeKey === 'type' && cast . type . type ) )
&& 'Object' == utils . getFunctionName ( cast . constructor )
2014-09-14 07:04:16 -04:00
&& Object . keys ( cast ) . length ) {
2015-11-24 22:08:58 -08:00
// The `minimize` and `typeKey` options propagate to child schemas
// declared inline, like `{ arr: [{ val: { $type: String } }] }`.
// See gh-3560
var childSchemaOptions = { minimize : options . minimize } ;
if ( options . typeKey ) {
childSchemaOptions . typeKey = options . typeKey ;
}
var childSchema = new Schema ( cast , childSchemaOptions ) ;
return new MongooseTypes . DocumentArray ( path , childSchema , obj ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
return new MongooseTypes . Array ( path , cast || MongooseTypes . Mixed , obj ) ;
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
if ( type instanceof Schema ) {
return new MongooseTypes . Embedded ( type , path , obj ) ;
}
var name ;
if ( Buffer . isBuffer ( type ) ) {
name = 'Buffer' ;
} else {
name = 'string' == typeof type
? type
// If not string, `type` is a function. Outside of IE, function.name
// gives you the function name. In IE, you need to compute it
: type . schemaName || utils . getFunctionName ( type ) ;
}
2014-09-14 07:04:16 -04:00
if ( name ) {
name = name . charAt ( 0 ) . toUpperCase ( ) + name . substring ( 1 ) ;
}
2015-11-24 22:08:58 -08:00
if ( undefined == MongooseTypes [ name ] ) {
throw new TypeError ( 'Undefined type `' + name + '` at `' + path +
2014-09-14 07:04:16 -04:00
'`\n Did you try nesting Schemas? ' +
'You can only nest using refs or arrays.' ) ;
}
2015-11-24 22:08:58 -08:00
return new MongooseTypes [ name ] ( path , obj ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Iterates the schemas paths similar to Array # forEach .
*
* The callback is passed the pathname and schemaType as arguments on each iteration .
*
* @ param { Function } fn callback function
* @ return { Schema } this
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . eachPath = function ( fn ) {
var keys = Object . keys ( this . paths ) ,
len = keys . length ;
2014-09-14 07:04:16 -04:00
for ( var i = 0 ; i < len ; ++ i ) {
fn ( keys [ i ] , this . paths [ keys [ i ] ] ) ;
}
return this ;
} ;
/ * *
* Returns an Array of path strings that are required by this schema .
*
* @ api public
2015-11-24 22:08:58 -08:00
* @ param { Boolean } invalidate refresh the cache
2014-09-14 07:04:16 -04:00
* @ return { Array }
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . requiredPaths = function requiredPaths ( invalidate ) {
if ( this . _requiredpaths && ! invalidate ) return this . _requiredpaths ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var paths = Object . keys ( this . paths ) ,
i = paths . length ,
ret = [ ] ;
2014-09-14 07:04:16 -04:00
while ( i -- ) {
var path = paths [ i ] ;
if ( this . paths [ path ] . isRequired ) ret . push ( path ) ;
}
return this . _requiredpaths = ret ;
2015-11-24 22:08:58 -08:00
} ;
/ * *
* Returns indexes from fields and schema - level indexes ( cached ) .
*
* @ api private
* @ return { Array }
* /
Schema . prototype . indexedPaths = function indexedPaths ( ) {
if ( this . _indexedpaths ) return this . _indexedpaths ;
return this . _indexedpaths = this . indexes ( ) ;
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Returns the pathType of ` path ` for this schema .
*
* Given a path , returns whether it is a real , virtual , nested , or ad - hoc / undefined path .
*
* @ param { String } path
* @ return { String }
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . pathType = function ( path ) {
2014-09-14 07:04:16 -04:00
if ( path in this . paths ) return 'real' ;
if ( path in this . virtuals ) return 'virtual' ;
if ( path in this . nested ) return 'nested' ;
if ( path in this . subpaths ) return 'real' ;
2015-11-24 22:08:58 -08:00
if ( /\.\d+\.|\.\d+$/ . test ( path ) ) {
return getPositionalPathType ( this , path ) ;
2014-09-14 07:04:16 -04:00
} else {
2015-11-24 22:08:58 -08:00
return 'adhocOrUndefined' ;
2014-09-14 07:04:16 -04:00
}
} ;
2015-11-24 22:08:58 -08:00
/ * *
* Returns true iff this path is a child of a mixed schema .
*
* @ param { String } path
* @ return { Boolean }
* @ api private
* /
Schema . prototype . hasMixedParent = function ( path ) {
var subpaths = path . split ( /\./g ) ;
path = '' ;
for ( var i = 0 ; i < subpaths . length ; ++ i ) {
path = i > 0 ? path + '.' + subpaths [ i ] : subpaths [ i ] ;
if ( path in this . paths &&
this . paths [ path ] instanceof MongooseTypes . Mixed ) {
return true ;
}
}
return false ;
} ;
2014-09-14 07:04:16 -04:00
/ * !
* ignore
* /
2015-11-24 22:08:58 -08:00
function getPositionalPathType ( self , path ) {
2014-09-14 07:04:16 -04:00
var subpaths = path . split ( /\.(\d+)\.|\.(\d+)$/ ) . filter ( Boolean ) ;
if ( subpaths . length < 2 ) {
return self . paths [ subpaths [ 0 ] ] ;
}
var val = self . path ( subpaths [ 0 ] ) ;
2015-11-24 22:08:58 -08:00
var isNested = false ;
2014-09-14 07:04:16 -04:00
if ( ! val ) return val ;
2015-11-24 22:08:58 -08:00
var last = subpaths . length - 1 ,
subpath ,
i = 1 ;
2014-09-14 07:04:16 -04:00
for ( ; i < subpaths . length ; ++ i ) {
2015-11-24 22:08:58 -08:00
isNested = false ;
2014-09-14 07:04:16 -04:00
subpath = subpaths [ i ] ;
if ( i === last && val && ! val . schema && ! /\D/ . test ( subpath ) ) {
2015-11-24 22:08:58 -08:00
if ( val instanceof MongooseTypes . Array ) {
2014-09-14 07:04:16 -04:00
// StringSchema, NumberSchema, etc
val = val . caster ;
} else {
val = undefined ;
}
break ;
}
// ignore if its just a position segment: path.0.subpath
if ( ! /\D/ . test ( subpath ) ) continue ;
if ( ! ( val && val . schema ) ) {
val = undefined ;
break ;
}
2015-11-24 22:08:58 -08:00
var type = val . schema . pathType ( subpath ) ;
isNested = ( type === 'nested' ) ;
2014-09-14 07:04:16 -04:00
val = val . schema . path ( subpath ) ;
}
2015-11-24 22:08:58 -08:00
self . subpaths [ path ] = val ;
if ( val ) {
return 'real' ;
}
if ( isNested ) {
return 'nested' ;
}
return 'adhocOrUndefined' ;
}
/ * !
* ignore
* /
function getPositionalPath ( self , path ) {
getPositionalPathType ( self , path ) ;
return self . subpaths [ path ] ;
2014-09-14 07:04:16 -04:00
}
/ * *
* Adds a method call to the queue .
*
* @ param { String } name name of the document method to call later
* @ param { Array } args arguments to pass to the method
2015-11-24 22:08:58 -08:00
* @ api public
2014-09-14 07:04:16 -04:00
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . queue = function ( name , args ) {
2014-09-14 07:04:16 -04:00
this . callQueue . push ( [ name , args ] ) ;
return this ;
} ;
/ * *
* Defines a pre hook for the document .
*
* # # # # Example
*
* var toySchema = new Schema ( . . ) ;
*
* toySchema . pre ( 'save' , function ( next ) {
* if ( ! this . created ) this . created = new Date ;
* next ( ) ;
* } )
*
* toySchema . pre ( 'validate' , function ( next ) {
* if ( this . name != 'Woody' ) this . name = 'Woody' ;
* next ( ) ;
* } )
*
* @ param { String } method
* @ param { Function } callback
* @ see hooks . js https : //github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . pre = function ( ) {
var name = arguments [ 0 ] ;
if ( IS _QUERY _HOOK [ name ] ) {
this . s . hooks . pre . apply ( this . s . hooks , arguments ) ;
return this ;
}
2014-09-14 07:04:16 -04:00
return this . queue ( 'pre' , arguments ) ;
} ;
/ * *
2015-11-24 22:08:58 -08:00
* Defines a post hook for the document
2014-09-14 07:04:16 -04:00
*
* Post hooks fire ` on ` the event emitted from document instances of Models compiled from this schema .
*
* var schema = new Schema ( . . ) ;
* schema . post ( 'save' , function ( doc ) {
* console . log ( 'this fired after a document was saved' ) ;
* } ) ;
*
* var Model = mongoose . model ( 'Model' , schema ) ;
*
* var m = new Model ( . . ) ;
* m . save ( function ( err ) {
* console . log ( 'this fires after the `post` hook' ) ;
* } ) ;
*
* @ param { String } method name of the method to hook
* @ param { Function } fn callback
* @ see hooks . js https : //github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . post = function ( method , fn ) {
if ( IS _QUERY _HOOK [ method ] ) {
this . s . hooks . post . apply ( this . s . hooks , arguments ) ;
return this ;
}
// assuming that all callbacks with arity < 2 are synchronous post hooks
if ( fn . length < 2 ) {
return this . queue ( 'on' , [ arguments [ 0 ] , function ( doc ) {
return fn . call ( doc , doc ) ;
} ] ) ;
}
return this . queue ( 'post' , [ arguments [ 0 ] , function ( next ) {
// wrap original function so that the callback goes last,
// for compatibility with old code that is using synchronous post hooks
var self = this ;
var args = Array . prototype . slice . call ( arguments , 1 ) ;
fn . call ( this , this , function ( err ) {
return next . apply ( self , [ err ] . concat ( args ) ) ;
} ) ;
} ] ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * *
* Registers a plugin for this schema .
*
* @ param { Function } plugin callback
2015-11-24 22:08:58 -08:00
* @ param { Object } [ opts ]
2014-09-14 07:04:16 -04:00
* @ see plugins
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . plugin = function ( fn , opts ) {
2014-09-14 07:04:16 -04:00
fn ( this , opts ) ;
return this ;
} ;
/ * *
* Adds an instance method to documents constructed from Models compiled from this schema .
*
* # # # # Example
*
* var schema = kittySchema = new Schema ( . . ) ;
*
* schema . method ( 'meow' , function ( ) {
* console . log ( 'meeeeeoooooooooooow' ) ;
* } )
*
* var Kitty = mongoose . model ( 'Kitty' , schema ) ;
*
* var fizz = new Kitty ;
* fizz . meow ( ) ; // meeeeeooooooooooooow
*
* If a hash of name / fn pairs is passed as the only argument , each name / fn pair will be added as methods .
*
* schema . method ( {
* purr : function ( ) { }
* , scratch : function ( ) { }
* } ) ;
*
* // later
* fizz . purr ( ) ;
* fizz . scratch ( ) ;
*
* @ param { String | Object } method name
* @ param { Function } [ fn ]
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . method = function ( name , fn ) {
2014-09-14 07:04:16 -04:00
if ( 'string' != typeof name )
for ( var i in name )
this . methods [ i ] = name [ i ] ;
else
this . methods [ name ] = fn ;
return this ;
} ;
/ * *
* Adds static "class" methods to Models compiled from this schema .
*
* # # # # Example
*
* var schema = new Schema ( . . ) ;
* schema . static ( 'findByName' , function ( name , callback ) {
* return this . find ( { name : name } , callback ) ;
* } ) ;
*
* var Drink = mongoose . model ( 'Drink' , schema ) ;
* Drink . findByName ( 'sanpellegrino' , function ( err , drinks ) {
* //
* } ) ;
*
* If a hash of name / fn pairs is passed as the only argument , each name / fn pair will be added as statics .
*
* @ param { String } name
* @ param { Function } fn
* @ api public
* /
Schema . prototype . static = function ( name , fn ) {
if ( 'string' != typeof name )
for ( var i in name )
this . statics [ i ] = name [ i ] ;
else
this . statics [ name ] = fn ;
return this ;
} ;
/ * *
* Defines an index ( most likely compound ) for this schema .
*
* # # # # Example
*
* schema . index ( { first : 1 , last : - 1 } )
*
* @ param { Object } fields
2015-11-24 22:08:58 -08:00
* @ param { Object } [ options ] Options to pass to [ MongoDB driver ' s ` createIndex() ` function ] ( http : //mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#createIndex)
* @ param { String } [ options . expires = null ] Mongoose - specific syntactic sugar , uses [ ms ] ( https : //www.npmjs.com/package/ms) to convert `expires` option into seconds for the `expireAfterSeconds` in the above link.
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . index = function ( fields , options ) {
2014-09-14 07:04:16 -04:00
options || ( options = { } ) ;
if ( options . expires )
utils . expires ( options ) ;
this . _indexes . push ( [ fields , options ] ) ;
return this ;
} ;
/ * *
* Sets / gets a schema option .
*
2015-11-24 22:08:58 -08:00
* # # # # Example
*
* schema . set ( 'strict' ) ; // 'true' by default
* schema . set ( 'strict' , false ) ; // Sets 'strict' to false
* schema . set ( 'strict' ) ; // 'false'
*
2014-09-14 07:04:16 -04:00
* @ param { String } key option name
* @ param { Object } [ value ] if not passed , the current option value is returned
2015-11-24 22:08:58 -08:00
* @ see Schema . /
2014-09-14 07:04:16 -04:00
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . set = function ( key , value , _tags ) {
2014-09-14 07:04:16 -04:00
if ( 1 === arguments . length ) {
return this . options [ key ] ;
}
switch ( key ) {
case 'read' :
2015-11-24 22:08:58 -08:00
this . options [ key ] = readPref ( value , _tags ) ;
2014-09-14 07:04:16 -04:00
break ;
case 'safe' :
this . options [ key ] = false === value
? { w : 0 }
2015-11-24 22:08:58 -08:00
: value ;
2014-09-14 07:04:16 -04:00
break ;
default :
this . options [ key ] = value ;
}
return this ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Gets a schema option .
*
* @ param { String } key option name
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . get = function ( key ) {
2014-09-14 07:04:16 -04:00
return this . options [ key ] ;
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* The allowed index types
*
* @ static indexTypes
* @ receiver Schema
* @ api public
* /
var indexTypes = '2d 2dsphere hashed text' . split ( ' ' ) ;
Object . defineProperty ( Schema , 'indexTypes' , {
2015-11-24 22:08:58 -08:00
get : function ( ) { return indexTypes ; } ,
set : function ( ) { throw new Error ( 'Cannot overwrite Schema.indexTypes' ) ; }
} ) ;
2014-09-14 07:04:16 -04:00
/ * *
* Compiles indexes from fields and schema - level indexes
*
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . indexes = function ( ) {
2014-09-14 07:04:16 -04:00
'use strict' ;
2015-11-24 22:08:58 -08:00
var indexes = [ ] ;
var seenPrefix = { } ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var collectIndexes = function ( schema , prefix ) {
if ( seenPrefix [ prefix ] ) {
return ;
}
seenPrefix [ prefix ] = true ;
2014-09-14 07:04:16 -04:00
prefix = prefix || '' ;
var key , path , index , field , isObject , options , type ;
var keys = Object . keys ( schema . paths ) ;
for ( var i = 0 ; i < keys . length ; ++ i ) {
key = keys [ i ] ;
path = schema . paths [ key ] ;
2015-11-24 22:08:58 -08:00
if ( path instanceof MongooseTypes . DocumentArray ) {
2014-09-14 07:04:16 -04:00
collectIndexes ( path . schema , key + '.' ) ;
} else {
index = path . _index ;
if ( false !== index && null != index ) {
field = { } ;
isObject = utils . isObject ( index ) ;
options = isObject ? index : { } ;
type = 'string' == typeof index ? index :
isObject ? index . type :
false ;
if ( type && ~ Schema . indexTypes . indexOf ( type ) ) {
field [ prefix + key ] = type ;
} else {
field [ prefix + key ] = 1 ;
}
delete options . type ;
if ( ! ( 'background' in options ) ) {
options . background = true ;
}
indexes . push ( [ field , options ] ) ;
}
}
}
if ( prefix ) {
fixSubIndexPaths ( schema , prefix ) ;
} else {
2015-11-24 22:08:58 -08:00
schema . _indexes . forEach ( function ( index ) {
2014-09-14 07:04:16 -04:00
if ( ! ( 'background' in index [ 1 ] ) ) index [ 1 ] . background = true ;
} ) ;
indexes = indexes . concat ( schema . _indexes ) ;
}
2015-11-24 22:08:58 -08:00
} ;
collectIndexes ( this ) ;
return indexes ;
2014-09-14 07:04:16 -04:00
/ * !
* Checks for indexes added to subdocs using Schema . index ( ) .
* These indexes need their paths prefixed properly .
*
* schema . _indexes = [ [ indexObj , options ] , [ indexObj , options ] . . ]
* /
2015-11-24 22:08:58 -08:00
function fixSubIndexPaths ( schema , prefix ) {
var subindexes = schema . _indexes ,
len = subindexes . length ,
indexObj ,
newindex ,
klen ,
keys ,
key ,
i = 0 ,
j ;
2014-09-14 07:04:16 -04:00
for ( i = 0 ; i < len ; ++ i ) {
indexObj = subindexes [ i ] [ 0 ] ;
keys = Object . keys ( indexObj ) ;
klen = keys . length ;
newindex = { } ;
// use forward iteration, order matters
for ( j = 0 ; j < klen ; ++ j ) {
key = keys [ j ] ;
newindex [ prefix + key ] = indexObj [ key ] ;
}
indexes . push ( [ newindex , subindexes [ i ] [ 1 ] ] ) ;
}
}
2015-11-24 22:08:58 -08:00
} ;
2014-09-14 07:04:16 -04:00
/ * *
* Creates a virtual type with the given name .
*
* @ param { String } name
* @ param { Object } [ options ]
* @ return { VirtualType }
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . virtual = function ( name , options ) {
2014-09-14 07:04:16 -04:00
var virtuals = this . virtuals ;
var parts = name . split ( '.' ) ;
2015-11-24 22:08:58 -08:00
return virtuals [ name ] = parts . reduce ( function ( mem , part , i ) {
mem [ part ] || ( mem [ part ] = ( i === parts . length - 1 )
2014-09-14 07:04:16 -04:00
? new VirtualType ( options , name )
: { } ) ;
return mem [ part ] ;
} , this . tree ) ;
} ;
/ * *
* Returns the virtual type with the given ` name ` .
*
* @ param { String } name
* @ return { VirtualType }
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . virtualpath = function ( name ) {
2014-09-14 07:04:16 -04:00
return this . virtuals [ name ] ;
} ;
/ * *
2015-11-24 22:08:58 -08:00
* Removes the given ` path ` ( or [ ` paths ` ] ) .
*
* @ param { String | Array } path
*
* @ api public
2014-09-14 07:04:16 -04:00
* /
2015-11-24 22:08:58 -08:00
Schema . prototype . remove = function ( path ) {
if ( typeof path === 'string' ) {
path = [ path ] ;
}
if ( Array . isArray ( path ) ) {
path . forEach ( function ( name ) {
if ( this . path ( name ) ) {
delete this . paths [ name ] ;
}
} , this ) ;
}
} ;
/ * !
* ignore
* /
Schema . prototype . _getSchema = function ( path ) {
var schema = this ;
var pathschema = schema . path ( path ) ;
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
if ( pathschema ) {
return pathschema ;
}
// look for arrays
return ( function search ( parts , schema ) {
var p = parts . length + 1 ,
foundschema ,
trypath ;
while ( p -- ) {
trypath = parts . slice ( 0 , p ) . join ( '.' ) ;
foundschema = schema . path ( trypath ) ;
if ( foundschema ) {
if ( foundschema . caster ) {
// array of Mixed?
if ( foundschema . caster instanceof MongooseTypes . Mixed ) {
return foundschema . caster ;
}
// Now that we found the array, we need to check if there
// are remaining document paths to look up for casting.
// Also we need to handle array.$.path since schema.path
// doesn't work for that.
// If there is no foundschema.schema we are dealing with
// a path like array.$
if ( p !== parts . length && foundschema . schema ) {
if ( '$' === parts [ p ] ) {
// comments.$.comments.$.title
return search ( parts . slice ( p + 1 ) , foundschema . schema ) ;
} else {
// this is the last path of the selector
return search ( parts . slice ( p ) , foundschema . schema ) ;
}
}
}
return foundschema ;
}
2014-09-14 07:04:16 -04:00
}
2015-11-24 22:08:58 -08:00
} ) ( path . split ( '.' ) , schema ) ;
2014-09-14 07:04:16 -04:00
} ;
/ * !
* Module exports .
* /
module . exports = exports = Schema ;
// require down here because of reference issues
/ * *
* The various built - in Mongoose Schema Types .
*
* # # # # Example :
*
* var mongoose = require ( 'mongoose' ) ;
* var ObjectId = mongoose . Schema . Types . ObjectId ;
*
* # # # # Types :
*
* - [ String ] ( # schema - string - js )
* - [ Number ] ( # schema - number - js )
* - [ Boolean ] ( # schema - boolean - js ) | Bool
* - [ Array ] ( # schema - array - js )
* - [ Buffer ] ( # schema - buffer - js )
* - [ Date ] ( # schema - date - js )
* - [ ObjectId ] ( # schema - objectid - js ) | Oid
* - [ Mixed ] ( # schema - mixed - js )
*
* Using this exposed access to the ` Mixed ` SchemaType , we can use them in our schema .
*
* var Mixed = mongoose . Schema . Types . Mixed ;
* new mongoose . Schema ( { _user : Mixed } )
*
* @ api public
* /
2015-11-24 22:08:58 -08:00
Schema . Types = MongooseTypes = require ( './schema/index' ) ;
2014-09-14 07:04:16 -04:00
/ * !
* ignore
* /
2015-11-24 22:08:58 -08:00
exports . ObjectId = MongooseTypes . ObjectId ;