Skip to content

Commit

Permalink
fix(populate): Apply dynamic ref getter on sub-document instead of pa…
Browse files Browse the repository at this point in the history
…rent document

Fix Automattic#12363
  • Loading branch information
Hybrid-Force committed Sep 17, 2022
1 parent 586c86f commit 066c024
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 95 deletions.
190 changes: 97 additions & 93 deletions lib/helpers/populate/getModelsMapForPopulate.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,114 +355,118 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
const virtual = _virtualRes.virtual;

for (const doc of docs) {
let modelNames = null;
const data = {};

// localField and foreignField
let localField;
const virtualPrefix = _virtualRes.nestedSchemaPath ?
_virtualRes.nestedSchemaPath + '.' : '';
if (typeof virtual.options.localField === 'function') {
localField = virtualPrefix + virtual.options.localField.call(doc, doc);
} else if (Array.isArray(virtual.options.localField)) {
localField = virtual.options.localField.map(field => virtualPrefix + field);
} else {
localField = virtualPrefix + virtual.options.localField;
}
data.count = virtual.options.count;

if (virtual.options.skip != null && !options.hasOwnProperty('skip')) {
options.skip = virtual.options.skip;
}
if (virtual.options.limit != null && !options.hasOwnProperty('limit')) {
options.limit = virtual.options.limit;
}
if (virtual.options.perDocumentLimit != null && !options.hasOwnProperty('perDocumentLimit')) {
options.perDocumentLimit = virtual.options.perDocumentLimit;
}
let foreignField = virtual.options.foreignField;
// Use the correct target doc/sub-doc for dynamic ref on nested schema. See gh-12363
let subDocs = _virtualRes.nestedSchemaPath ?
utils.getValue(_virtualRes.nestedSchemaPath, doc) : doc;
subDocs = Array.isArray(subDocs) ? subDocs : subDocs ? [subDocs] : [];
for (const subDoc of subDocs) {
let modelNames = null;
const data = {};

// localField and foreignField
let localField = virtual.options.localField;
const virtualPrefix = _virtualRes.nestedSchemaPath ?
_virtualRes.nestedSchemaPath + '.' : '';
if (typeof localField === 'function') {
localField = localField.call(subDoc, subDoc);
}
if (Array.isArray(localField)) {
localField = localField.map(field => virtualPrefix + field);
} else {
localField = virtualPrefix + localField;
}
data.count = virtual.options.count;

if (!localField || !foreignField) {
return new MongooseError('If you are populating a virtual, you must set the ' +
'localField and foreignField options');
}
if (virtual.options.skip != null && !options.hasOwnProperty('skip')) {
options.skip = virtual.options.skip;
}
if (virtual.options.limit != null && !options.hasOwnProperty('limit')) {
options.limit = virtual.options.limit;
}
if (virtual.options.perDocumentLimit != null && !options.hasOwnProperty('perDocumentLimit')) {
options.perDocumentLimit = virtual.options.perDocumentLimit;
}
let foreignField = virtual.options.foreignField;

if (typeof localField === 'function') {
localField = localField.call(doc, doc);
}
if (typeof foreignField === 'function') {
foreignField = foreignField.call(doc, doc);
}
if (!localField || !foreignField) {
return new MongooseError('If you are populating a virtual, you must set the ' +
'localField and foreignField options');
}

data.isRefPath = false;
if (typeof foreignField === 'function') {
foreignField = foreignField.call(subDoc, subDoc);
}

// `justOne = null` means we don't know from the schema whether the end
// result should be an array or a single doc. This can result from
// populating a POJO using `Model.populate()`
let justOne = null;
if ('justOne' in options && options.justOne !== void 0) {
justOne = options.justOne;
}
data.isRefPath = false;

if (virtual.options.refPath) {
modelNames =
modelNamesFromRefPath(virtual.options.refPath, doc, options.path);
justOne = !!virtual.options.justOne;
data.isRefPath = true;
} else if (virtual.options.ref) {
let normalizedRef;
if (typeof virtual.options.ref === 'function' && !virtual.options.ref[modelSymbol]) {
normalizedRef = virtual.options.ref.call(doc, doc);
} else {
normalizedRef = virtual.options.ref;
// `justOne = null` means we don't know from the schema whether the end
// result should be an array or a single doc. This can result from
// populating a POJO using `Model.populate()`
let justOne = null;
if ('justOne' in options && options.justOne !== void 0) {
justOne = options.justOne;
}
justOne = !!virtual.options.justOne;
// When referencing nested arrays, the ref should be an Array
// of modelNames.
if (Array.isArray(normalizedRef)) {
modelNames = normalizedRef;
} else {
modelNames = [normalizedRef];
}
}

data.isVirtual = true;
data.virtual = virtual;
data.justOne = justOne;
if (virtual.options.refPath) {
modelNames =
modelNamesFromRefPath(virtualPrefix + virtual.options.refPath, doc, options.path);
justOne = !!virtual.options.justOne;
data.isRefPath = true;
} else if (virtual.options.ref) {
let normalizedRef;
if (typeof virtual.options.ref === 'function' && !virtual.options.ref[modelSymbol]) {
normalizedRef = virtual.options.ref.call(subDoc, subDoc);
} else {
normalizedRef = virtual.options.ref;
}
justOne = !!virtual.options.justOne;
// When referencing nested arrays, the ref should be an Array
// of modelNames.
if (Array.isArray(normalizedRef)) {
modelNames = normalizedRef;
} else {
modelNames = [normalizedRef];
}
}

// `match`
let match = get(options, 'match', null) ||
get(data, 'virtual.options.match', null) ||
get(data, 'virtual.options.options.match', null);
data.isVirtual = true;
data.virtual = virtual;
data.justOne = justOne;

let hasMatchFunction = typeof match === 'function';
if (hasMatchFunction) {
match = match.call(doc, doc);
}
// `match`
let match = get(options, 'match', null) ||
get(data, 'virtual.options.match', null) ||
get(data, 'virtual.options.options.match', null);

if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
match = Object.assign({}, match);
for (let i = 1; i < localField.length; ++i) {
match[foreignField[i]] = convertTo_id(mpath.get(localField[i], doc, lookupLocalFields), model.schema);
hasMatchFunction = true;
let hasMatchFunction = typeof match === 'function';
if (hasMatchFunction) {
match = match.call(subDoc, subDoc);
}

localField = localField[0];
foreignField = foreignField[0];
}
if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
match = Object.assign({}, match);
for (let i = 1; i < localField.length; ++i) {
match[foreignField[i]] = convertTo_id(mpath.get(localField[i], doc, lookupLocalFields), model.schema);
hasMatchFunction = true;
}

data.localField = localField;
data.foreignField = foreignField;
data.match = match;
data.hasMatchFunction = hasMatchFunction;
localField = localField[0];
foreignField = foreignField[0];
}

// Get local fields
const ret = _getLocalFieldValues(doc, localField, model, options, virtual);
data.localField = localField;
data.foreignField = foreignField;
data.match = match;
data.hasMatchFunction = hasMatchFunction;

try {
addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc);
} catch (err) {
return err;
// Get local fields
const ret = _getLocalFieldValues(doc, localField, model, options, virtual);

try {
addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc);
} catch (err) {
return err;
}
}
}

Expand Down

0 comments on commit 066c024

Please sign in to comment.