thoughts/node_modules/mongoose/lib/schema.js
node_modules/mongoose/lib/schema.jsRaw
1/*!
2 * Module dependencies.
3 */
4
5var EventEmitter = require('events').EventEmitter
6 , VirtualType = require('./virtualtype')
7 , utils = require('./utils')
8 , NamedScope
9 , Query
10 , Types
11
12/**
13 * Schema constructor.
14 *
15 * ####Example:
16 *
17 * var child = new Schema({ name: String });
18 * var schema = new Schema({ name: String, age: Number, children: [child] });
19 * var Tree = mongoose.model('Tree', schema);
20 *
21 * // setting schema options
22 * new Schema({ name: String }, { _id: false, autoIndex: false })
23 *
24 * ####Options:
25 *
26 * - [autoIndex](/docs/guide.html#autoIndex): bool - defaults to true
27 * - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true
28 * - [capped](/docs/guide.html#capped): bool - defaults to false
29 * - [collection](/docs/guide.html#collection): string - no default
30 * - [id](/docs/guide.html#id): bool - defaults to true
31 * - [_id](/docs/guide.html#_id): bool - defaults to true
32 * - `minimize`: bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true
33 * - [read](/docs/guide.html#read): string
34 * - [safe](/docs/guide.html#safe): bool - defaults to true.
35 * - [shardKey](/docs/guide.html#shardKey): bool - defaults to `null`
36 * - [strict](/docs/guide.html#strict): bool - defaults to true
37 * - [toJSON](/docs/guide.html#toJSON) - object - no default
38 * - [toObject](/docs/guide.html#toObject) - object - no default
39 * - [versionKey](/docs/guide.html#versionKey): bool - defaults to "__v"
40 *
41 * ####Note:
42 *
43 * _When nesting schemas, (`children` in the example above), always declare the child schema first before passing it into is parent._
44 *
45 * @param {Object} definition
46 * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
47 * @event `init`: Emitted after the schema is compiled into a `Model`.
48 * @api public
49 */
50
51function Schema (obj, options) {
52 if (!(this instanceof Schema))
53 return new Schema(obj, options);
54
55 this.paths = {};
56 this.subpaths = {};
57 this.virtuals = {};
58 this.nested = {};
59 this.inherits = {};
60 this.callQueue = [];
61 this._indexes = [];
62 this.methods = {};
63 this.statics = {};
64 this.tree = {};
65 this._requiredpaths = undefined;
66
67 this.options = this.defaultOptions(options);
68
69 // build paths
70 if (obj) {
71 this.add(obj);
72 }
73
74 // ensure the documents get an auto _id unless disabled
75 var auto_id = !this.paths['_id'] && (!this.options.noId && this.options._id);
76 if (auto_id) {
77 this.add({ _id: {type: Schema.ObjectId, auto: true} });
78 }
79
80 // ensure the documents receive an id getter unless disabled
81 var autoid = !this.paths['id'] && (!this.options.noVirtualId && this.options.id);
82 if (autoid) {
83 this.virtual('id').get(idGetter);
84 }
85}
86
87/*!
88 * Returns this documents _id cast to a string.
89 */
90
91function idGetter () {
92 if (this.$__._id) {
93 return this.$__._id;
94 }
95
96 return this.$__._id = null == this._id
97 ? null
98 : String(this._id);
99}
100
101/*!
102 * Inherit from EventEmitter.
103 */
104
105Schema.prototype.__proto__ = EventEmitter.prototype;
106
107/**
108 * Schema as flat paths
109 *
110 * ####Example:
111 * {
112 * '_id' : SchemaType,
113 * , 'nested.key' : SchemaType,
114 * }
115 *
116 * @api private
117 * @property paths
118 */
119
120Schema.prototype.paths;
121
122/**
123 * Schema as a tree
124 *
125 * ####Example:
126 * {
127 * '_id' : ObjectId
128 * , 'nested' : {
129 * 'key' : String
130 * }
131 * }
132 *
133 * @api private
134 * @property tree
135 */
136
137Schema.prototype.tree;
138
139/**
140 * Returns default options for this schema, merged with `options`.
141 *
142 * @param {Object} options
143 * @return {Object}
144 * @api private
145 */
146
147Schema.prototype.defaultOptions = function (options) {
148 if (options && false === options.safe) {
149 options.safe = { w: 0 };
150 }
151
152 options = utils.options({
153 strict: true
154 , bufferCommands: true
155 , capped: false // { size, max, autoIndexId }
156 , versionKey: '__v'
157 , minimize: true
158 , autoIndex: true
159 , shardKey: null
160 , read: null
161 // the following are only applied at construction time
162 , noId: false // deprecated, use { _id: false }
163 , _id: true
164 , noVirtualId: false // deprecated, use { id: false }
165 , id: true
166 }, options);
167
168 if (options.read)
169 options.read = utils.readPref(options.read);
170
171 return options;
172}
173
174/**
175 * Adds key path / schema type pairs to this schema.
176 *
177 * ####Example:
178 *
179 * var ToySchema = new Schema;
180 * ToySchema.add({ name: 'string', color: 'string', price: 'number' });
181 *
182 * @param {Object} obj
183 * @param {String} prefix
184 * @api public
185 */
186
187Schema.prototype.add = function add (obj, prefix) {
188 prefix = prefix || '';
189 var keys = Object.keys(obj);
190
191 for (var i = 0; i < keys.length; ++i) {
192 var key = keys[i];
193
194 if (null == obj[key]) {
195 throw new TypeError('Invalid value for schema path `'+ prefix + key +'`');
196 }
197
198 if (utils.isObject(obj[key]) && (!obj[key].constructor || 'Object' == obj[key].constructor.name) && (!obj[key].type || obj[key].type.type)) {
199 if (Object.keys(obj[key]).length) {
200 // nested object { last: { name: String }}
201 this.nested[prefix + key] = true;
202 this.add(obj[key], prefix + key + '.');
203 } else {
204 this.path(prefix + key, obj[key]); // mixed type
205 }
206 } else {
207 this.path(prefix + key, obj[key]);
208 }
209 }
210};
211
212/**
213 * Reserved document keys.
214 *
215 * 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.
216 *
217 * on, emit, _events, db, init, isNew, errors, schema, options, modelName, collection, _pres, _posts, toObject
218 *
219 * _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.
220 *
221 * var schema = new Schema(..);
222 * schema.methods.init = function () {} // potentially breaking
223 */
224
225Schema.reserved = Object.create(null);
226var reserved = Schema.reserved;
227reserved.on =
228reserved.db =
229reserved.init =
230reserved.isNew =
231reserved.errors =
232reserved.schema =
233reserved.options =
234reserved.modelName =
235reserved.collection =
236reserved.toObject =
237reserved.emit = // EventEmitter
238reserved._events = // EventEmitter
239reserved._pres = reserved._posts = 1 // hooks.js
240
241/**
242 * Gets/sets schema paths.
243 *
244 * Sets a path (if arity 2)
245 * Gets a path (if arity 1)
246 *
247 * ####Example
248 *
249 * schema.path('name') // returns a SchemaType
250 * schema.path('name', Number) // changes the schemaType of `name` to Number
251 *
252 * @param {String} path
253 * @param {Object} constructor
254 * @api public
255 */
256
257Schema.prototype.path = function (path, obj) {
258 if (obj == undefined) {
259 if (this.paths[path]) return this.paths[path];
260 if (this.subpaths[path]) return this.subpaths[path];
261
262 // subpaths?
263 return /\.\d+\.?.*$/.test(path)
264 ? getPositionalPath(this, path)
265 : undefined;
266 }
267
268 // some path names conflict with document methods
269 if (reserved[path]) {
270 throw new Error("`" + path + "` may not be used as a schema pathname");
271 }
272
273 // update the tree
274 var subpaths = path.split(/\./)
275 , last = subpaths.pop()
276 , branch = this.tree;
277
278 subpaths.forEach(function(sub, i) {
279 if (!branch[sub]) branch[sub] = {};
280 if ('object' != typeof branch[sub]) {
281 var msg = 'Cannot set nested path `' + path + '`. '
282 + 'Parent path `'
283 + subpaths.slice(0, i).concat([sub]).join('.')
284 + '` already set to type ' + branch[sub].name
285 + '.';
286 throw new Error(msg);
287 }
288 branch = branch[sub];
289 });
290
291 branch[last] = utils.clone(obj);
292
293 this.paths[path] = Schema.interpretAsType(path, obj);
294 return this;
295};
296
297/**
298 * Converts type arguments into Mongoose Types.
299 *
300 * @param {String} path
301 * @param {Object} obj constructor
302 * @api private
303 */
304
305Schema.interpretAsType = function (path, obj) {
306 if (obj.constructor && obj.constructor.name != 'Object')
307 obj = { type: obj };
308
309 // Get the type making sure to allow keys named "type"
310 // and default to mixed if not specified.
311 // { type: { type: String, default: 'freshcut' } }
312 var type = obj.type && !obj.type.type
313 ? obj.type
314 : {};
315
316 if ('Object' == type.constructor.name || 'mixed' == type) {
317 return new Types.Mixed(path, obj);
318 }
319
320 if (Array.isArray(type) || Array == type || 'array' == type) {
321 // if it was specified through { type } look for `cast`
322 var cast = (Array == type || 'array' == type)
323 ? obj.cast
324 : type[0];
325
326 if (cast instanceof Schema) {
327 return new Types.DocumentArray(path, cast, obj);
328 }
329
330 if ('string' == typeof cast) {
331 cast = Types[cast.charAt(0).toUpperCase() + cast.substring(1)];
332 } else if (cast && (!cast.type || cast.type.type)
333 && 'Object' == cast.constructor.name
334 && Object.keys(cast).length) {
335 return new Types.DocumentArray(path, new Schema(cast), obj);
336 }
337
338 return new Types.Array(path, cast || Types.Mixed, obj);
339 }
340
341 var name = 'string' == typeof type
342 ? type
343 : type.name;
344
345 if (name) {
346 name = name.charAt(0).toUpperCase() + name.substring(1);
347 }
348
349 if (undefined == Types[name]) {
350 throw new TypeError('Undefined type at `' + path +
351 '`\n Did you try nesting Schemas? ' +
352 'You can only nest using refs or arrays.');
353 }
354
355 return new Types[name](path, obj);
356};
357
358/**
359 * Iterates the schemas paths similar to Array#forEach.
360 *
361 * The callback is passed the pathname and schemaType as arguments on each iteration.
362 *
363 * @param {Function} fn callback function
364 * @return {Schema} this
365 * @api public
366 */
367
368Schema.prototype.eachPath = function (fn) {
369 var keys = Object.keys(this.paths)
370 , len = keys.length;
371
372 for (var i = 0; i < len; ++i) {
373 fn(keys[i], this.paths[keys[i]]);
374 }
375
376 return this;
377};
378
379/**
380 * Returns an Array of path strings that are required by this schema.
381 *
382 * @api public
383 * @return {Array}
384 */
385
386Schema.prototype.requiredPaths = function requiredPaths () {
387 if (this._requiredpaths) return this._requiredpaths;
388
389 var paths = Object.keys(this.paths)
390 , i = paths.length
391 , ret = [];
392
393 while (i--) {
394 var path = paths[i];
395 if (this.paths[path].isRequired) ret.push(path);
396 }
397
398 return this._requiredpaths = ret;
399}
400
401/**
402 * Returns the pathType of `path` for this schema.
403 *
404 * Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.
405 *
406 * @param {String} path
407 * @return {String}
408 * @api public
409 */
410
411Schema.prototype.pathType = function (path) {
412 if (path in this.paths) return 'real';
413 if (path in this.virtuals) return 'virtual';
414 if (path in this.nested) return 'nested';
415 if (path in this.subpaths) return 'real';
416
417 if (/\.\d+\.|\.\d+$/.test(path) && getPositionalPath(this, path)) {
418 return 'real';
419 } else {
420 return 'adhocOrUndefined'
421 }
422};
423
424/*!
425 * ignore
426 */
427
428function getPositionalPath (self, path) {
429 var subpaths = path.split(/\.(\d+)\.|\.(\d+)$/).filter(Boolean);
430 if (subpaths.length < 2) {
431 return self.paths[subpaths[0]];
432 }
433
434 var val = self.path(subpaths[0]);
435 if (!val) return val;
436
437 var last = subpaths.length - 1
438 , subpath
439 , i = 1;
440
441 for (; i < subpaths.length; ++i) {
442 subpath = subpaths[i];
443
444 if (i === last && val && !val.schema && !/\D/.test(subpath)) {
445 if (val instanceof Types.Array) {
446 // StringSchema, NumberSchema, etc
447 val = val.caster;
448 } else {
449 val = undefined;
450 }
451 break;
452 }
453
454 // ignore if its just a position segment: path.0.subpath
455 if (!/\D/.test(subpath)) continue;
456
457 if (!(val && val.schema)) {
458 val = undefined;
459 break;
460 }
461
462 val = val.schema.path(subpath);
463 }
464
465 return self.subpaths[path] = val;
466}
467
468/**
469 * Adds a method call to the queue.
470 *
471 * @param {String} name name of the document method to call later
472 * @param {Array} args arguments to pass to the method
473 * @api private
474 */
475
476Schema.prototype.queue = function(name, args){
477 this.callQueue.push([name, args]);
478 return this;
479};
480
481/**
482 * Defines a pre hook for the document.
483 *
484 * ####Example
485 *
486 * var toySchema = new Schema(..);
487 *
488 * toySchema.pre('save', function (next) {
489 * if (!this.created) this.created = new Date;
490 * next();
491 * })
492 *
493 * toySchema.pre('validate', function (next) {
494 * if (this.name != 'Woody') this.name = 'Woody';
495 * next();
496 * })
497 *
498 * @param {String} method
499 * @param {Function} callback
500 * @see hooks.js https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3
501 * @api public
502 */
503
504Schema.prototype.pre = function(){
505 return this.queue('pre', arguments);
506};
507
508/**
509 * Defines a post for the document
510 *
511 * Post hooks fire `on` the event emitted from document instances of Models compiled from this schema.
512 *
513 * var schema = new Schema(..);
514 * schema.post('save', function (doc) {
515 * console.log('this fired after a document was saved');
516 * });
517 *
518 * var Model = mongoose.model('Model', schema);
519 *
520 * var m = new Model(..);
521 * m.save(function (err) {
522 * console.log('this fires after the `post` hook');
523 * });
524 *
525 * @param {String} method name of the method to hook
526 * @param {Function} fn callback
527 * @see hooks.js https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3
528 * @api public
529 */
530
531Schema.prototype.post = function(method, fn){
532 return this.queue('on', arguments);
533};
534
535/**
536 * Registers a plugin for this schema.
537 *
538 * @param {Function} plugin callback
539 * @param {Object} opts
540 * @see plugins
541 * @api public
542 */
543
544Schema.prototype.plugin = function (fn, opts) {
545 fn(this, opts);
546 return this;
547};
548
549/**
550 * Adds an instance method to documents constructed from Models compiled from this schema.
551 *
552 * ####Example
553 *
554 * var schema = kittySchema = new Schema(..);
555 *
556 * schema.method('meow', function () {
557 * console.log('meeeeeoooooooooooow');
558 * })
559 *
560 * var Kitty = mongoose.model('Kitty', schema);
561 *
562 * var fizz = new Kitty;
563 * fizz.meow(); // meeeeeooooooooooooow
564 *
565 * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
566 *
567 * schema.method({
568 * purr: function () {}
569 * , scratch: function () {}
570 * });
571 *
572 * // later
573 * fizz.purr();
574 * fizz.scratch();
575 *
576 * @param {String|Object} method name
577 * @param {Function} [fn]
578 * @api public
579 */
580
581Schema.prototype.method = function (name, fn) {
582 if ('string' != typeof name)
583 for (var i in name)
584 this.methods[i] = name[i];
585 else
586 this.methods[name] = fn;
587 return this;
588};
589
590/**
591 * Adds static "class" methods to Models compiled from this schema.
592 *
593 * ####Example
594 *
595 * var schema = new Schema(..);
596 * schema.static('findByName', function (name, callback) {
597 * return this.find({ name: name }, callback);
598 * });
599 *
600 * var Drink = mongoose.model('Drink', schema);
601 * Drink.findByName('sanpellegrino', function (err, drinks) {
602 * //
603 * });
604 *
605 * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
606 *
607 * @param {String} name
608 * @param {Function} fn
609 * @api public
610 */
611
612Schema.prototype.static = function(name, fn) {
613 if ('string' != typeof name)
614 for (var i in name)
615 this.statics[i] = name[i];
616 else
617 this.statics[name] = fn;
618 return this;
619};
620
621/**
622 * Defines an index (most likely compound) for this schema.
623 *
624 * ####Example
625 *
626 * schema.index({ first: 1, last: -1 })
627 *
628 * @param {Object} fields
629 * @param {Object} [options]
630 * @api public
631 */
632
633Schema.prototype.index = function (fields, options) {
634 options || (options = {});
635
636 if (options.expires)
637 utils.expires(options);
638
639 this._indexes.push([fields, options]);
640 return this;
641};
642
643/**
644 * Sets/gets a schema option.
645 *
646 * @param {String} key option name
647 * @param {Object} [value] if not passed, the current option value is returned
648 * @api public
649 */
650
651Schema.prototype.set = function (key, value, _tags) {
652 if (1 === arguments.length) {
653 return this.options[key];
654 }
655
656 switch (key) {
657 case 'read':
658 this.options[key] = utils.readPref(value, _tags)
659 break;
660 case 'safe':
661 this.options[key] = false === value
662 ? { w: 0 }
663 : value
664 break;
665 default:
666 this.options[key] = value;
667 }
668
669 return this;
670}
671
672/**
673 * Gets a schema option.
674 *
675 * @param {String} key option name
676 * @api public
677 */
678
679Schema.prototype.get = function (key) {
680 return this.options[key];
681}
682
683/**
684 * The allowed index types
685 *
686 * @static indexTypes
687 * @receiver Schema
688 * @api public
689 */
690
691var indexTypes = '2d 2dsphere hashed text'.split(' ');
692
693Object.defineProperty(Schema, 'indexTypes', {
694 get: function () { return indexTypes }
695 , set: function () { throw new Error('Cannot overwrite Schema.indexTypes') }
696})
697
698/**
699 * Compiles indexes from fields and schema-level indexes
700 *
701 * @api public
702 */
703
704Schema.prototype.indexes = function () {
705 'use strict';
706
707 var indexes = []
708 , seenSchemas = []
709 collectIndexes(this);
710 return indexes;
711
712 function collectIndexes (schema, prefix) {
713 if (~seenSchemas.indexOf(schema)) return;
714 seenSchemas.push(schema);
715
716 prefix = prefix || '';
717
718 var key, path, index, field, isObject, options, type;
719 var keys = Object.keys(schema.paths);
720
721 for (var i = 0; i < keys.length; ++i) {
722 key = keys[i];
723 path = schema.paths[key];
724
725 if (path instanceof Types.DocumentArray) {
726 collectIndexes(path.schema, key + '.');
727 } else {
728 index = path._index;
729
730 if (false !== index && null != index) {
731 field = {};
732 isObject = utils.isObject(index);
733 options = isObject ? index : {};
734 type = 'string' == typeof index ? index :
735 isObject ? index.type :
736 false;
737
738 if (type && ~Schema.indexTypes.indexOf(type)) {
739 field[prefix + key] = type;
740 } else {
741 field[prefix + key] = 1;
742 }
743
744 delete options.type;
745 if (!('background' in options)) {
746 options.background = true;
747 }
748
749 indexes.push([field, options]);
750 }
751 }
752 }
753
754 if (prefix) {
755 fixSubIndexPaths(schema, prefix);
756 } else {
757 schema._indexes.forEach(function (index) {
758 if (!('background' in index[1])) index[1].background = true;
759 });
760 indexes = indexes.concat(schema._indexes);
761 }
762 }
763
764 /*!
765 * Checks for indexes added to subdocs using Schema.index().
766 * These indexes need their paths prefixed properly.
767 *
768 * schema._indexes = [ [indexObj, options], [indexObj, options] ..]
769 */
770
771 function fixSubIndexPaths (schema, prefix) {
772 var subindexes = schema._indexes
773 , len = subindexes.length
774 , indexObj
775 , newindex
776 , klen
777 , keys
778 , key
779 , i = 0
780 , j
781
782 for (i = 0; i < len; ++i) {
783 indexObj = subindexes[i][0];
784 keys = Object.keys(indexObj);
785 klen = keys.length;
786 newindex = {};
787
788 // use forward iteration, order matters
789 for (j = 0; j < klen; ++j) {
790 key = keys[j];
791 newindex[prefix + key] = indexObj[key];
792 }
793
794 indexes.push([newindex, subindexes[i][1]]);
795 }
796 }
797}
798
799/**
800 * Creates a virtual type with the given name.
801 *
802 * @param {String} name
803 * @param {Object} [options]
804 * @return {VirtualType}
805 */
806
807Schema.prototype.virtual = function (name, options) {
808 var virtuals = this.virtuals;
809 var parts = name.split('.');
810 return virtuals[name] = parts.reduce(function (mem, part, i) {
811 mem[part] || (mem[part] = (i === parts.length-1)
812 ? new VirtualType(options, name)
813 : {});
814 return mem[part];
815 }, this.tree);
816};
817
818/**
819 * Returns the virtual type with the given `name`.
820 *
821 * @param {String} name
822 * @return {VirtualType}
823 */
824
825Schema.prototype.virtualpath = function (name) {
826 return this.virtuals[name];
827};
828
829/**
830 * These still haven't been fixed. Once they're working we'll make them public again.
831 * @api private
832 */
833
834Schema.prototype.namedScope = function (name, fn) {
835 var namedScopes = this.namedScopes || (this.namedScopes = new NamedScope)
836 , newScope = Object.create(namedScopes)
837 , allScopes = namedScopes.scopesByName || (namedScopes.scopesByName = {});
838 allScopes[name] = newScope;
839 newScope.name = name;
840 newScope.block = fn;
841 newScope.query = new Query();
842 newScope.decorate(namedScopes, {
843 block0: function (block) {
844 return function () {
845 block.call(this.query);
846 return this;
847 };
848 },
849 blockN: function (block) {
850 return function () {
851 block.apply(this.query, arguments);
852 return this;
853 };
854 },
855 basic: function (query) {
856 return function () {
857 this.query.find(query);
858 return this;
859 };
860 }
861 });
862 return newScope;
863};
864
865/*!
866 * Module exports.
867 */
868
869module.exports = exports = Schema;
870
871// require down here because of reference issues
872
873/**
874 * The various built-in Mongoose Schema Types.
875 *
876 * ####Example:
877 *
878 * var mongoose = require('mongoose');
879 * var ObjectId = mongoose.Schema.Types.ObjectId;
880 *
881 * ####Types:
882 *
883 * - [String](#schema-string-js)
884 * - [Number](#schema-number-js)
885 * - [Boolean](#schema-boolean-js) | Bool
886 * - [Array](#schema-array-js)
887 * - [Buffer](#schema-buffer-js)
888 * - [Date](#schema-date-js)
889 * - [ObjectId](#schema-objectid-js) | Oid
890 * - [Mixed](#schema-mixed-js)
891 *
892 * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema.
893 *
894 * var Mixed = mongoose.Schema.Types.Mixed;
895 * new mongoose.Schema({ _user: Mixed })
896 *
897 * @api public
898 */
899
900Schema.Types = require('./schema/index');
901
902/*!
903 * ignore
904 */
905
906Types = Schema.Types;
907NamedScope = require('./namedscope')
908Query = require('./query');
909var ObjectId = exports.ObjectId = Types.ObjectId;
910