123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- 'use strict';
- const Document = require('../document');
- const immediate = require('../helpers/immediate');
- const internalToObjectOptions = require('../options').internalToObjectOptions;
- const promiseOrCallback = require('../helpers/promiseOrCallback');
- const util = require('util');
- const utils = require('../utils');
- module.exports = Subdocument;
- /**
- * Subdocument constructor.
- *
- * @inherits Document
- * @api private
- */
- function Subdocument(value, fields, parent, skipId, options) {
- if (parent != null) {
- // If setting a nested path, should copy isNew from parent re: gh-7048
- const parentOptions = { isNew: parent.isNew };
- if ('defaults' in parent.$__) {
- parentOptions.defaults = parent.$__.defaults;
- }
- options = Object.assign(parentOptions, options);
- }
- if (options != null && options.path != null) {
- this.$basePath = options.path;
- }
- Document.call(this, value, fields, skipId, options);
- delete this.$__.priorDoc;
- }
- Subdocument.prototype = Object.create(Document.prototype);
- Object.defineProperty(Subdocument.prototype, '$isSubdocument', {
- configurable: false,
- writable: false,
- value: true
- });
- Object.defineProperty(Subdocument.prototype, '$isSingleNested', {
- configurable: false,
- writable: false,
- value: true
- });
- /*!
- * ignore
- */
- 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) {
- utils.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 promiseOrCallback(fn, cb => {
- this.$__save(cb);
- });
- };
- /*!
- * Given a path relative to this document, return the path relative
- * to the top-level document.
- */
- Subdocument.prototype.$__fullPath = function(path) {
- if (!this.$__.fullPath) {
- this.ownerDocument();
- }
- return path ?
- this.$__.fullPath + '.' + path :
- this.$__.fullPath;
- };
- /*!
- * Given a path relative to this document, return the path relative
- * to the top-level document.
- */
- Subdocument.prototype.$__pathRelativeToParent = function(p) {
- if (p == null) {
- return this.$basePath;
- }
- return [this.$basePath, p].join('.');
- };
- /**
- * 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));
- };
- /*!
- * ignore
- */
- Subdocument.prototype.$isValid = function(path) {
- const parent = this.$parent();
- const fullPath = this.$__pathRelativeToParent(path);
- if (parent != null && fullPath != null) {
- return parent.$isValid(fullPath);
- }
- return Document.prototype.$isValid.call(this, path);
- };
- /*!
- * ignore
- */
- Subdocument.prototype.markModified = function(path) {
- Document.prototype.markModified.call(this, path);
- const parent = this.$parent();
- const fullPath = this.$__pathRelativeToParent(path);
- if (parent == null || fullPath == null) {
- return;
- }
- const myPath = this.$__pathRelativeToParent().replace(/\.$/, '');
- if (parent.isDirectModified(myPath) || this.isNew) {
- return;
- }
- this.$__parent.markModified(fullPath, this);
- };
- /*!
- * ignore
- */
- Subdocument.prototype.isModified = function(paths, modifiedPaths) {
- const parent = this.$parent();
- if (parent != null) {
- if (Array.isArray(paths) || typeof paths === 'string') {
- paths = (Array.isArray(paths) ? paths : paths.split(' '));
- paths = paths.map(p => this.$__pathRelativeToParent(p)).filter(p => p != null);
- } else if (!paths) {
- paths = this.$__pathRelativeToParent();
- }
- return parent.$isModified(paths, modifiedPaths);
- }
- return Document.prototype.isModified.call(this, paths, modifiedPaths);
- };
- /**
- * Marks a path as valid, removing existing validation errors.
- *
- * @param {String} path the field to mark as valid
- * @api private
- * @method $markValid
- * @receiver Subdocument
- */
- Subdocument.prototype.$markValid = function(path) {
- Document.prototype.$markValid.call(this, path);
- const parent = this.$parent();
- const fullPath = this.$__pathRelativeToParent(path);
- if (parent != null && fullPath != null) {
- parent.$markValid(fullPath);
- }
- };
- /*!
- * ignore
- */
- Subdocument.prototype.invalidate = function(path, err, val) {
- Document.prototype.invalidate.call(this, path, err, val);
- const parent = this.$parent();
- const fullPath = this.$__pathRelativeToParent(path);
- if (parent != null && fullPath != null) {
- parent.invalidate(fullPath, err, val);
- } else if (err.kind === 'cast' || err.name === 'CastError' || fullPath == null) {
- throw err;
- }
- return this.ownerDocument().$__.validationError;
- };
- /*!
- * ignore
- */
- Subdocument.prototype.$ignore = function(path) {
- Document.prototype.$ignore.call(this, path);
- const parent = this.$parent();
- const fullPath = this.$__pathRelativeToParent(path);
- if (parent != null && fullPath != null) {
- parent.$ignore(fullPath);
- }
- };
- /**
- * Returns the top level document of this sub-document.
- *
- * @return {Document}
- */
- Subdocument.prototype.ownerDocument = function() {
- if (this.$__.ownerDocument) {
- return this.$__.ownerDocument;
- }
- let parent = this; // eslint-disable-line consistent-this
- const paths = [];
- const seenDocs = new Set([parent]);
- while (true) {
- if (typeof parent.$__pathRelativeToParent !== 'function') {
- break;
- }
- paths.unshift(parent.$__pathRelativeToParent(void 0, true));
- const _parent = parent.$parent();
- if (_parent == null) {
- break;
- }
- parent = _parent;
- if (seenDocs.has(parent)) {
- throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself');
- }
- seenDocs.add(parent);
- }
- this.$__.fullPath = paths.join('.');
- this.$__.ownerDocument = parent;
- return this.$__.ownerDocument;
- };
- /*!
- * ignore
- */
- Subdocument.prototype.$__fullPathWithIndexes = function() {
- let parent = this; // eslint-disable-line consistent-this
- const paths = [];
- const seenDocs = new Set([parent]);
- while (true) {
- if (typeof parent.$__pathRelativeToParent !== 'function') {
- break;
- }
- paths.unshift(parent.$__pathRelativeToParent(void 0, false));
- const _parent = parent.$parent();
- if (_parent == null) {
- break;
- }
- parent = _parent;
- if (seenDocs.has(parent)) {
- throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself');
- }
- seenDocs.add(parent);
- }
- return paths.join('.');
- };
- /**
- * Returns this sub-documents parent document.
- *
- * @api public
- */
- Subdocument.prototype.parent = function() {
- return this.$__parent;
- };
- /**
- * Returns this sub-documents parent document.
- *
- * @api public
- * @method $parent
- */
- Subdocument.prototype.$parent = Subdocument.prototype.parent;
- /*!
- * no-op for hooks
- */
- Subdocument.prototype.$__remove = function(cb) {
- if (cb == null) {
- return;
- }
- return cb(null, this);
- };
- Subdocument.prototype.$__removeFromParent = function() {
- this.$__parent.set(this.$basePath, null);
- };
- /**
- * 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.$__removeFromParent();
- }
- return this.$__remove(callback);
- };
- /*!
- * 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")`');
- };
- /**
- * Helper for console.log
- *
- * @api public
- */
- Subdocument.prototype.inspect = function() {
- return this.toObject({
- transform: false,
- virtuals: false,
- flattenDecimals: false
- });
- };
- if (util.inspect.custom) {
- /*!
- * Avoid Node deprecation warning DEP0079
- */
- Subdocument.prototype[util.inspect.custom] = Subdocument.prototype.inspect;
- }
- /*!
- * Registers remove event listeners for triggering
- * on subdocuments.
- *
- * @param {Subdocument} 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);
- }
|