|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- /* eslint no-func-assign: 1 */
-
- /*!
- * Module dependencies.
- */
-
- 'use strict';
-
- const Document = require('../document_provider')();
- const EventEmitter = require('events').EventEmitter;
- const immediate = require('../helpers/immediate');
- const internalToObjectOptions = require('../options').internalToObjectOptions;
- const get = require('../helpers/get');
- const utils = require('../utils');
- const util = require('util');
-
- const documentArrayParent = require('../helpers/symbols').documentArrayParent;
- const validatorErrorSymbol = require('../helpers/symbols').validatorErrorSymbol;
-
- /**
- * EmbeddedDocument constructor.
- *
- * @param {Object} obj js object returned from the db
- * @param {MongooseDocumentArray} parentArr the parent array of this document
- * @param {Boolean} skipId
- * @inherits Document
- * @api private
- */
-
- function EmbeddedDocument(obj, parentArr, skipId, fields, index) {
- if (parentArr) {
- this.__parentArray = parentArr;
- this[documentArrayParent] = parentArr.$parent();
- } else {
- this.__parentArray = undefined;
- this[documentArrayParent] = undefined;
- }
- this.$setIndex(index);
- this.$isDocumentArrayElement = true;
-
- Document.call(this, obj, fields, skipId);
-
- const _this = this;
- this.on('isNew', function(val) {
- _this.isNew = val;
- });
-
- _this.on('save', function() {
- _this.constructor.emit('save', _this);
- });
- }
-
- /*!
- * Inherit from Document
- */
- EmbeddedDocument.prototype = Object.create(Document.prototype);
- EmbeddedDocument.prototype.constructor = EmbeddedDocument;
-
- for (const i in EventEmitter.prototype) {
- EmbeddedDocument[i] = EventEmitter.prototype[i];
- }
-
- EmbeddedDocument.prototype.toBSON = function() {
- return this.toObject(internalToObjectOptions);
- };
-
- /*!
- * ignore
- */
-
- EmbeddedDocument.prototype.$setIndex = function(index) {
- this.__index = index;
-
- if (get(this, '$__.validationError', null) != null) {
- const keys = Object.keys(this.$__.validationError.errors);
- for (const key of keys) {
- this.invalidate(key, this.$__.validationError.errors[key]);
- }
- }
- };
-
- /**
- * Marks the embedded doc modified.
- *
- * ####Example:
- *
- * var doc = blogpost.comments.id(hexstring);
- * doc.mixed.type = 'changed';
- * doc.markModified('mixed.type');
- *
- * @param {String} path the path which changed
- * @api public
- * @receiver EmbeddedDocument
- */
-
- EmbeddedDocument.prototype.markModified = function(path) {
- this.$__.activePaths.modify(path);
- if (!this.__parentArray) {
- return;
- }
-
- if (this.isNew) {
- // Mark the WHOLE parent array as modified
- // if this is a new document (i.e., we are initializing
- // a document),
- this.__parentArray._markModified();
- } else {
- this.__parentArray._markModified(this, path);
- }
- };
-
- /*!
- * ignore
- */
-
- EmbeddedDocument.prototype.populate = function() {
- throw new Error('Mongoose does not support calling populate() on nested ' +
- 'docs. Instead of `doc.arr[0].populate("path")`, use ' +
- '`doc.populate("arr.0.path")`');
- };
-
- /**
- * Used as a stub for [hooks.js](https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3)
- *
- * ####NOTE:
- *
- * _This is a no-op. Does not actually save the doc to the db._
- *
- * @param {Function} [fn]
- * @return {Promise} resolved Promise
- * @api private
- */
-
- EmbeddedDocument.prototype.save = function(options, fn) {
- if (typeof options === 'function') {
- fn = options;
- options = {};
- }
- options = options || {};
-
- if (!options.suppressWarning) {
- console.warn('mongoose: calling `save()` on a subdoc does **not** save ' +
- 'the document to MongoDB, it only runs save middleware. ' +
- 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' +
- 'if you\'re sure this behavior is right for your app.');
- }
-
- return utils.promiseOrCallback(fn, cb => {
- this.$__save(cb);
- });
- };
-
- /**
- * Used as a stub for middleware
- *
- * ####NOTE:
- *
- * _This is a no-op. Does not actually save the doc to the db._
- *
- * @param {Function} [fn]
- * @method $__save
- * @api private
- */
-
- EmbeddedDocument.prototype.$__save = function(fn) {
- return immediate(() => fn(null, this));
- };
-
- /*!
- * Registers remove event listeners for triggering
- * on subdocuments.
- *
- * @param {EmbeddedDocument} sub
- * @api private
- */
-
- function registerRemoveListener(sub) {
- let owner = sub.ownerDocument();
-
- function emitRemove() {
- owner.removeListener('save', emitRemove);
- owner.removeListener('remove', emitRemove);
- sub.emit('remove', sub);
- sub.constructor.emit('remove', sub);
- owner = sub = null;
- }
-
- owner.on('save', emitRemove);
- owner.on('remove', emitRemove);
- }
-
- /*!
- * no-op for hooks
- */
-
- EmbeddedDocument.prototype.$__remove = function(cb) {
- return cb(null, this);
- };
-
- /**
- * Removes the subdocument from its parent array.
- *
- * @param {Object} [options]
- * @param {Function} [fn]
- * @api public
- */
-
- EmbeddedDocument.prototype.remove = function(options, fn) {
- if ( typeof options === 'function' && !fn ) {
- fn = options;
- options = undefined;
- }
- if (!this.__parentArray || (options && options.noop)) {
- fn && fn(null);
- return this;
- }
-
- let _id;
- if (!this.willRemove) {
- _id = this._doc._id;
- if (!_id) {
- throw new Error('For your own good, Mongoose does not know ' +
- 'how to remove an EmbeddedDocument that has no _id');
- }
- this.__parentArray.pull({_id: _id});
- this.willRemove = true;
- registerRemoveListener(this);
- }
-
- if (fn) {
- fn(null);
- }
-
- return this;
- };
-
- /**
- * Override #update method of parent documents.
- * @api private
- */
-
- EmbeddedDocument.prototype.update = function() {
- throw new Error('The #update method is not available on EmbeddedDocuments');
- };
-
- /**
- * Helper for console.log
- *
- * @api public
- */
-
- EmbeddedDocument.prototype.inspect = function() {
- return this.toObject({
- transform: false,
- virtuals: false,
- flattenDecimals: false
- });
- };
-
- if (util.inspect.custom) {
- /*!
- * Avoid Node deprecation warning DEP0079
- */
-
- EmbeddedDocument.prototype[util.inspect.custom] = EmbeddedDocument.prototype.inspect;
- }
-
- /**
- * Marks a path as invalid, causing validation to fail.
- *
- * @param {String} path the field to invalidate
- * @param {String|Error} err error which states the reason `path` was invalid
- * @return {Boolean}
- * @api public
- */
-
- EmbeddedDocument.prototype.invalidate = function(path, err, val) {
- Document.prototype.invalidate.call(this, path, err, val);
-
- if (!this[documentArrayParent] || this.__index == null) {
- if (err[validatorErrorSymbol] || err.name === 'ValidationError') {
- return true;
- }
- throw err;
- }
-
- const index = this.__index;
- const parentPath = this.__parentArray.$path();
- const fullPath = [parentPath, index, path].join('.');
- this[documentArrayParent].invalidate(fullPath, err, val);
-
- return true;
- };
-
- /**
- * Marks a path as valid, removing existing validation errors.
- *
- * @param {String} path the field to mark as valid
- * @api private
- * @method $markValid
- * @receiver EmbeddedDocument
- */
-
- EmbeddedDocument.prototype.$markValid = function(path) {
- if (!this[documentArrayParent]) {
- return;
- }
-
- const index = this.__index;
- if (typeof index !== 'undefined') {
- const parentPath = this.__parentArray.$path();
- const fullPath = [parentPath, index, path].join('.');
- this[documentArrayParent].$markValid(fullPath);
- }
- };
-
- /*!
- * ignore
- */
-
- EmbeddedDocument.prototype.$ignore = function(path) {
- Document.prototype.$ignore.call(this, path);
-
- if (!this[documentArrayParent]) {
- return;
- }
-
- const index = this.__index;
- if (typeof index !== 'undefined') {
- const parentPath = this.__parentArray.$path();
- const fullPath = [parentPath, index, path].join('.');
- this[documentArrayParent].$ignore(fullPath);
- }
- };
-
- /**
- * Checks if a path is invalid
- *
- * @param {String} path the field to check
- * @api private
- * @method $isValid
- * @receiver EmbeddedDocument
- */
-
- EmbeddedDocument.prototype.$isValid = function(path) {
- const index = this.__index;
- if (typeof index !== 'undefined' && this[documentArrayParent]) {
- return !this[documentArrayParent].$__.validationError ||
- !this[documentArrayParent].$__.validationError.errors[this.$__fullPath(path)];
- }
-
- return true;
- };
-
- /**
- * Returns the top level document of this sub-document.
- *
- * @return {Document}
- */
-
- EmbeddedDocument.prototype.ownerDocument = function() {
- if (this.$__.ownerDocument) {
- return this.$__.ownerDocument;
- }
-
- let parent = this[documentArrayParent];
- if (!parent) {
- return this;
- }
-
- while (parent[documentArrayParent] || parent.$parent) {
- parent = parent[documentArrayParent] || parent.$parent;
- }
-
- this.$__.ownerDocument = parent;
- return this.$__.ownerDocument;
- };
-
- /**
- * Returns the full path to this document. If optional `path` is passed, it is appended to the full path.
- *
- * @param {String} [path]
- * @return {String}
- * @api private
- * @method $__fullPath
- * @memberOf EmbeddedDocument
- * @instance
- */
-
- EmbeddedDocument.prototype.$__fullPath = function(path) {
- if (!this.$__.fullPath) {
- let parent = this; // eslint-disable-line consistent-this
- if (!parent[documentArrayParent]) {
- return path;
- }
-
- const paths = [];
- while (parent[documentArrayParent] || parent.$parent) {
- if (parent[documentArrayParent]) {
- paths.unshift(parent.__parentArray.$path());
- } else {
- paths.unshift(parent.$basePath);
- }
- parent = parent[documentArrayParent] || parent.$parent;
- }
-
- this.$__.fullPath = paths.join('.');
-
- if (!this.$__.ownerDocument) {
- // optimization
- this.$__.ownerDocument = parent;
- }
- }
-
- return path
- ? this.$__.fullPath + '.' + path
- : this.$__.fullPath;
- };
-
- /**
- * Returns this sub-documents parent document.
- *
- * @api public
- */
-
- EmbeddedDocument.prototype.parent = function() {
- return this[documentArrayParent];
- };
-
- /**
- * Returns this sub-documents parent array.
- *
- * @api public
- */
-
- EmbeddedDocument.prototype.parentArray = function() {
- return this.__parentArray;
- };
-
- /*!
- * Module exports.
- */
-
- module.exports = EmbeddedDocument;
|