From 28b50f01f90717ab896a3ea10d8526805250fab0 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 12 Aug 2024 12:53:00 -0400 Subject: [PATCH] fix(document): make sure depopulate does not convert hydrated arrays to vanilla arrays --- lib/document.js | 36 +++++++++++++- lib/types/array/methods/index.js | 1 + test/document.populate.test.js | 85 +++++++++++++++++++++++++++++++- test/document.test.js | 3 ++ 4 files changed, 121 insertions(+), 4 deletions(-) diff --git a/lib/document.js b/lib/document.js index afc9e5ceacc..19298d05773 100644 --- a/lib/document.js +++ b/lib/document.js @@ -4774,7 +4774,23 @@ Document.prototype.depopulate = function(path) { continue; } delete populated[key]; - utils.setValue(key, populatedIds, this._doc); + if (Array.isArray(populatedIds)) { + const arr = utils.getValue(key, this._doc); + if (arr.isMongooseArray) { + const rawArray = arr.__array; + for (let i = 0; i < rawArray.length; ++i) { + const subdoc = rawArray[i]; + if (subdoc == null) { + continue; + } + rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; + } + } else { + utils.setValue(key, populatedIds, this._doc); + } + } else { + utils.setValue(key, populatedIds, this._doc); + } } return this; } @@ -4787,7 +4803,23 @@ Document.prototype.depopulate = function(path) { delete this.$$populatedVirtuals[singlePath]; delete this._doc[singlePath]; } else if (populatedIds) { - utils.setValue(singlePath, populatedIds, this._doc); + if (Array.isArray(populatedIds)) { + const arr = utils.getValue(singlePath, this._doc); + if (arr.isMongooseArray) { + const rawArray = arr.__array; + for (let i = 0; i < rawArray.length; ++i) { + const subdoc = rawArray[i]; + if (subdoc == null) { + continue; + } + rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id; + } + } else { + utils.setValue(singlePath, populatedIds, this._doc); + } + } else { + utils.setValue(singlePath, populatedIds, this._doc); + } } } return this; diff --git a/lib/types/array/methods/index.js b/lib/types/array/methods/index.js index b79628cd4cd..cf31914bb7e 100644 --- a/lib/types/array/methods/index.js +++ b/lib/types/array/methods/index.js @@ -721,6 +721,7 @@ const methods = { } this._registerAtomic('$push', atomic); + return ret; }, diff --git a/test/document.populate.test.js b/test/document.populate.test.js index 37e8ce3f600..bbfbc1df99a 100644 --- a/test/document.populate.test.js +++ b/test/document.populate.test.js @@ -552,7 +552,7 @@ describe('document.populate', function() { const docs = await Person.create([{ name: 'Axl Rose' }, { name: 'Slash' }]); - const band = await Band.create({ + let band = await Band.create({ name: 'Guns N\' Roses', members: [docs[0]._id, docs[1]], lead: docs[0]._id @@ -561,18 +561,44 @@ describe('document.populate', function() { await band.populate('members'); assert.equal(band.members[0].name, 'Axl Rose'); + assert.ok(band.members.isMongooseArray); + assert.ok(band.members.addToSet); band.depopulate('members'); assert.ok(!band.members[0].name); assert.equal(band.members[0].toString(), docs[0]._id.toString()); assert.equal(band.members[1].toString(), docs[1]._id.toString()); + assert.ok(band.members.isMongooseArray); + assert.ok(band.members.addToSet); + assert.deepStrictEqual(band.getChanges(), {}); assert.ok(!band.populated('members')); assert.ok(!band.populated('lead')); - await band.populate('lead'); + await band.populate('lead'); assert.equal(band.lead.name, 'Axl Rose'); band.depopulate('lead'); assert.ok(!band.lead.name); + assert.deepStrictEqual(band.getChanges(), {}); assert.equal(band.lead.toString(), docs[0]._id.toString()); + + const newId = new mongoose.Types.ObjectId(); + band.lead = newId; + assert.deepStrictEqual(band.getChanges(), { $set: { lead: newId } }); + + band = await Band.findById(band._id).orFail(); + await band.populate('members'); + + assert.equal(band.members[0].name, 'Axl Rose'); + assert.ok(band.members.isMongooseArray); + assert.ok(band.members.addToSet); + band.depopulate('members'); + assert.ok(!band.members[0].name); + assert.equal(band.members[0].toString(), docs[0]._id.toString()); + assert.equal(band.members[1].toString(), docs[1]._id.toString()); + assert.ok(band.members.isMongooseArray); + assert.ok(band.members.addToSet); + assert.deepStrictEqual(band.getChanges(), {}); + assert.ok(!band.populated('members')); + assert.ok(!band.populated('lead')); }); it('depopulates all (gh-6073)', async function() { @@ -706,7 +732,62 @@ describe('document.populate', function() { author.depopulate('books'); assert.ok(author.books); assert.strictEqual(author.books.length, 0); + }); + + it('depopulates after pushing manually populated (gh-2509)', async function() { + const Book = db.model( + 'Book', + new mongoose.Schema({ + name: String, + chapters: Number + }) + ); + const Author = db.model( + 'Person', + new mongoose.Schema({ + name: String, + books: { type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Book' }], default: [] } + }) + ); + + const books = await Book.create([ + { name: 'Lost Years of Merlin' }, + { name: 'Seven Songs of Merlin' }, + { name: 'Fires of Merlin' } + ]); + let author = new Author({ + name: 'T.A. Barron', + books: [books[0]._id] + }); + await author.save(); + await author.populate('books'); + assert.ok(author.books); + assert.strictEqual(author.books.length, 1); + + author.books.push(books[1]); + author.depopulate('books'); + assert.ok(author.books); + assert.ok(author.books.isMongooseArray); + assert.ok(!author.$populated('books')); + assert.deepStrictEqual(author.books, [books[0]._id, books[1]._id]); + await author.save(); + + author = await Author.findById(author._id).orFail(); + assert.strictEqual(author.books.length, 2); + assert.deepStrictEqual(author.books, [books[0]._id, books[1]._id]); + await author.populate('books'); + author.books.pull(books[0]._id); + assert.strictEqual(author.books.length, 1); + assert.equal(author.books[0].name, 'Seven Songs of Merlin'); + author.depopulate(); + assert.ok(author.books); + assert.ok(author.books.isMongooseArray); + assert.deepStrictEqual(author.books, [books[1]._id]); + await author.save(); + author = await Author.findById(author._id).orFail(); + assert.strictEqual(author.books.length, 1); + assert.deepStrictEqual(author.books, [books[1]._id]); }); }); diff --git a/test/document.test.js b/test/document.test.js index f65479224d0..7158464a748 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -10805,7 +10805,10 @@ describe('document', function() { assert.ok(!band.populated('members')); assert.ok(!band.populated('lead')); assert.ok(!band.populated('embeddedMembers.member')); + assert.ok(band.members.isMongooseArray); + assert.ok(band.embeddedMembers.isMongooseArray); assert.ok(!band.embeddedMembers[0].member.name); + // assert.ok(!band.embeddedMembers[0].$populated('member')); }); it('should allow dashes in the path name (gh-10677)', async function() {