loopback-datasource-juggler/lib/include.js

228 lines
6.2 KiB
JavaScript
Raw Normal View History

2014-03-13 23:43:38 +00:00
var async = require('async');
var utils = require('./utils');
var isPlainObject = utils.isPlainObject;
var defineCachedRelations = utils.defineCachedRelations;
2014-10-10 10:28:39 +00:00
/*!
* Normalize the include to be an array
* @param include
* @returns {*}
*/
function normalizeInclude(include) {
var newInclude;
2014-10-10 10:28:39 +00:00
if (typeof include === 'string') {
return [include];
} else if (isPlainObject(include)) {
// Build an array of key/value pairs
newInclude = [];
2014-10-10 12:32:14 +00:00
var rel = include.rel || include.relation;
var obj = {};
if (typeof rel === 'string') {
2014-10-10 12:32:14 +00:00
obj[rel] = new IncludeScope(include.scope);
2014-10-10 10:28:39 +00:00
newInclude.push(obj);
} else {
for (var key in include) {
obj[key] = include[key];
newInclude.push(obj);
}
}
return newInclude;
} else if (Array.isArray(include)) {
newInclude = [];
for (var i = 0, n = include.length; i < n; i++) {
var subIncludes = normalizeInclude(include[i]);
newInclude = newInclude.concat(subIncludes);
}
return newInclude;
2014-10-10 10:28:39 +00:00
} else {
return include;
}
}
function IncludeScope(scope) {
this._scope = utils.deepMerge({}, scope || {});
if (this._scope.include) {
this._include = normalizeInclude(this._scope.include);
delete this._scope.include;
} else {
this._include = null;
}
};
IncludeScope.prototype.conditions = function() {
return utils.deepMerge({}, this._scope);
};
IncludeScope.prototype.include = function() {
return this._include;
};
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
/**
* Normalize includes - used in DataAccessObject
*
*/
Inclusion.normalizeInclude = normalizeInclude;
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
2014-06-18 23:42:00 +00:00
* @param {String|Object|Array} include Which relations to load.
2014-03-12 23:28:46 +00:00
* @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;
2014-03-13 23:43:38 +00:00
if (!include || (Array.isArray(include) && include.length === 0) ||
(isPlainObject(include) && Object.keys(include).length === 0)) {
// The objects are empty
return process.nextTick(function() {
cb && cb(null, objects);
});
2014-01-24 17:09:53 +00:00
}
2014-03-13 23:43:38 +00:00
include = normalizeInclude(include);
2014-03-13 23:43:38 +00:00
async.each(include, function(item, callback) {
processIncludeItem(objects, item, callback);
}, function(err) {
cb && cb(err, objects);
});
function processIncludeItem(objs, include, cb) {
2014-01-24 17:09:53 +00:00
var relations = self.relations;
2014-10-10 10:28:39 +00:00
var relationName;
var subInclude = null, scope = null;
if (isPlainObject(include)) {
relationName = Object.keys(include)[0];
2014-10-10 10:28:39 +00:00
if (include[relationName] instanceof IncludeScope) {
scope = include[relationName];
subInclude = scope.include();
} else {
subInclude = include[relationName];
}
2014-01-24 17:09:53 +00:00
} else {
relationName = include;
2014-03-13 23:43:38 +00:00
subInclude = null;
2014-01-24 17:09:53 +00:00
}
2014-07-29 11:57:49 +00:00
var relation = relations[relationName];
2014-01-24 17:09:53 +00:00
if (!relation) {
cb(new Error('Relation "' + relationName + '" is not defined for '
+ self.modelName + ' model'));
return;
2013-04-11 23:23:34 +00:00
}
// Just skip if inclusion is disabled
if (relation.options.disableInclude) {
cb();
return;
}
2014-07-29 11:57:49 +00:00
2014-03-13 23:43:38 +00:00
// Calling the relation method for each object
async.each(objs, function (obj, callback) {
if(relation.type === 'belongsTo') {
// If the belongsTo relation doesn't have an owner
if(obj[relation.keyFrom] === null || obj[relation.keyFrom] === undefined) {
defineCachedRelations(obj);
// Set to null if the owner doesn't exist
obj.__cachedRelations[relationName] = null;
if(obj === inst) {
obj.__data[relationName] = null;
} else {
obj[relationName] = null;
}
2014-03-13 23:43:38 +00:00
return callback();
2013-04-11 23:23:34 +00:00
}
2014-01-24 17:09:53 +00:00
}
2014-03-13 23:43:38 +00:00
var inst = (obj instanceof self) ? obj : new self(obj);
// Calling the relation method on the instance
2014-10-10 10:28:39 +00:00
var related; // relation accessor function
2014-10-14 20:47:59 +00:00
if ((relation.multiple || relation.type === 'belongsTo') && scope) {
2014-10-10 10:28:39 +00:00
var includeScope = {};
var filter = scope.conditions();
// make sure not to miss any fields for sub includes
if (filter.fields && Array.isArray(subInclude) && relation.modelTo.relations) {
includeScope.fields = [];
subInclude.forEach(function(name) {
var rel = relation.modelTo.relations[name];
if (rel && rel.type === 'belongsTo') {
includeScope.fields.push(rel.keyFrom);
}
});
}
utils.mergeQuery(filter, includeScope, {fields: false});
2014-10-14 20:47:59 +00:00
2014-10-10 10:28:39 +00:00
related = inst[relationName].bind(inst, filter);
} else {
related = inst[relationName].bind(inst);
}
related(function (err, result) {
2014-03-13 23:43:38 +00:00
if (err) {
return callback(err);
} else {
2014-03-13 23:43:38 +00:00
defineCachedRelations(obj);
obj.__cachedRelations[relationName] = result;
if(obj === inst) {
obj.__data[relationName] = result;
obj.setStrict(false);
} else {
obj[relationName] = result;
}
2014-07-29 11:57:49 +00:00
2014-03-13 23:43:38 +00:00
if (subInclude && result) {
var subItems = relation.multiple ? result : [result];
// Recursively include the related models
relation.modelTo.include(subItems, subInclude, callback);
} else {
callback(null, result);
2014-01-24 17:09:53 +00:00
}
2014-03-13 23:43:38 +00:00
}
});
}, cb);
2014-01-24 17:09:53 +00:00
}
};
2013-04-11 23:23:34 +00:00