Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(populate): handle virtual populate underneath document array with justOne=true and sort set where 1 element has only 1 result #12815

Merged
merged 1 commit into from Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/helpers/populate/assignRawDocsToIdStructure.js
Expand Up @@ -43,6 +43,8 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re

let i = 0;
const len = rawIds.length;
const hasResultArrays = Object.values(resultOrder).find(o => Array.isArray(o));

for (i = 0; i < len; ++i) {
id = rawIds[i];

Expand Down Expand Up @@ -77,7 +79,8 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re
if (doc) {
if (sorting) {
const _resultOrder = resultOrder[sid];
if (Array.isArray(_resultOrder) && Array.isArray(doc) && _resultOrder.length === doc.length) {
if (hasResultArrays) {
// If result arrays, rely on the MongoDB server response for ordering
newOrder.push(doc);
} else {
newOrder[_resultOrder] = doc;
Expand Down
122 changes: 122 additions & 0 deletions test/model.populate.test.js
Expand Up @@ -4429,6 +4429,55 @@ describe('model: populate:', function() {
assert.deepEqual(app.modules[1].menu.map(i => i.title), ['Redo', 'Undo']);
});

it('in embedded array with sort and one result (gh-10552)', async function() {
const AppMenuItemSchema = new Schema({
appId: 'ObjectId',
moduleId: Number,
title: String,
parent: {
type: mongoose.ObjectId,
ref: 'AppMenuItem'
},
order: Number
});

const moduleSchema = new Schema({
_id: Number,
title: { type: String },
hidden: { type: Boolean }
});

moduleSchema.virtual('menu', {
ref: 'Test1',
localField: '_id',
foreignField: 'moduleId',
options: { sort: { title: 1 } }
});

const appSchema = new Schema({
modules: [moduleSchema]
});

const App = db.model('Test', appSchema);
const AppMenuItem = db.model('Test1', AppMenuItemSchema);

let app = await App.create({ modules: [{ _id: 1, title: 'File' }, { _id: 2, title: 'Preferences' }] });
await AppMenuItem.create([
{ title: 'Save', moduleId: 1 },
{ title: 'Save As', moduleId: 1 },
// { title: 'Undo', moduleId: 2 },
{ title: 'Redo', moduleId: 2 }
]);

app = await App.findById(app).populate('modules.menu');
app = app.toObject({ virtuals: true });

assert.equal(app.modules.length, 2);
assert.equal(app.modules[0].menu.length, 2);
assert.deepEqual(app.modules[0].menu.map(i => i.title), ['Save', 'Save As']);
assert.deepEqual(app.modules[1].menu.map(i => i.title), ['Redo']);
});

it('justOne option (gh-4263)', function(done) {
const PersonSchema = new Schema({
name: String,
Expand Down Expand Up @@ -10793,6 +10842,79 @@ describe('model: populate:', function() {
assert.equal(row.values.get(createList._id.toString()).valueObject.name, 'test');
});

it('handles virtual populate with `justOne` underneath document array and sort (gh-12730) (gh-10552)', async function() {
const shiftSchema = new mongoose.Schema({
employeeId: mongoose.Types.ObjectId,
startedAt: Date,
endedAt: Date,
name: String
});

const Shift = db.model('Child', shiftSchema);

const employeeSchema = new mongoose.Schema({
name: String
}, { toJSON: { virtuals: true }, toObject: { virtuals: true } });

employeeSchema.virtual('mostRecentShift', {
ref: Shift,
localField: '_id',
foreignField: 'employeeId',
options: {
sort: { startedAt: -1 }
},
justOne: true
});

const storeSchema = new mongoose.Schema({
location: String,
employees: [employeeSchema]
});

const Store = db.model('Parent', storeSchema);

const store = await Store.create({
location: 'Tashbaan',
employees: [
{ name: 'Aravis' },
{ name: 'Shasta' }
]
});

const employeeAravis = store.employees.find(({ name }) => name === 'Aravis');
const employeeShasta = store.employees.find(({ name }) => name === 'Shasta');

await Shift.insertMany([
{
employeeId: employeeAravis._id,
startedAt: new Date(Date.now() - 57600000),
endedAt: new Date(Date.now() - 43200000),
name: 'shift1'
},
{
employeeId: employeeAravis._id,
startedAt: new Date(Date.now() - 28800000),
endedAt: new Date(Date.now() - 14400000),
name: 'shift2'
},
{
employeeId: employeeShasta._id,
startedAt: new Date(Date.now() - 14400000),
endedAt: new Date(),
name: 'shift3'
}
]);

const storeWithMostRecentShifts = await Store.
findOne({ location: 'Tashbaan' }).
populate('employees.mostRecentShift');

assert.deepStrictEqual(
storeWithMostRecentShifts.employees.map(e => e.mostRecentShift.name),
['shift2', 'shift3']
);
});

describe('strictPopulate', function() {
it('reports full path when throwing `strictPopulate` error with deep populate (gh-10923)', async function() {
const L2 = db.model('Test', new Schema({ name: String }));
Expand Down