arnaucube
arnaucube/thoughtsPublic Self-hosted repository
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;