schema.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775
  1. /*!
  2. * Module dependencies.
  3. */
  4. var readPref = require('./drivers').ReadPreference;
  5. var EventEmitter = require('events').EventEmitter;
  6. var VirtualType = require('./virtualtype');
  7. var utils = require('./utils');
  8. var MongooseTypes;
  9. var Kareem = require('kareem');
  10. var each = require('async/each');
  11. var SchemaType = require('./schematype');
  12. var IS_KAREEM_HOOK = {
  13. count: true,
  14. find: true,
  15. findOne: true,
  16. findOneAndUpdate: true,
  17. findOneAndRemove: true,
  18. insertMany: true,
  19. update: true
  20. };
  21. /**
  22. * Schema constructor.
  23. *
  24. * ####Example:
  25. *
  26. * var child = new Schema({ name: String });
  27. * var schema = new Schema({ name: String, age: Number, children: [child] });
  28. * var Tree = mongoose.model('Tree', schema);
  29. *
  30. * // setting schema options
  31. * new Schema({ name: String }, { _id: false, autoIndex: false })
  32. *
  33. * ####Options:
  34. *
  35. * - [autoIndex](/docs/guide.html#autoIndex): bool - defaults to null (which means use the connection's autoIndex option)
  36. * - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true
  37. * - [capped](/docs/guide.html#capped): bool - defaults to false
  38. * - [collection](/docs/guide.html#collection): string - no default
  39. * - [emitIndexErrors](/docs/guide.html#emitIndexErrors): bool - defaults to false.
  40. * - [id](/docs/guide.html#id): bool - defaults to true
  41. * - [_id](/docs/guide.html#_id): bool - defaults to true
  42. * - `minimize`: bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true
  43. * - [read](/docs/guide.html#read): string
  44. * - [safe](/docs/guide.html#safe): bool - defaults to true.
  45. * - [shardKey](/docs/guide.html#shardKey): bool - defaults to `null`
  46. * - [strict](/docs/guide.html#strict): bool - defaults to true
  47. * - [toJSON](/docs/guide.html#toJSON) - object - no default
  48. * - [toObject](/docs/guide.html#toObject) - object - no default
  49. * - [typeKey](/docs/guide.html#typeKey) - string - defaults to 'type'
  50. * - [useNestedStrict](/docs/guide.html#useNestedStrict) - boolean - defaults to false
  51. * - [validateBeforeSave](/docs/guide.html#validateBeforeSave) - bool - defaults to `true`
  52. * - [versionKey](/docs/guide.html#versionKey): string - defaults to "__v"
  53. *
  54. * ####Note:
  55. *
  56. * _When nesting schemas, (`children` in the example above), always declare the child schema first before passing it into its parent._
  57. *
  58. * @param {Object} definition
  59. * @param {Object} [options]
  60. * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
  61. * @event `init`: Emitted after the schema is compiled into a `Model`.
  62. * @api public
  63. */
  64. function Schema(obj, options) {
  65. if (!(this instanceof Schema)) {
  66. return new Schema(obj, options);
  67. }
  68. this.obj = obj;
  69. this.paths = {};
  70. this.subpaths = {};
  71. this.virtuals = {};
  72. this.singleNestedPaths = {};
  73. this.nested = {};
  74. this.inherits = {};
  75. this.callQueue = [];
  76. this._indexes = [];
  77. this.methods = {};
  78. this.statics = {};
  79. this.tree = {};
  80. this._requiredpaths = undefined;
  81. this.discriminatorMapping = undefined;
  82. this._indexedpaths = undefined;
  83. this.query = {};
  84. this.childSchemas = [];
  85. this.s = {
  86. hooks: new Kareem(),
  87. kareemHooks: IS_KAREEM_HOOK
  88. };
  89. this.options = this.defaultOptions(options);
  90. // build paths
  91. if (obj) {
  92. this.add(obj);
  93. }
  94. // check if _id's value is a subdocument (gh-2276)
  95. var _idSubDoc = obj && obj._id && utils.isObject(obj._id);
  96. // ensure the documents get an auto _id unless disabled
  97. var auto_id = !this.paths['_id'] &&
  98. (!this.options.noId && this.options._id) && !_idSubDoc;
  99. if (auto_id) {
  100. obj = {_id: {auto: true}};
  101. obj._id[this.options.typeKey] = Schema.ObjectId;
  102. this.add(obj);
  103. }
  104. // ensure the documents receive an id getter unless disabled
  105. var autoid = !this.paths['id'] &&
  106. (!this.options.noVirtualId && this.options.id);
  107. if (autoid) {
  108. this.virtual('id').get(idGetter);
  109. }
  110. for (var i = 0; i < this._defaultMiddleware.length; ++i) {
  111. var m = this._defaultMiddleware[i];
  112. this[m.kind](m.hook, !!m.isAsync, m.fn);
  113. }
  114. if (this.options.timestamps) {
  115. this.setupTimestamp(this.options.timestamps);
  116. }
  117. }
  118. /*!
  119. * Returns this documents _id cast to a string.
  120. */
  121. function idGetter() {
  122. if (this.$__._id) {
  123. return this.$__._id;
  124. }
  125. this.$__._id = this._id == null
  126. ? null
  127. : String(this._id);
  128. return this.$__._id;
  129. }
  130. /*!
  131. * Inherit from EventEmitter.
  132. */
  133. Schema.prototype = Object.create(EventEmitter.prototype);
  134. Schema.prototype.constructor = Schema;
  135. Schema.prototype.instanceOfSchema = true;
  136. /**
  137. * Default middleware attached to a schema. Cannot be changed.
  138. *
  139. * This field is used to make sure discriminators don't get multiple copies of
  140. * built-in middleware. Declared as a constant because changing this at runtime
  141. * may lead to instability with Model.prototype.discriminator().
  142. *
  143. * @api private
  144. * @property _defaultMiddleware
  145. */
  146. Object.defineProperty(Schema.prototype, '_defaultMiddleware', {
  147. configurable: false,
  148. enumerable: false,
  149. writable: false,
  150. value: [
  151. {
  152. kind: 'pre',
  153. hook: 'save',
  154. fn: function(next, options) {
  155. var _this = this;
  156. // Nested docs have their own presave
  157. if (this.ownerDocument) {
  158. return next();
  159. }
  160. var hasValidateBeforeSaveOption = options &&
  161. (typeof options === 'object') &&
  162. ('validateBeforeSave' in options);
  163. var shouldValidate;
  164. if (hasValidateBeforeSaveOption) {
  165. shouldValidate = !!options.validateBeforeSave;
  166. } else {
  167. shouldValidate = this.schema.options.validateBeforeSave;
  168. }
  169. // Validate
  170. if (shouldValidate) {
  171. // HACK: use $__original_validate to avoid promises so bluebird doesn't
  172. // complain
  173. if (this.$__original_validate) {
  174. this.$__original_validate({__noPromise: true}, function(error) {
  175. return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) {
  176. next(error);
  177. });
  178. });
  179. } else {
  180. this.validate({__noPromise: true}, function(error) {
  181. return _this.schema.s.hooks.execPost('save:error', _this, [ _this], { error: error }, function(error) {
  182. next(error);
  183. });
  184. });
  185. }
  186. } else {
  187. next();
  188. }
  189. }
  190. },
  191. {
  192. kind: 'pre',
  193. hook: 'save',
  194. isAsync: true,
  195. fn: function(next, done) {
  196. var _this = this;
  197. var subdocs = this.$__getAllSubdocs();
  198. if (!subdocs.length || this.$__preSavingFromParent) {
  199. done();
  200. next();
  201. return;
  202. }
  203. each(subdocs, function(subdoc, cb) {
  204. subdoc.$__preSavingFromParent = true;
  205. subdoc.save(function(err) {
  206. cb(err);
  207. });
  208. }, function(error) {
  209. for (var i = 0; i < subdocs.length; ++i) {
  210. delete subdocs[i].$__preSavingFromParent;
  211. }
  212. if (error) {
  213. return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) {
  214. done(error);
  215. });
  216. }
  217. next();
  218. done();
  219. });
  220. }
  221. },
  222. {
  223. kind: 'pre',
  224. hook: 'validate',
  225. isAsync: true,
  226. fn: function(next, done) {
  227. // Hack to ensure that we always wrap validate() in a promise
  228. next();
  229. done();
  230. }
  231. },
  232. {
  233. kind: 'pre',
  234. hook: 'remove',
  235. isAsync: true,
  236. fn: function(next, done) {
  237. if (this.ownerDocument) {
  238. done();
  239. next();
  240. return;
  241. }
  242. var subdocs = this.$__getAllSubdocs();
  243. if (!subdocs.length || this.$__preSavingFromParent) {
  244. done();
  245. next();
  246. return;
  247. }
  248. each(subdocs, function(subdoc, cb) {
  249. subdoc.remove({ noop: true }, function(err) {
  250. cb(err);
  251. });
  252. }, function(error) {
  253. if (error) {
  254. done(error);
  255. return;
  256. }
  257. next();
  258. done();
  259. });
  260. }
  261. }
  262. ]
  263. });
  264. /**
  265. * The original object passed to the schema constructor
  266. *
  267. * ####Example:
  268. *
  269. * var schema = new Schema({ a: String }).add({ b: String });
  270. * schema.obj; // { a: String }
  271. *
  272. * @api public
  273. * @property obj
  274. */
  275. Schema.prototype.obj;
  276. /**
  277. * Schema as flat paths
  278. *
  279. * ####Example:
  280. * {
  281. * '_id' : SchemaType,
  282. * , 'nested.key' : SchemaType,
  283. * }
  284. *
  285. * @api private
  286. * @property paths
  287. */
  288. Schema.prototype.paths;
  289. /**
  290. * Schema as a tree
  291. *
  292. * ####Example:
  293. * {
  294. * '_id' : ObjectId
  295. * , 'nested' : {
  296. * 'key' : String
  297. * }
  298. * }
  299. *
  300. * @api private
  301. * @property tree
  302. */
  303. Schema.prototype.tree;
  304. /**
  305. * Returns default options for this schema, merged with `options`.
  306. *
  307. * @param {Object} options
  308. * @return {Object}
  309. * @api private
  310. */
  311. Schema.prototype.defaultOptions = function(options) {
  312. if (options && options.safe === false) {
  313. options.safe = {w: 0};
  314. }
  315. if (options && options.safe && options.safe.w === 0) {
  316. // if you turn off safe writes, then versioning goes off as well
  317. options.versionKey = false;
  318. }
  319. options = utils.options({
  320. strict: true,
  321. bufferCommands: true,
  322. capped: false, // { size, max, autoIndexId }
  323. versionKey: '__v',
  324. discriminatorKey: '__t',
  325. minimize: true,
  326. autoIndex: null,
  327. shardKey: null,
  328. read: null,
  329. validateBeforeSave: true,
  330. // the following are only applied at construction time
  331. noId: false, // deprecated, use { _id: false }
  332. _id: true,
  333. noVirtualId: false, // deprecated, use { id: false }
  334. id: true,
  335. typeKey: 'type',
  336. retainKeyOrder: false
  337. }, options);
  338. if (options.read) {
  339. options.read = readPref(options.read);
  340. }
  341. return options;
  342. };
  343. /**
  344. * Adds key path / schema type pairs to this schema.
  345. *
  346. * ####Example:
  347. *
  348. * var ToySchema = new Schema;
  349. * ToySchema.add({ name: 'string', color: 'string', price: 'number' });
  350. *
  351. * @param {Object} obj
  352. * @param {String} prefix
  353. * @api public
  354. */
  355. Schema.prototype.add = function add(obj, prefix) {
  356. prefix = prefix || '';
  357. var keys = Object.keys(obj);
  358. for (var i = 0; i < keys.length; ++i) {
  359. var key = keys[i];
  360. if (obj[key] == null) {
  361. throw new TypeError('Invalid value for schema path `' + prefix + key + '`');
  362. }
  363. if (Array.isArray(obj[key]) && obj[key].length === 1 && obj[key][0] == null) {
  364. throw new TypeError('Invalid value for schema Array path `' + prefix + key + '`');
  365. }
  366. if (utils.isObject(obj[key]) &&
  367. (!obj[key].constructor || utils.getFunctionName(obj[key].constructor) === 'Object') &&
  368. (!obj[key][this.options.typeKey] || (this.options.typeKey === 'type' && obj[key].type.type))) {
  369. if (Object.keys(obj[key]).length) {
  370. // nested object { last: { name: String }}
  371. this.nested[prefix + key] = true;
  372. this.add(obj[key], prefix + key + '.');
  373. } else {
  374. if (prefix) {
  375. this.nested[prefix.substr(0, prefix.length - 1)] = true;
  376. }
  377. this.path(prefix + key, obj[key]); // mixed type
  378. }
  379. } else {
  380. if (prefix) {
  381. this.nested[prefix.substr(0, prefix.length - 1)] = true;
  382. }
  383. this.path(prefix + key, obj[key]);
  384. }
  385. }
  386. };
  387. /**
  388. * Reserved document keys.
  389. *
  390. * 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.
  391. *
  392. * on, emit, _events, db, get, set, init, isNew, errors, schema, options, modelName, collection, _pres, _posts, toObject
  393. *
  394. * _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.
  395. *
  396. * var schema = new Schema(..);
  397. * schema.methods.init = function () {} // potentially breaking
  398. */
  399. Schema.reserved = Object.create(null);
  400. var reserved = Schema.reserved;
  401. // Core object
  402. reserved['prototype'] =
  403. // EventEmitter
  404. reserved.emit =
  405. reserved.on =
  406. reserved.once =
  407. reserved.listeners =
  408. reserved.removeListener =
  409. // document properties and functions
  410. reserved.collection =
  411. reserved.db =
  412. reserved.errors =
  413. reserved.init =
  414. reserved.isModified =
  415. reserved.isNew =
  416. reserved.get =
  417. reserved.modelName =
  418. reserved.save =
  419. reserved.schema =
  420. reserved.set =
  421. reserved.toObject =
  422. reserved.validate =
  423. // hooks.js
  424. reserved._pres = reserved._posts = 1;
  425. /*!
  426. * Document keys to print warnings for
  427. */
  428. var warnings = {};
  429. warnings.increment = '`increment` should not be used as a schema path name ' +
  430. 'unless you have disabled versioning.';
  431. /**
  432. * Gets/sets schema paths.
  433. *
  434. * Sets a path (if arity 2)
  435. * Gets a path (if arity 1)
  436. *
  437. * ####Example
  438. *
  439. * schema.path('name') // returns a SchemaType
  440. * schema.path('name', Number) // changes the schemaType of `name` to Number
  441. *
  442. * @param {String} path
  443. * @param {Object} constructor
  444. * @api public
  445. */
  446. Schema.prototype.path = function(path, obj) {
  447. if (obj === undefined) {
  448. if (this.paths[path]) {
  449. return this.paths[path];
  450. }
  451. if (this.subpaths[path]) {
  452. return this.subpaths[path];
  453. }
  454. if (this.singleNestedPaths[path]) {
  455. return this.singleNestedPaths[path];
  456. }
  457. // subpaths?
  458. return /\.\d+\.?.*$/.test(path)
  459. ? getPositionalPath(this, path)
  460. : undefined;
  461. }
  462. // some path names conflict with document methods
  463. if (reserved[path]) {
  464. throw new Error('`' + path + '` may not be used as a schema pathname');
  465. }
  466. if (warnings[path]) {
  467. console.log('WARN: ' + warnings[path]);
  468. }
  469. // update the tree
  470. var subpaths = path.split(/\./),
  471. last = subpaths.pop(),
  472. branch = this.tree;
  473. subpaths.forEach(function(sub, i) {
  474. if (!branch[sub]) {
  475. branch[sub] = {};
  476. }
  477. if (typeof branch[sub] !== 'object') {
  478. var msg = 'Cannot set nested path `' + path + '`. '
  479. + 'Parent path `'
  480. + subpaths.slice(0, i).concat([sub]).join('.')
  481. + '` already set to type ' + branch[sub].name
  482. + '.';
  483. throw new Error(msg);
  484. }
  485. branch = branch[sub];
  486. });
  487. branch[last] = utils.clone(obj);
  488. this.paths[path] = Schema.interpretAsType(path, obj, this.options);
  489. if (this.paths[path].$isSingleNested) {
  490. for (var key in this.paths[path].schema.paths) {
  491. this.singleNestedPaths[path + '.' + key] =
  492. this.paths[path].schema.paths[key];
  493. }
  494. for (key in this.paths[path].schema.singleNestedPaths) {
  495. this.singleNestedPaths[path + '.' + key] =
  496. this.paths[path].schema.singleNestedPaths[key];
  497. }
  498. this.childSchemas.push(this.paths[path].schema);
  499. } else if (this.paths[path].$isMongooseDocumentArray) {
  500. this.childSchemas.push(this.paths[path].schema);
  501. }
  502. return this;
  503. };
  504. /**
  505. * Converts type arguments into Mongoose Types.
  506. *
  507. * @param {String} path
  508. * @param {Object} obj constructor
  509. * @api private
  510. */
  511. Schema.interpretAsType = function(path, obj, options) {
  512. if (obj.constructor) {
  513. var constructorName = utils.getFunctionName(obj.constructor);
  514. if (constructorName !== 'Object') {
  515. var oldObj = obj;
  516. obj = {};
  517. obj[options.typeKey] = oldObj;
  518. }
  519. }
  520. // Get the type making sure to allow keys named "type"
  521. // and default to mixed if not specified.
  522. // { type: { type: String, default: 'freshcut' } }
  523. var type = obj[options.typeKey] && (options.typeKey !== 'type' || !obj.type.type)
  524. ? obj[options.typeKey]
  525. : {};
  526. if (utils.getFunctionName(type.constructor) === 'Object' || type === 'mixed') {
  527. return new MongooseTypes.Mixed(path, obj);
  528. }
  529. if (Array.isArray(type) || Array === type || type === 'array') {
  530. // if it was specified through { type } look for `cast`
  531. var cast = (Array === type || type === 'array')
  532. ? obj.cast
  533. : type[0];
  534. if (cast && cast.instanceOfSchema) {
  535. return new MongooseTypes.DocumentArray(path, cast, obj);
  536. }
  537. if (Array.isArray(cast)) {
  538. return new MongooseTypes.Array(path, Schema.interpretAsType(path, cast, options), obj);
  539. }
  540. if (typeof cast === 'string') {
  541. cast = MongooseTypes[cast.charAt(0).toUpperCase() + cast.substring(1)];
  542. } else if (cast && (!cast[options.typeKey] || (options.typeKey === 'type' && cast.type.type))
  543. && utils.getFunctionName(cast.constructor) === 'Object') {
  544. if (Object.keys(cast).length) {
  545. // The `minimize` and `typeKey` options propagate to child schemas
  546. // declared inline, like `{ arr: [{ val: { $type: String } }] }`.
  547. // See gh-3560
  548. var childSchemaOptions = {minimize: options.minimize};
  549. if (options.typeKey) {
  550. childSchemaOptions.typeKey = options.typeKey;
  551. }
  552. var childSchema = new Schema(cast, childSchemaOptions);
  553. childSchema.$implicitlyCreated = true;
  554. return new MongooseTypes.DocumentArray(path, childSchema, obj);
  555. } else {
  556. // Special case: empty object becomes mixed
  557. return new MongooseTypes.Array(path, MongooseTypes.Mixed, obj);
  558. }
  559. }
  560. if (cast) {
  561. type = cast[options.typeKey] && (options.typeKey !== 'type' || !cast.type.type)
  562. ? cast[options.typeKey]
  563. : cast;
  564. name = typeof type === 'string'
  565. ? type
  566. : type.schemaName || utils.getFunctionName(type);
  567. if (!(name in MongooseTypes)) {
  568. throw new TypeError('Undefined type `' + name + '` at array `' + path +
  569. '`');
  570. }
  571. }
  572. return new MongooseTypes.Array(path, cast || MongooseTypes.Mixed, obj, options);
  573. }
  574. if (type && type.instanceOfSchema) {
  575. return new MongooseTypes.Embedded(type, path, obj);
  576. }
  577. var name;
  578. if (Buffer.isBuffer(type)) {
  579. name = 'Buffer';
  580. } else {
  581. name = typeof type === 'string'
  582. ? type
  583. // If not string, `type` is a function. Outside of IE, function.name
  584. // gives you the function name. In IE, you need to compute it
  585. : type.schemaName || utils.getFunctionName(type);
  586. }
  587. if (name) {
  588. name = name.charAt(0).toUpperCase() + name.substring(1);
  589. }
  590. if (undefined == MongooseTypes[name]) {
  591. throw new TypeError('Undefined type `' + name + '` at `' + path +
  592. '`\n Did you try nesting Schemas? ' +
  593. 'You can only nest using refs or arrays.');
  594. }
  595. return new MongooseTypes[name](path, obj);
  596. };
  597. /**
  598. * Iterates the schemas paths similar to Array#forEach.
  599. *
  600. * The callback is passed the pathname and schemaType as arguments on each iteration.
  601. *
  602. * @param {Function} fn callback function
  603. * @return {Schema} this
  604. * @api public
  605. */
  606. Schema.prototype.eachPath = function(fn) {
  607. var keys = Object.keys(this.paths),
  608. len = keys.length;
  609. for (var i = 0; i < len; ++i) {
  610. fn(keys[i], this.paths[keys[i]]);
  611. }
  612. return this;
  613. };
  614. /**
  615. * Returns an Array of path strings that are required by this schema.
  616. *
  617. * @api public
  618. * @param {Boolean} invalidate refresh the cache
  619. * @return {Array}
  620. */
  621. Schema.prototype.requiredPaths = function requiredPaths(invalidate) {
  622. if (this._requiredpaths && !invalidate) {
  623. return this._requiredpaths;
  624. }
  625. var paths = Object.keys(this.paths),
  626. i = paths.length,
  627. ret = [];
  628. while (i--) {
  629. var path = paths[i];
  630. if (this.paths[path].isRequired) {
  631. ret.push(path);
  632. }
  633. }
  634. this._requiredpaths = ret;
  635. return this._requiredpaths;
  636. };
  637. /**
  638. * Returns indexes from fields and schema-level indexes (cached).
  639. *
  640. * @api private
  641. * @return {Array}
  642. */
  643. Schema.prototype.indexedPaths = function indexedPaths() {
  644. if (this._indexedpaths) {
  645. return this._indexedpaths;
  646. }
  647. this._indexedpaths = this.indexes();
  648. return this._indexedpaths;
  649. };
  650. /**
  651. * Returns the pathType of `path` for this schema.
  652. *
  653. * Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.
  654. *
  655. * @param {String} path
  656. * @return {String}
  657. * @api public
  658. */
  659. Schema.prototype.pathType = function(path) {
  660. if (path in this.paths) {
  661. return 'real';
  662. }
  663. if (path in this.virtuals) {
  664. return 'virtual';
  665. }
  666. if (path in this.nested) {
  667. return 'nested';
  668. }
  669. if (path in this.subpaths) {
  670. return 'real';
  671. }
  672. if (path in this.singleNestedPaths) {
  673. return 'real';
  674. }
  675. if (/\.\d+\.|\.\d+$/.test(path)) {
  676. return getPositionalPathType(this, path);
  677. }
  678. return 'adhocOrUndefined';
  679. };
  680. /**
  681. * Returns true iff this path is a child of a mixed schema.
  682. *
  683. * @param {String} path
  684. * @return {Boolean}
  685. * @api private
  686. */
  687. Schema.prototype.hasMixedParent = function(path) {
  688. var subpaths = path.split(/\./g);
  689. path = '';
  690. for (var i = 0; i < subpaths.length; ++i) {
  691. path = i > 0 ? path + '.' + subpaths[i] : subpaths[i];
  692. if (path in this.paths &&
  693. this.paths[path] instanceof MongooseTypes.Mixed) {
  694. return true;
  695. }
  696. }
  697. return false;
  698. };
  699. /**
  700. * Setup updatedAt and createdAt timestamps to documents if enabled
  701. *
  702. * @param {Boolean|Object} timestamps timestamps options
  703. * @api private
  704. */
  705. Schema.prototype.setupTimestamp = function(timestamps) {
  706. if (timestamps) {
  707. var createdAt = timestamps.createdAt || 'createdAt';
  708. var updatedAt = timestamps.updatedAt || 'updatedAt';
  709. var schemaAdditions = {};
  710. schemaAdditions[updatedAt] = Date;
  711. if (!this.paths[createdAt]) {
  712. schemaAdditions[createdAt] = Date;
  713. }
  714. this.add(schemaAdditions);
  715. this.pre('save', function(next) {
  716. var defaultTimestamp = new Date();
  717. var auto_id = this._id && this._id.auto;
  718. if (!this[createdAt] && this.isSelected(createdAt)) {
  719. this[createdAt] = auto_id ? this._id.getTimestamp() : defaultTimestamp;
  720. }
  721. if (this.isNew || this.isModified()) {
  722. this[updatedAt] = this.isNew ? this[createdAt] : defaultTimestamp;
  723. }
  724. next();
  725. });
  726. var genUpdates = function(overwrite) {
  727. var now = new Date();
  728. var updates = {};
  729. if (overwrite) {
  730. updates[updatedAt] = now;
  731. updates[createdAt] = now;
  732. return updates;
  733. }
  734. updates = { $set: {}, $setOnInsert: {} };
  735. updates.$set[updatedAt] = now;
  736. updates.$setOnInsert[createdAt] = now;
  737. return updates;
  738. };
  739. this.methods.initializeTimestamps = function() {
  740. if (!this[createdAt]) {
  741. this[createdAt] = new Date();
  742. }
  743. if (!this[updatedAt]) {
  744. this[updatedAt] = new Date();
  745. }
  746. return this;
  747. };
  748. this.pre('findOneAndUpdate', function(next) {
  749. var overwrite = this.options.overwrite;
  750. this.findOneAndUpdate({}, genUpdates(overwrite), { overwrite: overwrite });
  751. applyTimestampsToChildren(this);
  752. next();
  753. });
  754. this.pre('update', function(next) {
  755. var overwrite = this.options.overwrite;
  756. this.update({}, genUpdates(overwrite), { overwrite: overwrite });
  757. applyTimestampsToChildren(this);
  758. next();
  759. });
  760. }
  761. };
  762. /*!
  763. * ignore
  764. */
  765. function applyTimestampsToChildren(query) {
  766. var now = new Date();
  767. var update = query.getUpdate();
  768. var keys = Object.keys(update);
  769. var key;
  770. var schema = query.model.schema;
  771. var len;
  772. var createdAt;
  773. var updatedAt;
  774. var timestamps;
  775. var path;
  776. var hasDollarKey = keys.length && keys[0].charAt(0) === '$';
  777. if (hasDollarKey) {
  778. if (update.$push) {
  779. for (key in update.$push) {
  780. var $path = schema.path(key);
  781. if (update.$push[key] &&
  782. $path &&
  783. $path.$isMongooseDocumentArray &&
  784. $path.schema.options.timestamps) {
  785. timestamps = $path.schema.options.timestamps;
  786. createdAt = timestamps.createdAt || 'createdAt';
  787. updatedAt = timestamps.updatedAt || 'updatedAt';
  788. if (update.$push[key].$each) {
  789. update.$push[key].$each.forEach(function(subdoc) {
  790. subdoc[updatedAt] = now;
  791. subdoc[createdAt] = now;
  792. });
  793. } else {
  794. update.$push[key][updatedAt] = now;
  795. update.$push[key][createdAt] = now;
  796. }
  797. }
  798. }
  799. }
  800. if (update.$set) {
  801. for (key in update.$set) {
  802. path = schema.path(key);
  803. if (!path) {
  804. continue;
  805. }
  806. if (Array.isArray(update.$set[key]) && path.$isMongooseDocumentArray) {
  807. len = update.$set[key].length;
  808. timestamps = schema.path(key).schema.options.timestamps;
  809. if (timestamps) {
  810. createdAt = timestamps.createdAt || 'createdAt';
  811. updatedAt = timestamps.updatedAt || 'updatedAt';
  812. for (var i = 0; i < len; ++i) {
  813. update.$set[key][i][updatedAt] = now;
  814. update.$set[key][i][createdAt] = now;
  815. }
  816. }
  817. } else if (update.$set[key] && path.$isSingleNested) {
  818. timestamps = schema.path(key).schema.options.timestamps;
  819. if (timestamps) {
  820. createdAt = timestamps.createdAt || 'createdAt';
  821. updatedAt = timestamps.updatedAt || 'updatedAt';
  822. update.$set[key][updatedAt] = now;
  823. update.$set[key][createdAt] = now;
  824. }
  825. }
  826. }
  827. }
  828. }
  829. }
  830. /*!
  831. * ignore
  832. */
  833. function getPositionalPathType(self, path) {
  834. var subpaths = path.split(/\.(\d+)\.|\.(\d+)$/).filter(Boolean);
  835. if (subpaths.length < 2) {
  836. return self.paths[subpaths[0]];
  837. }
  838. var val = self.path(subpaths[0]);
  839. var isNested = false;
  840. if (!val) {
  841. return val;
  842. }
  843. var last = subpaths.length - 1,
  844. subpath,
  845. i = 1;
  846. for (; i < subpaths.length; ++i) {
  847. isNested = false;
  848. subpath = subpaths[i];
  849. if (i === last && val && !/\D/.test(subpath)) {
  850. if (val.$isMongooseDocumentArray) {
  851. var oldVal = val;
  852. val = new SchemaType(subpath);
  853. val.cast = function(value, doc, init) {
  854. return oldVal.cast(value, doc, init)[0];
  855. };
  856. val.caster = oldVal.caster;
  857. val.schema = oldVal.schema;
  858. } else if (val instanceof MongooseTypes.Array) {
  859. // StringSchema, NumberSchema, etc
  860. val = val.caster;
  861. } else {
  862. val = undefined;
  863. }
  864. break;
  865. }
  866. // ignore if its just a position segment: path.0.subpath
  867. if (!/\D/.test(subpath)) {
  868. continue;
  869. }
  870. if (!(val && val.schema)) {
  871. val = undefined;
  872. break;
  873. }
  874. var type = val.schema.pathType(subpath);
  875. isNested = (type === 'nested');
  876. val = val.schema.path(subpath);
  877. }
  878. self.subpaths[path] = val;
  879. if (val) {
  880. return 'real';
  881. }
  882. if (isNested) {
  883. return 'nested';
  884. }
  885. return 'adhocOrUndefined';
  886. }
  887. /*!
  888. * ignore
  889. */
  890. function getPositionalPath(self, path) {
  891. getPositionalPathType(self, path);
  892. return self.subpaths[path];
  893. }
  894. /**
  895. * Adds a method call to the queue.
  896. *
  897. * @param {String} name name of the document method to call later
  898. * @param {Array} args arguments to pass to the method
  899. * @api public
  900. */
  901. Schema.prototype.queue = function(name, args) {
  902. this.callQueue.push([name, args]);
  903. return this;
  904. };
  905. /**
  906. * Defines a pre hook for the document.
  907. *
  908. * ####Example
  909. *
  910. * var toySchema = new Schema(..);
  911. *
  912. * toySchema.pre('save', function (next) {
  913. * if (!this.created) this.created = new Date;
  914. * next();
  915. * })
  916. *
  917. * toySchema.pre('validate', function (next) {
  918. * if (this.name !== 'Woody') this.name = 'Woody';
  919. * next();
  920. * })
  921. *
  922. * @param {String} method
  923. * @param {Function} callback
  924. * @see hooks.js https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3
  925. * @api public
  926. */
  927. Schema.prototype.pre = function() {
  928. var name = arguments[0];
  929. if (IS_KAREEM_HOOK[name]) {
  930. this.s.hooks.pre.apply(this.s.hooks, arguments);
  931. return this;
  932. }
  933. return this.queue('pre', arguments);
  934. };
  935. /**
  936. * Defines a post hook for the document
  937. *
  938. * var schema = new Schema(..);
  939. * schema.post('save', function (doc) {
  940. * console.log('this fired after a document was saved');
  941. * });
  942. *
  943. * shema.post('find', function(docs) {
  944. * console.log('this fired after you run a find query');
  945. * });
  946. *
  947. * var Model = mongoose.model('Model', schema);
  948. *
  949. * var m = new Model(..);
  950. * m.save(function(err) {
  951. * console.log('this fires after the `post` hook');
  952. * });
  953. *
  954. * m.find(function(err, docs) {
  955. * console.log('this fires after the post find hook');
  956. * });
  957. *
  958. * @param {String} method name of the method to hook
  959. * @param {Function} fn callback
  960. * @see middleware http://mongoosejs.com/docs/middleware.html
  961. * @see hooks.js https://www.npmjs.com/package/hooks-fixed
  962. * @see kareem http://npmjs.org/package/kareem
  963. * @api public
  964. */
  965. Schema.prototype.post = function(method, fn) {
  966. if (IS_KAREEM_HOOK[method]) {
  967. this.s.hooks.post.apply(this.s.hooks, arguments);
  968. return this;
  969. }
  970. // assuming that all callbacks with arity < 2 are synchronous post hooks
  971. if (fn.length < 2) {
  972. return this.queue('on', [arguments[0], function(doc) {
  973. return fn.call(doc, doc);
  974. }]);
  975. }
  976. if (fn.length === 3) {
  977. this.s.hooks.post(method + ':error', fn);
  978. return this;
  979. }
  980. return this.queue('post', [arguments[0], function(next) {
  981. // wrap original function so that the callback goes last,
  982. // for compatibility with old code that is using synchronous post hooks
  983. var _this = this;
  984. var args = Array.prototype.slice.call(arguments, 1);
  985. fn.call(this, this, function(err) {
  986. return next.apply(_this, [err].concat(args));
  987. });
  988. }]);
  989. };
  990. /**
  991. * Registers a plugin for this schema.
  992. *
  993. * @param {Function} plugin callback
  994. * @param {Object} [opts]
  995. * @see plugins
  996. * @api public
  997. */
  998. Schema.prototype.plugin = function(fn, opts) {
  999. fn(this, opts);
  1000. return this;
  1001. };
  1002. /**
  1003. * Adds an instance method to documents constructed from Models compiled from this schema.
  1004. *
  1005. * ####Example
  1006. *
  1007. * var schema = kittySchema = new Schema(..);
  1008. *
  1009. * schema.method('meow', function () {
  1010. * console.log('meeeeeoooooooooooow');
  1011. * })
  1012. *
  1013. * var Kitty = mongoose.model('Kitty', schema);
  1014. *
  1015. * var fizz = new Kitty;
  1016. * fizz.meow(); // meeeeeooooooooooooow
  1017. *
  1018. * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
  1019. *
  1020. * schema.method({
  1021. * purr: function () {}
  1022. * , scratch: function () {}
  1023. * });
  1024. *
  1025. * // later
  1026. * fizz.purr();
  1027. * fizz.scratch();
  1028. *
  1029. * @param {String|Object} method name
  1030. * @param {Function} [fn]
  1031. * @api public
  1032. */
  1033. Schema.prototype.method = function(name, fn) {
  1034. if (typeof name !== 'string') {
  1035. for (var i in name) {
  1036. this.methods[i] = name[i];
  1037. }
  1038. } else {
  1039. this.methods[name] = fn;
  1040. }
  1041. return this;
  1042. };
  1043. /**
  1044. * Adds static "class" methods to Models compiled from this schema.
  1045. *
  1046. * ####Example
  1047. *
  1048. * var schema = new Schema(..);
  1049. * schema.static('findByName', function (name, callback) {
  1050. * return this.find({ name: name }, callback);
  1051. * });
  1052. *
  1053. * var Drink = mongoose.model('Drink', schema);
  1054. * Drink.findByName('sanpellegrino', function (err, drinks) {
  1055. * //
  1056. * });
  1057. *
  1058. * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
  1059. *
  1060. * @param {String|Object} name
  1061. * @param {Function} [fn]
  1062. * @api public
  1063. */
  1064. Schema.prototype.static = function(name, fn) {
  1065. if (typeof name !== 'string') {
  1066. for (var i in name) {
  1067. this.statics[i] = name[i];
  1068. }
  1069. } else {
  1070. this.statics[name] = fn;
  1071. }
  1072. return this;
  1073. };
  1074. /**
  1075. * Defines an index (most likely compound) for this schema.
  1076. *
  1077. * ####Example
  1078. *
  1079. * schema.index({ first: 1, last: -1 })
  1080. *
  1081. * @param {Object} fields
  1082. * @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)
  1083. * @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.
  1084. * @api public
  1085. */
  1086. Schema.prototype.index = function(fields, options) {
  1087. options || (options = {});
  1088. if (options.expires) {
  1089. utils.expires(options);
  1090. }
  1091. this._indexes.push([fields, options]);
  1092. return this;
  1093. };
  1094. /**
  1095. * Sets/gets a schema option.
  1096. *
  1097. * ####Example
  1098. *
  1099. * schema.set('strict'); // 'true' by default
  1100. * schema.set('strict', false); // Sets 'strict' to false
  1101. * schema.set('strict'); // 'false'
  1102. *
  1103. * @param {String} key option name
  1104. * @param {Object} [value] if not passed, the current option value is returned
  1105. * @see Schema ./
  1106. * @api public
  1107. */
  1108. Schema.prototype.set = function(key, value, _tags) {
  1109. if (arguments.length === 1) {
  1110. return this.options[key];
  1111. }
  1112. switch (key) {
  1113. case 'read':
  1114. this.options[key] = readPref(value, _tags);
  1115. break;
  1116. case 'safe':
  1117. this.options[key] = value === false
  1118. ? {w: 0}
  1119. : value;
  1120. break;
  1121. case 'timestamps':
  1122. this.setupTimestamp(value);
  1123. this.options[key] = value;
  1124. break;
  1125. default:
  1126. this.options[key] = value;
  1127. }
  1128. return this;
  1129. };
  1130. /**
  1131. * Gets a schema option.
  1132. *
  1133. * @param {String} key option name
  1134. * @api public
  1135. */
  1136. Schema.prototype.get = function(key) {
  1137. return this.options[key];
  1138. };
  1139. /**
  1140. * The allowed index types
  1141. *
  1142. * @static indexTypes
  1143. * @receiver Schema
  1144. * @api public
  1145. */
  1146. var indexTypes = '2d 2dsphere hashed text'.split(' ');
  1147. Object.defineProperty(Schema, 'indexTypes', {
  1148. get: function() {
  1149. return indexTypes;
  1150. },
  1151. set: function() {
  1152. throw new Error('Cannot overwrite Schema.indexTypes');
  1153. }
  1154. });
  1155. /**
  1156. * Compiles indexes from fields and schema-level indexes
  1157. *
  1158. * @api public
  1159. */
  1160. Schema.prototype.indexes = function() {
  1161. 'use strict';
  1162. var indexes = [];
  1163. var seenPrefix = {};
  1164. var collectIndexes = function(schema, prefix) {
  1165. if (seenPrefix[prefix]) {
  1166. return;
  1167. }
  1168. seenPrefix[prefix] = true;
  1169. prefix = prefix || '';
  1170. var key, path, index, field, isObject, options, type;
  1171. var keys = Object.keys(schema.paths);
  1172. for (var i = 0; i < keys.length; ++i) {
  1173. key = keys[i];
  1174. path = schema.paths[key];
  1175. if ((path instanceof MongooseTypes.DocumentArray) || path.$isSingleNested) {
  1176. collectIndexes(path.schema, key + '.');
  1177. } else {
  1178. index = path._index;
  1179. if (index !== false && index !== null && index !== undefined) {
  1180. field = {};
  1181. isObject = utils.isObject(index);
  1182. options = isObject ? index : {};
  1183. type = typeof index === 'string' ? index :
  1184. isObject ? index.type :
  1185. false;
  1186. if (type && ~Schema.indexTypes.indexOf(type)) {
  1187. field[prefix + key] = type;
  1188. } else if (options.text) {
  1189. field[prefix + key] = 'text';
  1190. delete options.text;
  1191. } else {
  1192. field[prefix + key] = 1;
  1193. }
  1194. delete options.type;
  1195. if (!('background' in options)) {
  1196. options.background = true;
  1197. }
  1198. indexes.push([field, options]);
  1199. }
  1200. }
  1201. }
  1202. if (prefix) {
  1203. fixSubIndexPaths(schema, prefix);
  1204. } else {
  1205. schema._indexes.forEach(function(index) {
  1206. if (!('background' in index[1])) {
  1207. index[1].background = true;
  1208. }
  1209. });
  1210. indexes = indexes.concat(schema._indexes);
  1211. }
  1212. };
  1213. collectIndexes(this);
  1214. return indexes;
  1215. /*!
  1216. * Checks for indexes added to subdocs using Schema.index().
  1217. * These indexes need their paths prefixed properly.
  1218. *
  1219. * schema._indexes = [ [indexObj, options], [indexObj, options] ..]
  1220. */
  1221. function fixSubIndexPaths(schema, prefix) {
  1222. var subindexes = schema._indexes,
  1223. len = subindexes.length,
  1224. indexObj,
  1225. newindex,
  1226. klen,
  1227. keys,
  1228. key,
  1229. i = 0,
  1230. j;
  1231. for (i = 0; i < len; ++i) {
  1232. indexObj = subindexes[i][0];
  1233. keys = Object.keys(indexObj);
  1234. klen = keys.length;
  1235. newindex = {};
  1236. // use forward iteration, order matters
  1237. for (j = 0; j < klen; ++j) {
  1238. key = keys[j];
  1239. newindex[prefix + key] = indexObj[key];
  1240. }
  1241. indexes.push([newindex, subindexes[i][1]]);
  1242. }
  1243. }
  1244. };
  1245. /**
  1246. * Creates a virtual type with the given name.
  1247. *
  1248. * @param {String} name
  1249. * @param {Object} [options]
  1250. * @return {VirtualType}
  1251. */
  1252. Schema.prototype.virtual = function(name, options) {
  1253. if (options && options.ref) {
  1254. if (!options.localField) {
  1255. throw new Error('Reference virtuals require `localField` option');
  1256. }
  1257. if (!options.foreignField) {
  1258. throw new Error('Reference virtuals require `foreignField` option');
  1259. }
  1260. this.pre('init', function(next, obj) {
  1261. if (name in obj) {
  1262. if (!this.$$populatedVirtuals) {
  1263. this.$$populatedVirtuals = {};
  1264. }
  1265. if (options.justOne) {
  1266. this.$$populatedVirtuals[name] = Array.isArray(obj[name]) ?
  1267. obj[name][0] :
  1268. obj[name];
  1269. } else {
  1270. this.$$populatedVirtuals[name] = Array.isArray(obj[name]) ?
  1271. obj[name] :
  1272. obj[name] == null ? [] : [obj[name]];
  1273. }
  1274. delete obj[name];
  1275. }
  1276. if (this.ownerDocument) {
  1277. next();
  1278. return obj;
  1279. } else {
  1280. next();
  1281. }
  1282. });
  1283. var virtual = this.virtual(name);
  1284. virtual.options = options;
  1285. return virtual.
  1286. get(function() {
  1287. if (!this.$$populatedVirtuals) {
  1288. this.$$populatedVirtuals = {};
  1289. }
  1290. if (name in this.$$populatedVirtuals) {
  1291. return this.$$populatedVirtuals[name];
  1292. }
  1293. return null;
  1294. }).
  1295. set(function(v) {
  1296. if (!this.$$populatedVirtuals) {
  1297. this.$$populatedVirtuals = {};
  1298. }
  1299. this.$$populatedVirtuals[name] = v;
  1300. });
  1301. }
  1302. var virtuals = this.virtuals;
  1303. var parts = name.split('.');
  1304. if (this.pathType(name) === 'real') {
  1305. throw new Error('Virtual path "' + name + '"' +
  1306. ' conflicts with a real path in the schema');
  1307. }
  1308. virtuals[name] = parts.reduce(function(mem, part, i) {
  1309. mem[part] || (mem[part] = (i === parts.length - 1)
  1310. ? new VirtualType(options, name)
  1311. : {});
  1312. return mem[part];
  1313. }, this.tree);
  1314. return virtuals[name];
  1315. };
  1316. /*!
  1317. * ignore
  1318. */
  1319. Schema.prototype._getVirtual = function(name) {
  1320. return _getVirtual(this, name);
  1321. };
  1322. /*!
  1323. * ignore
  1324. */
  1325. function _getVirtual(schema, name) {
  1326. var parts = name.split('.');
  1327. var cur = '';
  1328. var nestedSchemaPath = '';
  1329. for (var i = 0; i < parts.length; ++i) {
  1330. cur += (cur.length > 0 ? '.' : '') + parts[i];
  1331. if (schema.virtuals[cur]) {
  1332. if (i === parts.length - 1) {
  1333. schema.virtuals[cur].$nestedSchemaPath = nestedSchemaPath;
  1334. return schema.virtuals[cur];
  1335. }
  1336. continue;
  1337. } else if (schema.paths[cur] && schema.paths[cur].schema) {
  1338. schema = schema.paths[cur].schema;
  1339. nestedSchemaPath += (nestedSchemaPath.length > 0 ? '.' : '') + cur;
  1340. cur = '';
  1341. } else {
  1342. return null;
  1343. }
  1344. }
  1345. }
  1346. /**
  1347. * Returns the virtual type with the given `name`.
  1348. *
  1349. * @param {String} name
  1350. * @return {VirtualType}
  1351. */
  1352. Schema.prototype.virtualpath = function(name) {
  1353. return this.virtuals[name];
  1354. };
  1355. /**
  1356. * Removes the given `path` (or [`paths`]).
  1357. *
  1358. * @param {String|Array} path
  1359. *
  1360. * @api public
  1361. */
  1362. Schema.prototype.remove = function(path) {
  1363. if (typeof path === 'string') {
  1364. path = [path];
  1365. }
  1366. if (Array.isArray(path)) {
  1367. path.forEach(function(name) {
  1368. if (this.path(name)) {
  1369. delete this.paths[name];
  1370. var pieces = name.split('.');
  1371. var last = pieces.pop();
  1372. var branch = this.tree;
  1373. for (var i = 0; i < pieces.length; ++i) {
  1374. branch = branch[pieces[i]];
  1375. }
  1376. delete branch[last];
  1377. }
  1378. }, this);
  1379. }
  1380. };
  1381. /**
  1382. * Loads an ES6 class into a schema. Maps setters + getters, static methods, and instance methods to schema virtuals, statics, and methods.
  1383. *
  1384. * @param {Function} model
  1385. */
  1386. Schema.prototype.loadClass = function(model, virtualsOnly) {
  1387. if (model === Object.prototype || model === Function.prototype) {
  1388. return this;
  1389. }
  1390. // Add static methods
  1391. if (!virtualsOnly) {
  1392. Object.getOwnPropertyNames(model).forEach(function(name) {
  1393. if (name.match(/^(length|name|prototype)$/)) {
  1394. return;
  1395. }
  1396. var method = Object.getOwnPropertyDescriptor(model, name);
  1397. if (typeof method.value === 'function') this.static(name, method.value);
  1398. }, this);
  1399. }
  1400. // Add methods and virtuals
  1401. Object.getOwnPropertyNames(model.prototype).forEach(function(name) {
  1402. if (name.match(/^(constructor)$/)) {
  1403. return;
  1404. }
  1405. var method = Object.getOwnPropertyDescriptor(model.prototype, name);
  1406. if (!virtualsOnly) {
  1407. if (typeof method.value === 'function') {
  1408. this.method(name, method.value);
  1409. }
  1410. }
  1411. if (typeof method.get === 'function') {
  1412. this.virtual(name).get(method.get);
  1413. }
  1414. if (typeof method.set === 'function') {
  1415. this.virtual(name).set(method.set);
  1416. }
  1417. }, this);
  1418. return (this.loadClass(Object.getPrototypeOf(model)));
  1419. };
  1420. /*!
  1421. * ignore
  1422. */
  1423. Schema.prototype._getSchema = function(path) {
  1424. var _this = this;
  1425. var pathschema = _this.path(path);
  1426. var resultPath = [];
  1427. if (pathschema) {
  1428. pathschema.$fullPath = path;
  1429. return pathschema;
  1430. }
  1431. function search(parts, schema) {
  1432. var p = parts.length + 1,
  1433. foundschema,
  1434. trypath;
  1435. while (p--) {
  1436. trypath = parts.slice(0, p).join('.');
  1437. foundschema = schema.path(trypath);
  1438. if (foundschema) {
  1439. resultPath.push(trypath);
  1440. if (foundschema.caster) {
  1441. // array of Mixed?
  1442. if (foundschema.caster instanceof MongooseTypes.Mixed) {
  1443. foundschema.caster.$fullPath = resultPath.join('.');
  1444. return foundschema.caster;
  1445. }
  1446. // Now that we found the array, we need to check if there
  1447. // are remaining document paths to look up for casting.
  1448. // Also we need to handle array.$.path since schema.path
  1449. // doesn't work for that.
  1450. // If there is no foundschema.schema we are dealing with
  1451. // a path like array.$
  1452. if (p !== parts.length && foundschema.schema) {
  1453. if (parts[p] === '$') {
  1454. // comments.$.comments.$.title
  1455. return search(parts.slice(p + 1), foundschema.schema);
  1456. }
  1457. // this is the last path of the selector
  1458. return search(parts.slice(p), foundschema.schema);
  1459. }
  1460. }
  1461. foundschema.$fullPath = resultPath.join('.');
  1462. return foundschema;
  1463. }
  1464. }
  1465. }
  1466. // look for arrays
  1467. return search(path.split('.'), _this);
  1468. };
  1469. /*!
  1470. * ignore
  1471. */
  1472. Schema.prototype._getPathType = function(path) {
  1473. var _this = this;
  1474. var pathschema = _this.path(path);
  1475. if (pathschema) {
  1476. return 'real';
  1477. }
  1478. function search(parts, schema) {
  1479. var p = parts.length + 1,
  1480. foundschema,
  1481. trypath;
  1482. while (p--) {
  1483. trypath = parts.slice(0, p).join('.');
  1484. foundschema = schema.path(trypath);
  1485. if (foundschema) {
  1486. if (foundschema.caster) {
  1487. // array of Mixed?
  1488. if (foundschema.caster instanceof MongooseTypes.Mixed) {
  1489. return { schema: foundschema, pathType: 'mixed' };
  1490. }
  1491. // Now that we found the array, we need to check if there
  1492. // are remaining document paths to look up for casting.
  1493. // Also we need to handle array.$.path since schema.path
  1494. // doesn't work for that.
  1495. // If there is no foundschema.schema we are dealing with
  1496. // a path like array.$
  1497. if (p !== parts.length && foundschema.schema) {
  1498. if (parts[p] === '$') {
  1499. if (p === parts.length - 1) {
  1500. return { schema: foundschema, pathType: 'nested' };
  1501. }
  1502. // comments.$.comments.$.title
  1503. return search(parts.slice(p + 1), foundschema.schema);
  1504. }
  1505. // this is the last path of the selector
  1506. return search(parts.slice(p), foundschema.schema);
  1507. }
  1508. return {
  1509. schema: foundschema,
  1510. pathType: foundschema.$isSingleNested ? 'nested' : 'array'
  1511. };
  1512. }
  1513. return { schema: foundschema, pathType: 'real' };
  1514. } else if (p === parts.length && schema.nested[trypath]) {
  1515. return { schema: schema, pathType: 'nested' };
  1516. }
  1517. }
  1518. return { schema: foundschema || schema, pathType: 'undefined' };
  1519. }
  1520. // look for arrays
  1521. return search(path.split('.'), _this);
  1522. };
  1523. /*!
  1524. * Module exports.
  1525. */
  1526. module.exports = exports = Schema;
  1527. // require down here because of reference issues
  1528. /**
  1529. * The various built-in Mongoose Schema Types.
  1530. *
  1531. * ####Example:
  1532. *
  1533. * var mongoose = require('mongoose');
  1534. * var ObjectId = mongoose.Schema.Types.ObjectId;
  1535. *
  1536. * ####Types:
  1537. *
  1538. * - [String](#schema-string-js)
  1539. * - [Number](#schema-number-js)
  1540. * - [Boolean](#schema-boolean-js) | Bool
  1541. * - [Array](#schema-array-js)
  1542. * - [Buffer](#schema-buffer-js)
  1543. * - [Date](#schema-date-js)
  1544. * - [ObjectId](#schema-objectid-js) | Oid
  1545. * - [Mixed](#schema-mixed-js)
  1546. *
  1547. * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema.
  1548. *
  1549. * var Mixed = mongoose.Schema.Types.Mixed;
  1550. * new mongoose.Schema({ _user: Mixed })
  1551. *
  1552. * @api public
  1553. */
  1554. Schema.Types = MongooseTypes = require('./schema/index');
  1555. /*!
  1556. * ignore
  1557. */
  1558. exports.ObjectId = MongooseTypes.ObjectId;