Skip to content

Commit

Permalink
fix: support populating just one entry in map of arrays of refs
Browse files Browse the repository at this point in the history
Fix #12494
  • Loading branch information
vkarpov15 committed Oct 26, 2022
1 parent 134d769 commit bcc85cb
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 5 deletions.
5 changes: 4 additions & 1 deletion lib/helpers/populate/getModelsMapForPopulate.js
Expand Up @@ -207,6 +207,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
let isRefPath = false;
let justOne = null;

const originalSchema = schema;
if (schema && schema.instance === 'Array') {
schema = schema.caster;
}
Expand Down Expand Up @@ -277,7 +278,9 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
schemaForCurrentDoc = schema;
}

if (schemaForCurrentDoc != null) {
if (originalSchema && originalSchema.path.endsWith('.$*')) {
justOne = !originalSchema.$isMongooseArray && !originalSchema._arrayPath;
} else if (schemaForCurrentDoc != null) {
justOne = !schemaForCurrentDoc.$isMongooseArray && !schemaForCurrentDoc._arrayPath;
}

Expand Down
6 changes: 5 additions & 1 deletion lib/model.js
Expand Up @@ -4784,7 +4784,11 @@ function populate(model, docs, options, callback) {
for (const val of vals) {
mod.options._childDocs.push(val);
}
_assign(model, vals, mod, assignmentOpts);
try {
_assign(model, vals, mod, assignmentOpts);
} catch (err) {
return callback(err);
}
}

for (const arr of params) {
Expand Down
7 changes: 7 additions & 0 deletions lib/types/map.js
@@ -1,6 +1,7 @@
'use strict';

const Mixed = require('../schema/mixed');
const MongooseError = require('../error/mongooseError');
const clone = require('../helpers/clone');
const deepEqual = require('../utils').deepEqual;
const getConstructorName = require('../helpers/getConstructorName');
Expand Down Expand Up @@ -102,6 +103,12 @@ class MongooseMap extends Map {
const priorVal = this.get(key);

if (populated != null) {
if (this.$__schemaType.$isSingleNested) {
throw new MongooseError(
'Cannot manually populate single nested subdoc underneath Map ' +
`at path "${this.$__path}". Try using an array instead of a Map.`
);
}
if (Array.isArray(value) && this.$__schemaType.$isMongooseArray) {
value = value.map(v => {
if (v.$__ == null) {
Expand Down
48 changes: 48 additions & 0 deletions test/document.test.js
Expand Up @@ -11016,6 +11016,54 @@ describe('document', function() {
assert.equal(foo.get('bar.another'), 2);
});

it('populating subdocument refs underneath maps throws (gh-12494) (gh-10856)', async function() {
// Bar model, has a name property and some other properties that we are interested in
const BarSchema = new Schema({
name: String,
more: String,
another: Number
});
const Bar = db.model('Bar', BarSchema);

// Denormalised Bar schema with just the name, for use on the Foo model
const BarNameSchema = new Schema({
_id: {
type: Schema.Types.ObjectId,
ref: 'Bar'
},
name: String
});

// Foo model, which contains denormalized bar data (just the name)
const FooSchema = new Schema({
something: String,
other: Number,
map: {
type: Map,
of: {
type: BarNameSchema,
ref: 'Bar'
}
}
});
const Foo = db.model('Foo', FooSchema);

const bar = await Bar.create({
name: 'I am Bar',
more: 'With more data',
another: 2
});
const { _id } = await Foo.create({
something: 'I am Foo',
other: 1,
map: { test: bar }
});

const err = await Foo.findById(_id).populate('map').then(() => null, err => err);
assert.ok(err);
assert.ok(err.message.includes('Cannot manually populate single nested subdoc underneath Map'), err.message);
});

it('handles save with undefined nested doc under subdoc (gh-11110)', async function() {
const testSchema = new Schema({
level_1_array: [new Schema({
Expand Down
17 changes: 14 additions & 3 deletions test/types.map.test.js
Expand Up @@ -1080,13 +1080,24 @@ describe('Map', function() {
}
}).save();

const query = UserModel.findById(_id);

// Using `.$*`
let query = UserModel.findById(_id);
query.populate({
path: 'addresses.$*'
});

const doc = await query.exec();
let doc = await query.exec();
assert.ok(Array.isArray(doc.addresses.get('home')));
assert.equal(doc.addresses.get('home').length, 1);
assert.equal(doc.addresses.get('home')[0].city, 'London');

// Populating just one path in the map
query = UserModel.findById(_id);
query.populate({
path: 'addresses.home'
});

doc = await query.exec();
assert.ok(Array.isArray(doc.addresses.get('home')));
assert.equal(doc.addresses.get('home').length, 1);
assert.equal(doc.addresses.get('home')[0].city, 'London');
Expand Down

0 comments on commit bcc85cb

Please sign in to comment.