Skip to content

Commit

Permalink
Merge pull request #6022 from Automattic/5970
Browse files Browse the repository at this point in the history
handle populating embedded discriminator paths
  • Loading branch information
vkarpov15 authored Jan 23, 2018
2 parents c28ce33 + cb48835 commit b4b59c4
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 94 deletions.
197 changes: 103 additions & 94 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var castUpdate = require('./services/query/castUpdate');
var discriminator = require('./services/model/discriminator');
var isPathSelectedInclusive = require('./services/projection/isPathSelectedInclusive');
var get = require('lodash.get');
var getSchemaTypes = require('./services/populate/getSchemaTypes');
var mpath = require('mpath');
var parallel = require('async/parallel');
var parallelLimit = require('async/parallelLimit');
Expand Down Expand Up @@ -3551,95 +3552,37 @@ function getModelsMapForPopulate(model, docs, options) {
var originalModel = options.model;
var isVirtual = false;
var isRefPathArray = false;

schema = model._getSchema(options.path);
var isUnderneathDocArray = schema && schema.$isUnderneathDocArray;
if (isUnderneathDocArray &&
options &&
options.options &&
options.options.sort) {
return new Error('Cannot populate with `sort` on path ' + options.path +
' because it is a subproperty of a document array');
}

if (schema && schema.caster) {
schema = schema.caster;
}

if (!schema && model.discriminators) {
discriminatorKey = model.schema.discriminatorMapping.key;
}

refPath = schema && schema.options && schema.options.refPath;
var modelSchema = model.schema;

for (i = 0; i < len; i++) {
doc = docs[i];

if (refPath) {
modelNames = utils.getValue(refPath, doc);
isRefPathArray = Array.isArray(modelNames);
} else {
if (!modelNameFromQuery) {
var modelForCurrentDoc = model;
var schemaForCurrentDoc;

if (!schema && discriminatorKey) {
modelForFindSchema = utils.getValue(discriminatorKey, doc);

if (modelForFindSchema) {
try {
modelForCurrentDoc = model.db.model(modelForFindSchema);
} catch (error) {
return error;
}

schemaForCurrentDoc = modelForCurrentDoc._getSchema(options.path);

if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
schemaForCurrentDoc = schemaForCurrentDoc.caster;
}
}
} else {
schemaForCurrentDoc = schema;
}
var virtual = modelForCurrentDoc.schema._getVirtual(options.path);

var ref;
if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
modelNames = [ref];
} else if ((ref = get(virtual, 'options.ref')) != null) {
if (typeof ref === 'function') {
ref = ref.call(doc, doc);
}

// When referencing nested arrays, the ref should be an Array
// of modelNames.
if (Array.isArray(ref)) {
modelNames = ref;
} else {
modelNames = [ref];
}
schema = getSchemaTypes(modelSchema, doc, options.path);
var isUnderneathDocArray = schema && schema.$isUnderneathDocArray;
if (isUnderneathDocArray &&
options &&
options.options &&
options.options.sort) {
return new Error('Cannot populate with `sort` on path ' + options.path +
' because it is a subproperty of a document array');
}

isVirtual = true;
} else {
// We may have a discriminator, in which case we don't want to
// populate using the base model by default
modelNames = discriminatorKey ? null : [model.modelName];
if (Array.isArray(schema)) {
for (var j = 0; j < schema.length; ++j) {
var _modelNames = _getModelNames(doc, schema[j]);
if (!_modelNames) {
continue;
}
} else {
modelNames = [modelNameFromQuery]; // query options
modelNames = (modelNames || []).concat(_modelNames);
}
} else {
modelNames = _getModelNames(doc, schema);
if (!modelNames) {
continue;
}
}

if (!modelNames) {
continue;
}

if (!Array.isArray(modelNames)) {
modelNames = [modelNames];
}

virtual = model.schema._getVirtual(options.path);
var virtual = model.schema._getVirtual(options.path);
var localField;
if (virtual && virtual.options) {
var virtualPrefix = virtual.$nestedSchemaPath ?
Expand Down Expand Up @@ -3729,6 +3672,86 @@ function getModelsMapForPopulate(model, docs, options) {
}
}

function _getModelNames(doc, schema) {
var modelNames;

if (schema && schema.caster) {
schema = schema.caster;
}

if (!schema && model.discriminators) {
discriminatorKey = model.schema.discriminatorMapping.key;
}

refPath = schema && schema.options && schema.options.refPath;

if (refPath) {
modelNames = utils.getValue(refPath, doc);
isRefPathArray = Array.isArray(modelNames);
} else {
if (!modelNameFromQuery) {
var modelForCurrentDoc = model;
var schemaForCurrentDoc;

if (!schema && discriminatorKey) {
modelForFindSchema = utils.getValue(discriminatorKey, doc);

if (modelForFindSchema) {
try {
modelForCurrentDoc = model.db.model(modelForFindSchema);
} catch (error) {
return error;
}

schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path);

if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
schemaForCurrentDoc = schemaForCurrentDoc.caster;
}
}
} else {
schemaForCurrentDoc = schema;
}
var virtual = modelForCurrentDoc.schema._getVirtual(options.path);

var ref;
if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
modelNames = [ref];
} else if ((ref = get(virtual, 'options.ref')) != null) {
if (typeof ref === 'function') {
ref = ref.call(doc, doc);
}

// When referencing nested arrays, the ref should be an Array
// of modelNames.
if (Array.isArray(ref)) {
modelNames = ref;
} else {
modelNames = [ref];
}

isVirtual = true;
} else {
// We may have a discriminator, in which case we don't want to
// populate using the base model by default
modelNames = discriminatorKey ? null : [model.modelName];
}
} else {
modelNames = [modelNameFromQuery]; // query options
}
}

if (!modelNames) {
return;
}

if (!Array.isArray(modelNames)) {
modelNames = [modelNames];
}

return modelNames;
}

return map;
}

Expand Down Expand Up @@ -3857,20 +3880,6 @@ function isDoc(doc) {
return true;
}

/**
* Finds the schema for `path`. This is different than
* calling `schema.path` as it also resolves paths with
* positional selectors (something.$.another.$.path).
*
* @param {String} path
* @return {Schema}
* @api private
*/

Model._getSchema = function _getSchema(path) {
return this.schema._getSchema(path);
};

/*!
* Compiler utility.
*
Expand Down
3 changes: 3 additions & 0 deletions lib/services/model/discriminator.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,11 @@ module.exports = function discriminator(model, name, schema) {

if (!model.schema.discriminatorMapping) {
model.schema.discriminatorMapping = {key: key, value: null, isRoot: true};
model.schema.discriminators = {};
}

model.schema.discriminators[name] = schema;

if (model.discriminators[name]) {
throw new Error('Discriminator with name "' + name + '" already exists');
}
Expand Down
115 changes: 115 additions & 0 deletions lib/services/populate/getSchemaTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use strict';

/*!
* ignore
*/

var Mixed = require('../../schema/mixed');
var mpath = require('mpath');

/*!
* @param {Schema} schema
* @param {Object} doc POJO
* @param {string} path
*/

module.exports = function getSchemaTypes(schema, doc, path) {
var pathschema = schema.path(path);

if (pathschema) {
return pathschema;
}

function search(parts, schema) {
var p = parts.length + 1;
var foundschema;
var trypath;

while (p--) {
trypath = parts.slice(0, p).join('.');
foundschema = schema.path(trypath);
if (foundschema) {
if (foundschema.caster) {
// array of Mixed?
if (foundschema.caster instanceof Mixed) {
return foundschema.caster;
}

var schemas = null;
if (doc != null && foundschema.schema != null && foundschema.schema.discriminators != null) {
var discriminators = foundschema.schema.discriminators;
var keys = mpath.get(trypath + '.' + foundschema.schema.options.discriminatorKey,
doc);
schemas = Object.keys(discriminators).
reduce(function(cur, discriminator) {
if (keys.indexOf(discriminator) !== -1) {
cur.push(discriminators[discriminator]);
}
return cur;
}, []);
}

// Now that we found the array, we need to check if there
// are remaining document paths to look up for casting.
// Also we need to handle array.$.path since schema.path
// doesn't work for that.
// If there is no foundschema.schema we are dealing with
// a path like array.$
if (p !== parts.length && foundschema.schema) {
var ret;
if (parts[p] === '$') {
if (p + 1 === parts.length) {
// comments.$
return foundschema;
}
// comments.$.comments.$.title
ret = search(parts.slice(p + 1), schema);
if (ret) {
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
!foundschema.schema.$isSingleNested;
}
return ret;
}

if (schemas != null && schemas.length > 0) {
ret = [];
for (var i = 0; i < schemas.length; ++i) {
var _ret = search(parts.slice(p), schemas[i]);
if (_ret != null) {
_ret.$isUnderneathDocArray = _ret.$isUnderneathDocArray ||
!foundschema.schema.$isSingleNested;
if (_ret.$isUnderneathDocArray) {
ret.$isUnderneathDocArray = true;
}
ret.push(_ret);
}
}
return ret;
} else {
ret = search(parts.slice(p), foundschema.schema);

if (ret) {
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
!foundschema.schema.$isSingleNested;
}

return ret;
}
}
}

return foundschema;
}
}
}

// look for arrays
var parts = path.split('.');
for (var i = 0; i < parts.length; ++i) {
if (parts[i] === '$') {
// Re: gh-5628, because `schema.path()` doesn't take $ into account.
parts[i] = '0';
}
}
return search(parts, schema);
};
Loading

0 comments on commit b4b59c4

Please sign in to comment.