|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- 'use strict';
-
- const Document = require('../document');
- const immediate = require('../helpers/immediate');
- const internalToObjectOptions = require('../options').internalToObjectOptions;
- const utils = require('../utils');
-
- const documentArrayParent = require('../helpers/symbols').documentArrayParent;
-
- module.exports = Subdocument;
-
- /**
- * Subdocument constructor.
- *
- * @inherits Document
- * @api private
- */
-
- function Subdocument(value, fields, parent, skipId, options) {
- this.$isSingleNested = true;
-
- const hasPriorDoc = options != null && options.priorDoc;
- let initedPaths = null;
- if (hasPriorDoc) {
- this._doc = Object.assign({}, options.priorDoc._doc);
- delete this._doc[this.schema.options.discriminatorKey];
- initedPaths = Object.keys(options.priorDoc._doc || {}).
- filter(key => key !== this.schema.options.discriminatorKey);
- }
- if (parent != null) {
- // If setting a nested path, should copy isNew from parent re: gh-7048
- options = Object.assign({}, options, { isNew: parent.isNew });
- }
- Document.call(this, value, fields, skipId, options);
-
- if (hasPriorDoc) {
- for (const key of initedPaths) {
- if (!this.$__.activePaths.states.modify[key] &&
- !this.$__.activePaths.states.default[key] &&
- !this.$__.$setCalled.has(key)) {
- delete this._doc[key];
- }
- }
- }
- }
-
- Subdocument.prototype = Object.create(Document.prototype);
-
- Subdocument.prototype.toBSON = function() {
- return this.toObject(internalToObjectOptions);
- };
-
- /**
- * Used as a stub for middleware
- *
- * ####NOTE:
- *
- * _This is a no-op. Does not actually save the doc to the db._
- *
- * @param {Function} [fn]
- * @return {Promise} resolved Promise
- * @api private
- */
-
- Subdocument.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
- */
-
- Subdocument.prototype.$__save = function(fn) {
- return immediate(() => fn(null, this));
- };
-
- Subdocument.prototype.$isValid = function(path) {
- if (this.$parent && this.$basePath) {
- return this.$parent.$isValid([this.$basePath, path].join('.'));
- }
- return Document.prototype.$isValid.call(this, path);
- };
-
- Subdocument.prototype.markModified = function(path) {
- Document.prototype.markModified.call(this, path);
-
- if (this.$parent && this.$basePath) {
- if (this.$parent.isDirectModified(this.$basePath)) {
- return;
- }
- this.$parent.markModified([this.$basePath, path].join('.'), this);
- }
- };
-
- /**
- * Marks a path as valid, removing existing validation errors.
- *
- * @param {String} path the field to mark as valid
- * @api private
- * @method $markValid
- * @receiver EmbeddedDocument
- */
-
- Subdocument.prototype.$markValid = function(path) {
- Document.prototype.$markValid.call(this, path);
- if (this.$parent && this.$basePath) {
- this.$parent.$markValid([this.$basePath, path].join('.'));
- }
- };
-
- /*!
- * ignore
- */
-
- Subdocument.prototype.invalidate = function(path, err, val) {
- // Hack: array subdocuments' validationError is equal to the owner doc's,
- // so validating an array subdoc gives the top-level doc back. Temporary
- // workaround for #5208 so we don't have circular errors.
- if (err !== this.ownerDocument().$__.validationError) {
- Document.prototype.invalidate.call(this, path, err, val);
- }
-
- if (this.$parent && this.$basePath) {
- this.$parent.invalidate([this.$basePath, path].join('.'), err, val);
- } else if (err.kind === 'cast' || err.name === 'CastError') {
- throw err;
- }
- };
-
- /*!
- * ignore
- */
-
- Subdocument.prototype.$ignore = function(path) {
- Document.prototype.$ignore.call(this, path);
- if (this.$parent && this.$basePath) {
- this.$parent.$ignore([this.$basePath, path].join('.'));
- }
- };
-
- /**
- * Returns the top level document of this sub-document.
- *
- * @return {Document}
- */
-
- Subdocument.prototype.ownerDocument = function() {
- if (this.$__.ownerDocument) {
- return this.$__.ownerDocument;
- }
-
- let parent = this.$parent;
- if (!parent) {
- return this;
- }
-
- while (parent.$parent || parent[documentArrayParent]) {
- parent = parent.$parent || parent[documentArrayParent];
- }
-
- this.$__.ownerDocument = parent;
- return this.$__.ownerDocument;
- };
-
- /**
- * Returns this sub-documents parent document.
- *
- * @api public
- */
-
- Subdocument.prototype.parent = function() {
- return this.$parent;
- };
-
- /*!
- * no-op for hooks
- */
-
- Subdocument.prototype.$__remove = function(cb) {
- return cb(null, this);
- };
-
- /**
- * Null-out this subdoc
- *
- * @param {Object} [options]
- * @param {Function} [callback] optional callback for compatibility with Document.prototype.remove
- */
-
- Subdocument.prototype.remove = function(options, callback) {
- if (typeof options === 'function') {
- callback = options;
- options = null;
- }
-
- registerRemoveListener(this);
-
- // If removing entire doc, no need to remove subdoc
- if (!options || !options.noop) {
- this.$parent.set(this.$basePath, null);
- }
-
- if (typeof callback === 'function') {
- callback(null);
- }
- };
-
- /*!
- * ignore
- */
-
- Subdocument.prototype.populate = function() {
- throw new Error('Mongoose does not support calling populate() on nested ' +
- 'docs. Instead of `doc.nested.populate("path")`, use ' +
- '`doc.populate("nested.path")`');
- };
-
- /*!
- * 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);
- }
|