From 403a28ee02b1a82cf9a34f4693fc02b23cad78e8 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 1 Jan 2024 15:53:19 -0500 Subject: [PATCH 1/3] fix: add ignoreAtomics option to isModified() for better backwards compatibility with Mongoose 5 Re: #14024 --- lib/document.js | 16 ++++++++++++--- lib/types/subdocument.js | 6 +++--- test/document.modified.test.js | 37 ++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/lib/document.js b/lib/document.js index 4b8cc160942..fdc49e79742 100644 --- a/lib/document.js +++ b/lib/document.js @@ -2223,8 +2223,9 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths; * @api public */ -Document.prototype.isModified = function(paths, modifiedPaths) { +Document.prototype.isModified = function(paths, options, modifiedPaths) { if (paths) { + const ignoreAtomics = options && options.ignoreAtomics; const directModifiedPathsObj = this.$__.activePaths.states.modify; if (directModifiedPathsObj == null) { return false; @@ -2245,7 +2246,16 @@ Document.prototype.isModified = function(paths, modifiedPaths) { return !!~modified.indexOf(path); }); - const directModifiedPaths = Object.keys(directModifiedPathsObj); + let directModifiedPaths = Object.keys(directModifiedPathsObj); + if (ignoreAtomics) { + directModifiedPaths = directModifiedPaths.filter(path => { + const value = this.$__getValue(path); + if (value != null && value[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) { + return false; + } + return true; + }); + } return isModifiedChild || paths.some(function(path) { return directModifiedPaths.some(function(mod) { return mod === path || path.startsWith(mod + '.'); @@ -2679,7 +2689,7 @@ function _getPathsToValidate(doc) { paths.delete(fullPathToSubdoc + '.' + modifiedPath); } - if (doc.$isModified(fullPathToSubdoc, modifiedPaths) && + if (doc.$isModified(fullPathToSubdoc, null, modifiedPaths) && !doc.isDirectModified(fullPathToSubdoc) && !doc.$isDefault(fullPathToSubdoc)) { paths.add(fullPathToSubdoc); diff --git a/lib/types/subdocument.js b/lib/types/subdocument.js index d282f892400..48a7fc71215 100644 --- a/lib/types/subdocument.js +++ b/lib/types/subdocument.js @@ -178,7 +178,7 @@ Subdocument.prototype.markModified = function(path) { * ignore */ -Subdocument.prototype.isModified = function(paths, modifiedPaths) { +Subdocument.prototype.isModified = function(paths, options, modifiedPaths) { const parent = this.$parent(); if (parent != null) { if (Array.isArray(paths) || typeof paths === 'string') { @@ -188,10 +188,10 @@ Subdocument.prototype.isModified = function(paths, modifiedPaths) { paths = this.$__pathRelativeToParent(); } - return parent.$isModified(paths, modifiedPaths); + return parent.$isModified(paths, options, modifiedPaths); } - return Document.prototype.isModified.call(this, paths, modifiedPaths); + return Document.prototype.isModified.call(this, paths, options, modifiedPaths); }; /** diff --git a/test/document.modified.test.js b/test/document.modified.test.js index 4518b2746cc..b733937d40e 100644 --- a/test/document.modified.test.js +++ b/test/document.modified.test.js @@ -208,6 +208,43 @@ describe('document modified', function() { assert.equal(post.isModified('comments.0.title'), true); assert.equal(post.isDirectModified('comments.0.title'), true); }); + it('with push (gh-14024)', async function() { + const post = new BlogPost(); + post.init({ + title: 'Test', + slug: 'test', + comments: [{ title: 'Test', date: new Date(), body: 'Test' }] + }); + + post.comments.push({ title: 'new comment', body: 'test' }); + + assert.equal(post.isModified('comments.0.title', { ignoreAtomics: true }), false); + assert.equal(post.isModified('comments.0.body', { ignoreAtomics: true }), false); + assert.equal(post.get('comments')[0].isModified('body', { ignoreAtomics: true }), false); + }); + it('with push and set (gh-14024)', async function() { + const post = new BlogPost(); + post.init({ + title: 'Test', + slug: 'test', + comments: [{ title: 'Test', date: new Date(), body: 'Test' }] + }); + + post.comments.push({ title: 'new comment', body: 'test' }); + post.get('comments')[0].set('title', 'Woot'); + + assert.equal(post.isModified('comments', { ignoreAtomics: true }), true); + assert.equal(post.isModified('comments.0.title', { ignoreAtomics: true }), true); + assert.equal(post.isDirectModified('comments.0.title'), true); + assert.equal(post.isDirectModified('comments.0.body'), false); + assert.equal(post.isModified('comments.0.body', { ignoreAtomics: true }), false); + + assert.equal(post.isModified('comments', { ignoreAtomics: true }), true); + assert.equal(post.isModified('comments.0.title', { ignoreAtomics: true }), true); + assert.equal(post.isDirectModified('comments.0.title'), true); + assert.equal(post.isDirectModified('comments.0.body'), false); + assert.equal(post.isModified('comments.0.body', { ignoreAtomics: true }), false); + }); it('with accessors', function() { const post = new BlogPost(); post.init({ From f7e981626e916f87b8e4fa66233a75f162718633 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 3 Jan 2024 15:55:42 -0500 Subject: [PATCH 2/3] docs(document): add ignoreAtomics option to docs --- lib/document.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/document.js b/lib/document.js index fdc49e79742..57d270bc3af 100644 --- a/lib/document.js +++ b/lib/document.js @@ -2219,6 +2219,8 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths; * doc.isDirectModified('documents') // false * * @param {String} [path] optional + * @param {Object} [options] + * @param {Boolean} [options.ignoreAtomics=false] If true, doesn't return true if path is underneath an array that was modified with atomic operations like `push()` * @return {Boolean} * @api public */ From 0960fae4d1b09bf70d2345a43e74cd1b137ab754 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 3 Jan 2024 15:55:59 -0500 Subject: [PATCH 3/3] types(document): add ignoreAtomics option to isModified typedefs re: #14024 --- types/document.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/document.d.ts b/types/document.d.ts index f43db5c34c8..3a831ea017a 100644 --- a/types/document.d.ts +++ b/types/document.d.ts @@ -179,7 +179,7 @@ declare module 'mongoose' { * Returns true if any of the given paths are modified, else false. If no arguments, returns `true` if any path * in this document is modified. */ - isModified(path?: string | Array): boolean; + isModified(path?: string | Array, options?: { ignoreAtomics?: boolean } | null): boolean; /** Boolean flag specifying if the document is new. */ isNew: boolean;