thoughts/node_modules/mongoose/lib/schematype.js
node_modules/mongoose/lib/schematype.jsRaw
1/*!
2 * Module dependencies.
3 */
4
5var utils = require('./utils');
6var CastError = require('./error').CastError;
7var ValidatorError = require('./error').ValidatorError;
8
9/**
10 * SchemaType constructor
11 *
12 * @param {String} path
13 * @param {Object} [options]
14 * @param {String} [instance]
15 * @api public
16 */
17
18function SchemaType (path, options, instance) {
19 this.path = path;
20 this.instance = instance;
21 this.validators = [];
22 this.setters = [];
23 this.getters = [];
24 this.options = options;
25 this._index = null;
26 this.selected;
27
28 for (var i in options) if (this[i] && 'function' == typeof this[i]) {
29 // { unique: true, index: true }
30 if ('index' == i && this._index) continue;
31
32 var opts = Array.isArray(options[i])
33 ? options[i]
34 : [options[i]];
35
36 this[i].apply(this, opts);
37 }
38};
39
40/**
41 * Sets a default value for this SchemaType.
42 *
43 * ####Example:
44 *
45 * var schema = new Schema({ n: { type: Number, default: 10 })
46 * var M = db.model('M', schema)
47 * var m = new M;
48 * console.log(m.n) // 10
49 *
50 * Defaults can be either `functions` which return the value to use as the default or the literal value itself. Either way, the value will be cast based on its schema type before being set during document creation.
51 *
52 * ####Example:
53 *
54 * // values are cast:
55 * var schema = new Schema({ aNumber: Number, default: "4.815162342" })
56 * var M = db.model('M', schema)
57 * var m = new M;
58 * console.log(m.aNumber, typeof m.aNumber) // 4.815162342 "number"
59 *
60 * @param {Function|any} val the default value
61 * @return {defaultValue}
62 * @api public
63 */
64
65SchemaType.prototype.default = function (val) {
66 if (1 === arguments.length) {
67 this.defaultValue = typeof val === 'function'
68 ? val
69 : this.cast(val);
70 return this;
71 } else if (arguments.length > 1) {
72 this.defaultValue = utils.args(arguments);
73 }
74 return this.defaultValue;
75};
76
77/**
78 * Declares the index options for this schematype.
79 *
80 * ####Example:
81 *
82 * var s = new Schema({ name: { type: String, index: true })
83 * var s = new Schema({ loc: { type: [Number], index: 'hashed' })
84 * var s = new Schema({ loc: { type: [Number], index: '2d', sparse: true })
85 * var s = new Schema({ loc: { type: [Number], index: { type: '2dsphere', sparse: true }})
86 * var s = new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }})
87 * Schema.path('my.path').index(true);
88 * Schema.path('my.date').index({ expires: 60 });
89 * Schema.path('my.path').index({ unique: true, sparse: true });
90 *
91 * ####NOTE:
92 *
93 * _Indexes are created in the background by default. Specify `background: false` to override._
94 *
95 * [Direction doesn't matter for single key indexes](http://www.mongodb.org/display/DOCS/Indexes#Indexes-CompoundKeysIndexes)
96 *
97 * @param {Object|Boolean|String} options
98 * @return {SchemaType} this
99 * @api public
100 */
101
102SchemaType.prototype.index = function (options) {
103 this._index = options;
104 utils.expires(this._index);
105 return this;
106};
107
108/**
109 * Declares an unique index.
110 *
111 * ####Example:
112 *
113 * var s = new Schema({ name: { type: String, unique: true })
114 * Schema.path('name').index({ unique: true });
115 *
116 * _NOTE: violating the constraint returns an `E11000` error from MongoDB when saving, not a Mongoose validation error._
117 *
118 * @param {Boolean} bool
119 * @return {SchemaType} this
120 * @api public
121 */
122
123SchemaType.prototype.unique = function (bool) {
124 if (null == this._index || 'boolean' == typeof this._index) {
125 this._index = {};
126 } else if ('string' == typeof this._index) {
127 this._index = { type: this._index };
128 }
129
130 this._index.unique = bool;
131 return this;
132};
133
134/**
135 * Declares a sparse index.
136 *
137 * ####Example:
138 *
139 * var s = new Schema({ name: { type: String, sparse: true })
140 * Schema.path('name').index({ sparse: true });
141 *
142 * @param {Boolean} bool
143 * @return {SchemaType} this
144 * @api public
145 */
146
147SchemaType.prototype.sparse = function (bool) {
148 if (null == this._index || 'boolean' == typeof this._index) {
149 this._index = {};
150 } else if ('string' == typeof this._index) {
151 this._index = { type: this._index };
152 }
153
154 this._index.sparse = bool;
155 return this;
156};
157
158/**
159 * Adds a setter to this schematype.
160 *
161 * ####Example:
162 *
163 * function capitalize (val) {
164 * if ('string' != typeof val) val = '';
165 * return val.charAt(0).toUpperCase() + val.substring(1);
166 * }
167 *
168 * // defining within the schema
169 * var s = new Schema({ name: { type: String, set: capitalize }})
170 *
171 * // or by retreiving its SchemaType
172 * var s = new Schema({ name: String })
173 * s.path('name').set(capitalize)
174 *
175 * Setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
176 *
177 * Suppose you are implementing user registration for a website. Users provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.
178 *
179 * You can set up email lower case normalization easily via a Mongoose setter.
180 *
181 * function toLower (v) {
182 * return v.toLowerCase();
183 * }
184 *
185 * var UserSchema = new Schema({
186 * email: { type: String, set: toLower }
187 * })
188 *
189 * var User = db.model('User', UserSchema)
190 *
191 * var user = new User({email: 'AVENUE@Q.COM'})
192 * console.log(user.email); // 'avenue@q.com'
193 *
194 * // or
195 * var user = new User
196 * user.email = 'Avenue@Q.com'
197 * console.log(user.email) // 'avenue@q.com'
198 *
199 * As you can see above, setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
200 *
201 * _NOTE: we could have also just used the built-in `lowercase: true` SchemaType option instead of defining our own function._
202 *
203 * new Schema({ email: { type: String, lowercase: true }})
204 *
205 * Setters are also passed a second argument, the schematype on which the setter was defined. This allows for tailored behavior based on options passed in the schema.
206 *
207 * function inspector (val, schematype) {
208 * if (schematype.options.required) {
209 * return schematype.path + ' is required';
210 * } else {
211 * return val;
212 * }
213 * }
214 *
215 * var VirusSchema = new Schema({
216 * name: { type: String, required: true, set: inspector },
217 * taxonomy: { type: String, set: inspector }
218 * })
219 *
220 * var Virus = db.model('Virus', VirusSchema);
221 * var v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' });
222 *
223 * console.log(v.name); // name is required
224 * console.log(v.taxonomy); // Parvovirinae
225 *
226 * @param {Function} fn
227 * @return {SchemaType} this
228 * @api public
229 */
230
231SchemaType.prototype.set = function (fn) {
232 if ('function' != typeof fn)
233 throw new TypeError('A setter must be a function.');
234 this.setters.push(fn);
235 return this;
236};
237
238/**
239 * Adds a getter to this schematype.
240 *
241 * ####Example:
242 *
243 * function dob (val) {
244 * if (!val) return val;
245 * return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear();
246 * }
247 *
248 * // defining within the schema
249 * var s = new Schema({ born: { type: Date, get: dob })
250 *
251 * // or by retreiving its SchemaType
252 * var s = new Schema({ born: Date })
253 * s.path('born').get(dob)
254 *
255 * Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see.
256 *
257 * Suppose you are storing credit card numbers and you want to hide everything except the last 4 digits to the mongoose user. You can do so by defining a getter in the following way:
258 *
259 * function obfuscate (cc) {
260 * return '****-****-****-' + cc.slice(cc.length-4, cc.length);
261 * }
262 *
263 * var AccountSchema = new Schema({
264 * creditCardNumber: { type: String, get: obfuscate }
265 * });
266 *
267 * var Account = db.model('Account', AccountSchema);
268 *
269 * Account.findById(id, function (err, found) {
270 * console.log(found.creditCardNumber); // '****-****-****-1234'
271 * });
272 *
273 * Getters are also passed a second argument, the schematype on which the getter was defined. This allows for tailored behavior based on options passed in the schema.
274 *
275 * function inspector (val, schematype) {
276 * if (schematype.options.required) {
277 * return schematype.path + ' is required';
278 * } else {
279 * return schematype.path + ' is not';
280 * }
281 * }
282 *
283 * var VirusSchema = new Schema({
284 * name: { type: String, required: true, get: inspector },
285 * taxonomy: { type: String, get: inspector }
286 * })
287 *
288 * var Virus = db.model('Virus', VirusSchema);
289 *
290 * Virus.findById(id, function (err, virus) {
291 * console.log(virus.name); // name is required
292 * console.log(virus.taxonomy); // taxonomy is not
293 * })
294 *
295 * @param {Function} fn
296 * @return {SchemaType} this
297 * @api public
298 */
299
300SchemaType.prototype.get = function (fn) {
301 if ('function' != typeof fn)
302 throw new TypeError('A getter must be a function.');
303 this.getters.push(fn);
304 return this;
305};
306
307/**
308 * Adds validator(s) for this document path.
309 *
310 * Validators always receive the value to validate as their first argument and must return `Boolean`. Returning false is interpreted as validation failure.
311 *
312 * ####Examples:
313 *
314 * function validator (val) {
315 * return val == 'something';
316 * }
317 *
318 * new Schema({ name: { type: String, validate: validator }});
319 *
320 * // with a custom error message
321 *
322 * var custom = [validator, 'validation failed']
323 * new Schema({ name: { type: String, validate: custom }});
324 *
325 * var many = [
326 * { validator: validator, msg: 'uh oh' }
327 * , { validator: fn, msg: 'failed' }
328 * ]
329 * new Schema({ name: { type: String, validate: many }});
330 *
331 * // or utilizing SchemaType methods directly:
332 *
333 * var schema = new Schema({ name: 'string' });
334 * schema.path('name').validate(validator, 'validation failed');
335 *
336 * ####Asynchronous validation:
337 *
338 * Passing a validator function that receives two arguments tells mongoose that the validator is an asynchronous validator. The second argument is an callback function that must be passed either `true` or `false` when validation is complete.
339 *
340 * schema.path('name').validate(function (value, respond) {
341 * doStuff(value, function () {
342 * ...
343 * respond(false); // validation failed
344 * })
345* }, 'my error type');
346*
347 * You might use asynchronous validators to retreive other documents from the database to validate against or to meet other I/O bound validation needs.
348 *
349 * Validation occurs `pre('save')` or whenever you manually execute [document#validate](#document_Document-validate).
350 *
351 * If validation fails during `pre('save')` and no callback was passed to receive the error, an `error` event will be emitted on your Models associated db [connection](#connection_Connection), passing the validation error object along.
352 *
353 * var conn = mongoose.createConnection(..);
354 * conn.on('error', handleError);
355 *
356 * var Product = conn.model('Product', yourSchema);
357 * var dvd = new Product(..);
358 * dvd.save(); // emits error on the `conn` above
359 *
360 * If you desire handling these errors at the Model level, attach an `error` listener to your Model and the event will instead be emitted there.
361 *
362 * // registering an error listener on the Model lets us handle errors more locally
363 * Product.on('error', handleError);
364 *
365 * @param {RegExp|Function|Object} obj validator
366 * @param {String} [error] optional error message
367 * @return {SchemaType} this
368 * @api public
369 */
370
371SchemaType.prototype.validate = function (obj, error) {
372 if ('function' == typeof obj || obj && 'RegExp' === obj.constructor.name) {
373 this.validators.push([obj, error]);
374 return this;
375 }
376
377 var i = arguments.length
378 , arg
379
380 while (i--) {
381 arg = arguments[i];
382 if (!(arg && 'Object' == arg.constructor.name)) {
383 var msg = 'Invalid validator. Received (' + typeof arg + ') '
384 + arg
385 + '. See http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate';
386
387 throw new Error(msg);
388 }
389 this.validate(arg.validator, arg.msg);
390 }
391
392 return this;
393};
394
395/**
396 * Adds a required validator to this schematype.
397 *
398 * ####Example:
399 *
400 * var s = new Schema({ born: { type: Date, required: true })
401 * // or
402 * Schema.path('name').required(true);
403 *
404 *
405 * @param {Boolean} required enable/disable the validator
406 * @return {SchemaType} this
407 * @api public
408 */
409
410SchemaType.prototype.required = function (required) {
411 var self = this;
412
413 function __checkRequired (v) {
414 // in here, `this` refers to the validating document.
415 // no validation when this path wasn't selected in the query.
416 if ('isSelected' in this &&
417 !this.isSelected(self.path) &&
418 !this.isModified(self.path)) return true;
419 return self.checkRequired(v, this);
420 }
421
422 if (false === required) {
423 this.isRequired = false;
424 this.validators = this.validators.filter(function (v) {
425 return v[0].name !== '__checkRequired';
426 });
427 } else {
428 this.isRequired = true;
429 this.validators.push([__checkRequired, 'required']);
430 }
431
432 return this;
433};
434
435/**
436 * Gets the default value
437 *
438 * @param {Object} scope the scope which callback are executed
439 * @param {Boolean} init
440 * @api private
441 */
442
443SchemaType.prototype.getDefault = function (scope, init) {
444 var ret = 'function' === typeof this.defaultValue
445 ? this.defaultValue.call(scope)
446 : this.defaultValue;
447
448 if (null !== ret && undefined !== ret) {
449 return this.cast(ret, scope, init);
450 } else {
451 return ret;
452 }
453};
454
455/**
456 * Applies setters
457 *
458 * @param {Object} value
459 * @param {Object} scope
460 * @param {Boolean} init
461 * @api private
462 */
463
464SchemaType.prototype.applySetters = function (value, scope, init, priorVal) {
465 if (SchemaType._isRef(this, value, scope, init)) {
466 return init
467 ? value
468 : this.cast(value, scope, init, priorVal);
469 }
470
471 var v = value
472 , setters = this.setters
473 , len = setters.length
474
475 if (!len) {
476 if (null === v || undefined === v) return v;
477 return this.cast(v, scope, init, priorVal)
478 }
479
480 while (len--) {
481 v = setters[len].call(scope, v, this);
482 }
483
484 if (null === v || undefined === v) return v;
485
486 // do not cast until all setters are applied #665
487 v = this.cast(v, scope, init, priorVal);
488
489 return v;
490};
491
492/**
493 * Applies getters to a value
494 *
495 * @param {Object} value
496 * @param {Object} scope
497 * @api private
498 */
499
500SchemaType.prototype.applyGetters = function (value, scope) {
501 if (SchemaType._isRef(this, value, scope, true)) return value;
502
503 var v = value
504 , getters = this.getters
505 , len = getters.length;
506
507 if (!len) {
508 return v;
509 }
510
511 while (len--) {
512 v = getters[len].call(scope, v, this);
513 }
514
515 return v;
516};
517
518/**
519 * Sets default `select()` behavior for this path.
520 *
521 * Set to `true` if this path should always be included in the results, `false` if it should be excluded by default. This setting can be overridden at the query level.
522 *
523 * ####Example:
524 *
525 * T = db.model('T', new Schema({ x: { type: String, select: true }}));
526 * T.find(..); // field x will always be selected ..
527 * // .. unless overridden;
528 * T.find().select('-x').exec(callback);
529 *
530 * @param {Boolean} val
531 * @return {SchemaType} this
532 * @api public
533 */
534
535SchemaType.prototype.select = function select (val) {
536 this.selected = !! val;
537 return this;
538}
539
540/**
541 * Performs a validation of `value` using the validators declared for this SchemaType.
542 *
543 * @param {any} value
544 * @param {Function} callback
545 * @param {Object} scope
546 * @api private
547 */
548
549SchemaType.prototype.doValidate = function (value, fn, scope) {
550 var err = false
551 , path = this.path
552 , count = this.validators.length;
553
554 if (!count) return fn(null);
555
556 function validate (ok, msg, val) {
557 if (err) return;
558 if (ok === undefined || ok) {
559 --count || fn(null);
560 } else {
561 fn(err = new ValidatorError(path, msg, val));
562 }
563 }
564
565 this.validators.forEach(function (v) {
566 var validator = v[0]
567 , message = v[1];
568
569 if (validator instanceof RegExp) {
570 validate(validator.test(value), message, value);
571 } else if ('function' === typeof validator) {
572 if (2 === validator.length) {
573 validator.call(scope, value, function (ok) {
574 validate(ok, message, value);
575 });
576 } else {
577 validate(validator.call(scope, value), message, value);
578 }
579 }
580 });
581};
582
583/**
584 * Determines if value is a valid Reference.
585 *
586 * @param {SchemaType} self
587 * @param {Object} value
588 * @param {Document} doc
589 * @param {Boolean} init
590 * @return {Boolean}
591 * @api private
592 */
593
594SchemaType._isRef = function (self, value, doc, init) {
595 // fast path
596 var ref = init && self.options && self.options.ref;
597
598 if (!ref && doc && doc.$__fullPath) {
599 // checks for
600 // - this populated with adhoc model and no ref was set in schema OR
601 // - setting / pushing values after population
602 var path = doc.$__fullPath(self.path);
603 var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
604 ref = owner.populated(path);
605 }
606
607 if (ref) {
608 if (null == value) return true;
609 if (!Buffer.isBuffer(value) && // buffers are objects too
610 'Binary' != value._bsontype // raw binary value from the db
611 && utils.isObject(value) // might have deselected _id in population query
612 ) {
613 return true;
614 }
615 }
616
617 return false;
618}
619
620/*!
621 * Module exports.
622 */
623
624module.exports = exports = SchemaType;
625
626exports.CastError = CastError;
627
628exports.ValidatorError = ValidatorError;