Skip to content

Commit

Permalink
feat(model): add applyDefaults() helper that allows applying defaul…
Browse files Browse the repository at this point in the history
…ts to document or POJO

Re: #11945
  • Loading branch information
vkarpov15 committed Jul 6, 2022
1 parent 38865df commit ae73d8b
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 121 deletions.
123 changes: 4 additions & 119 deletions lib/document.js
Expand Up @@ -18,6 +18,7 @@ const ValidatorError = require('./error/validator');
const VirtualType = require('./virtualtype');
const $__hasIncludedChildren = require('./helpers/projection/hasIncludedChildren');
const promiseOrCallback = require('./helpers/promiseOrCallback');
const applyDefaults = require('./helpers/document/applyDefaults');
const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
const compile = require('./helpers/document/compile').compile;
const defineKey = require('./helpers/document/compile').defineKey;
Expand Down Expand Up @@ -150,7 +151,7 @@ function Document(obj, fields, skipId, options) {
// By default, defaults get applied **before** setting initial values
// Re: gh-6155
if (defaults) {
$__applyDefaults(this, fields, exclude, hasIncludedChildren, true, null);
applyDefaults(this, fields, exclude, hasIncludedChildren, true, null);
}
}
if (obj) {
Expand All @@ -174,7 +175,7 @@ function Document(obj, fields, skipId, options) {
this.$__.skipDefaults = options.skipDefaults;
}
} else if (defaults) {
$__applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
}

if (!this.$__.strictMode && obj) {
Expand Down Expand Up @@ -413,122 +414,6 @@ Object.defineProperty(Document.prototype, '$op', {
}
});

/*!
* ignore
*/

function $__applyDefaults(doc, fields, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
const paths = Object.keys(doc.$__schema.paths);
const plen = paths.length;

for (let i = 0; i < plen; ++i) {
let def;
let curPath = '';
const p = paths[i];

if (p === '_id' && doc.$__.skipId) {
continue;
}

const type = doc.$__schema.paths[p];
const path = type.splitPath();
const len = path.length;
let included = false;
let doc_ = doc._doc;
for (let j = 0; j < len; ++j) {
if (doc_ == null) {
break;
}

const piece = path[j];
curPath += (!curPath.length ? '' : '.') + piece;

if (exclude === true) {
if (curPath in fields) {
break;
}
} else if (exclude === false && fields && !included) {
const hasSubpaths = type.$isSingleNested || type.$isMongooseDocumentArray;
if (curPath in fields || (hasSubpaths && hasIncludedChildren != null && hasIncludedChildren[curPath])) {
included = true;
} else if (hasIncludedChildren != null && !hasIncludedChildren[curPath]) {
break;
}
}

if (j === len - 1) {
if (doc_[piece] !== void 0) {
break;
}

if (typeof type.defaultValue === 'function') {
if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
break;
}
if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
break;
}
} else if (!isBeforeSetters) {
// Non-function defaults should always run **before** setters
continue;
}

if (pathsToSkip && pathsToSkip[curPath]) {
break;
}

if (fields && exclude !== null) {
if (exclude === true) {
// apply defaults to all non-excluded fields
if (p in fields) {
continue;
}

try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
} else if (included) {
// selected field
try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
}
} else {
try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
}
} else {
doc_ = doc_[piece];
}
}
}
}

/*!
* ignore
*/
Expand Down Expand Up @@ -748,7 +633,7 @@ Document.prototype.$__init = function(doc, opts) {
const hasIncludedChildren = this.$__.exclude === false && this.$__.fields ?
$__hasIncludedChildren(this.$__.fields) :
null;
$__applyDefaults(this, this.$__.fields, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);
applyDefaults(this, this.$__.fields, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);

return this;
};
Expand Down
113 changes: 113 additions & 0 deletions lib/helpers/document/applyDefaults.js
@@ -0,0 +1,113 @@
'use strict';

module.exports = function applyDefaults(doc, fields, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
const paths = Object.keys(doc.$__schema.paths);
const plen = paths.length;

for (let i = 0; i < plen; ++i) {
let def;
let curPath = '';
const p = paths[i];

if (p === '_id' && doc.$__.skipId) {
continue;
}

const type = doc.$__schema.paths[p];
const path = type.splitPath();
const len = path.length;
let included = false;
let doc_ = doc._doc;
for (let j = 0; j < len; ++j) {
if (doc_ == null) {
break;
}

const piece = path[j];
curPath += (!curPath.length ? '' : '.') + piece;

if (exclude === true) {
if (curPath in fields) {
break;
}
} else if (exclude === false && fields && !included) {
const hasSubpaths = type.$isSingleNested || type.$isMongooseDocumentArray;
if (curPath in fields || (hasSubpaths && hasIncludedChildren != null && hasIncludedChildren[curPath])) {
included = true;
} else if (hasIncludedChildren != null && !hasIncludedChildren[curPath]) {
break;
}
}

if (j === len - 1) {
if (doc_[piece] !== void 0) {
break;
}

if (typeof type.defaultValue === 'function') {
if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
break;
}
if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
break;
}
} else if (!isBeforeSetters) {
// Non-function defaults should always run **before** setters
continue;
}

if (pathsToSkip && pathsToSkip[curPath]) {
break;
}

if (fields && exclude !== null) {
if (exclude === true) {
// apply defaults to all non-excluded fields
if (p in fields) {
continue;
}

try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
} else if (included) {
// selected field
try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
}
} else {
try {
def = type.getDefault(doc, false);
} catch (err) {
doc.invalidate(p, err);
break;
}

if (typeof def !== 'undefined') {
doc_[piece] = def;
doc.$__.activePaths.default(p);
}
}
} else {
doc_ = doc_[piece];
}
}
}
};
52 changes: 52 additions & 0 deletions lib/helpers/model/applyDefaultsToPOJO.js
@@ -0,0 +1,52 @@
'use strict';

module.exports = function applyDefaultsToPOJO(doc, schema) {
const paths = Object.keys(schema.paths);
const plen = paths.length;

for (let i = 0; i < plen; ++i) {
let curPath = '';
const p = paths[i];

const type = schema.paths[p];
const path = type.splitPath();
const len = path.length;
let doc_ = doc;
for (let j = 0; j < len; ++j) {
if (doc_ == null) {
break;
}

const piece = path[j];
curPath += (!curPath.length ? '' : '.') + piece;

if (j === len - 1) {
if (typeof doc_[piece] !== 'undefined') {
if (type.$isSingleNested) {
applyDefaultsToPOJO(doc_[piece], type.caster.schema);
} else if (type.$isMongooseDocumentArray && Array.isArray(doc_[piece])) {
doc_[piece].forEach(el => applyDefaultsToPOJO(el, type.schema));
}

break;
}

const def = type.getDefault(doc, false, { skipCast: true });
if (typeof def !== 'undefined') {
doc_[piece] = def;

if (type.$isSingleNested) {
applyDefaultsToPOJO(def, type.caster.schema);
} else if (type.$isMongooseDocumentArray && Array.isArray(def)) {
def.forEach(el => applyDefaultsToPOJO(el, type.schema));
}
}
} else {
if (doc_[piece] == null) {
doc_[piece] = {};
}
doc_ = doc_[piece];
}
}
}
};
19 changes: 19 additions & 0 deletions lib/model.js
Expand Up @@ -22,6 +22,8 @@ const ServerSelectionError = require('./error/serverSelection');
const ValidationError = require('./error/validation');
const VersionError = require('./error/version');
const ParallelSaveError = require('./error/parallelSave');
const applyDefaultsHelper = require('./helpers/document/applyDefaults');
const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO');
const applyQueryMiddleware = require('./helpers/query/applyQueryMiddleware');
const applyHooks = require('./helpers/model/applyHooks');
const applyMethods = require('./helpers/model/applyMethods');
Expand Down Expand Up @@ -3685,13 +3687,30 @@ function handleSuccessfulWrite(document, resolve, reject) {
});
}

/**
*
* @param {Object|Document} obj object or document to apply defaults on
* @returns {Object|Document}
*/

Model.applyDefaults = function applyDefaults(doc) {
if (doc.$__ != null) {
return applyDefaultsHelper(doc, doc.$__.fields, doc.$__.exclude);
}

applyDefaultsToPOJO(doc, this.schema);

return doc;
};

/**
*
* @param {[Document]} documents The array of documents to build write operations of
* @param {Object} options
* @param {Boolean} options.skipValidation defaults to `false`, when set to true, building the write operations will bypass validating the documents.
* @returns
*/

Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, options) {
if (!Array.isArray(documents)) {
throw new Error(`bulkSave expects an array of documents to be passed, received \`${documents}\` instead`);
Expand Down
6 changes: 5 additions & 1 deletion lib/schema/documentarray.js
Expand Up @@ -342,7 +342,7 @@ DocumentArrayPath.prototype.doValidateSync = function(array, scope, options) {
* ignore
*/

DocumentArrayPath.prototype.getDefault = function(scope) {
DocumentArrayPath.prototype.getDefault = function(scope, init, options) {
let ret = typeof this.defaultValue === 'function'
? this.defaultValue.call(scope)
: this.defaultValue;
Expand All @@ -351,6 +351,10 @@ DocumentArrayPath.prototype.getDefault = function(scope) {
return ret;
}

if (options && options.skipCast) {
return ret;
}

// lazy load
MongooseDocumentArray || (MongooseDocumentArray = require('../types/DocumentArray'));

Expand Down

0 comments on commit ae73d8b

Please sign in to comment.