loopback-datasource-juggler/lib/include.js

175 lines
5.1 KiB
JavaScript
Raw Normal View History

var utils = require('./utils');
var isPlainObject = utils.isPlainObject;
var defineCachedRelations = utils.defineCachedRelations;
2014-03-12 23:28:46 +00:00
/*!
2013-04-11 23:23:34 +00:00
* Include mixin for ./model.js
*/
2013-05-28 05:20:30 +00:00
module.exports = Inclusion;
2014-03-12 23:28:46 +00:00
/**
* Inclusion - Model mixin.
*
* @class
*/
2013-05-28 05:20:30 +00:00
function Inclusion() {
}
2013-04-11 23:23:34 +00:00
/**
2014-03-12 23:28:46 +00:00
* Enables you to load relations of several objects and optimize numbers of requests.
2013-04-11 23:23:34 +00:00
*
* Examples:
*
2014-03-12 23:28:46 +00:00
* Load all users' posts with only one additional request:
* `User.include(users, 'posts', function() {});`
* Or
* `User.include(users, ['posts'], function() {});`
*
* Load all users posts and passports with two additional requests:
* `User.include(users, ['posts', 'passports'], function() {});`
*
* Load all passports owner (users), and all posts of each owner loaded:
*```Passport.include(passports, {owner: 'posts'}, function() {});
*``` Passport.include(passports, {owner: ['posts', 'passports']});
*``` Passport.include(passports, {owner: [{posts: 'images'}, 'passports']});
2013-04-11 23:23:34 +00:00
*
2014-03-12 23:28:46 +00:00
* @param {Array} objects Array of instances
* @param {String}, {Object} or {Array} include Which relations to load.
* @param {Function} cb Callback called when relations are loaded
*
2013-04-11 23:23:34 +00:00
*/
2013-05-28 05:20:30 +00:00
Inclusion.include = function (objects, include, cb) {
2014-01-24 17:09:53 +00:00
var self = this;
if (
!include || (Array.isArray(include) && include.length === 0) ||
(isPlainObject(include) && Object.keys(include).length === 0)
2014-01-24 17:09:53 +00:00
) {
cb(null, objects);
return;
}
include = processIncludeJoin(include);
var keyVals = {};
var objsByKeys = {};
var nbCallbacks = 0;
for (var i = 0; i < include.length; i++) {
var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys);
if (callback !== null) {
nbCallbacks++;
callback(function () {
nbCallbacks--;
if (nbCallbacks === 0) {
2014-01-24 17:09:53 +00:00
cb(null, objects);
}
});
} else {
cb(null, objects);
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
}
2013-04-11 23:23:34 +00:00
2014-01-24 17:09:53 +00:00
function processIncludeJoin(ij) {
if (typeof ij === 'string') {
ij = [ij];
}
if (isPlainObject(ij)) {
2014-01-24 17:09:53 +00:00
var newIj = [];
for (var key in ij) {
var obj = {};
obj[key] = ij[key];
newIj.push(obj);
}
return newIj;
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
return ij;
}
function processIncludeItem(objs, include, keyVals, objsByKeys) {
var relations = self.relations;
var relationName, subInclude;
if (isPlainObject(include)) {
relationName = Object.keys(include)[0];
subInclude = include[relationName];
2014-01-24 17:09:53 +00:00
} else {
relationName = include;
subInclude = [];
2014-01-24 17:09:53 +00:00
}
var relation = relations[relationName];
2013-04-11 23:23:34 +00:00
2014-01-24 17:09:53 +00:00
if (!relation) {
return function () {
cb(new Error('Relation "' + relationName + '" is not defined for '
+ self.modelName + ' model'));
};
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
var req = {'where': {}};
2013-04-11 23:23:34 +00:00
2014-01-24 17:09:53 +00:00
if (!keyVals[relation.keyFrom]) {
objsByKeys[relation.keyFrom] = {};
objs.filter(Boolean).forEach(function (obj) {
if (!objsByKeys[relation.keyFrom][obj[relation.keyFrom]]) {
objsByKeys[relation.keyFrom][obj[relation.keyFrom]] = [];
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj);
});
keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]);
}
2013-04-11 23:23:34 +00:00
2014-01-24 17:09:53 +00:00
if (keyVals[relation.keyFrom].length > 0) {
// deep clone is necessary since inq seems to change the processed array
var keysToBeProcessed = {};
var inValues = [];
for (var j = 0; j < keyVals[relation.keyFrom].length; j++) {
keysToBeProcessed[keyVals[relation.keyFrom][j]] = true;
if (keyVals[relation.keyFrom][j] !== 'null'
&& keyVals[relation.keyFrom][j] !== 'undefined') {
inValues.push(keyVals[relation.keyFrom][j]);
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
}
req.where[relation.keyTo] = {inq: inValues};
req.include = subInclude;
2014-01-24 17:09:53 +00:00
return function (cb) {
relation.modelTo.find(req, function (err, objsIncluded) {
var objectsFrom, j;
2014-01-24 17:09:53 +00:00
for (var i = 0; i < objsIncluded.length; i++) {
delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
for (j = 0; j < objectsFrom.length; j++) {
defineCachedRelations(objectsFrom[j]);
2014-01-24 17:09:53 +00:00
if (relation.multiple) {
if (!objectsFrom[j].__cachedRelations[relationName]) {
objectsFrom[j].__cachedRelations[relationName] = [];
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]);
} else {
objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i];
}
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
}
// No relation have been found for these keys
for (var key in keysToBeProcessed) {
objectsFrom = objsByKeys[relation.keyFrom][key];
for (j = 0; j < objectsFrom.length; j++) {
defineCachedRelations(objectsFrom[j]);
2014-01-24 17:09:53 +00:00
objectsFrom[j].__cachedRelations[relationName] =
relation.multiple ? [] : null;
}
}
cb(err, objsIncluded);
});
};
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
return null;
}
};
2013-04-11 23:23:34 +00:00