219 lines
5.9 KiB
JavaScript
219 lines
5.9 KiB
JavaScript
var async = require('async');
|
|
var utils = require('./utils');
|
|
var isPlainObject = utils.isPlainObject;
|
|
var defineCachedRelations = utils.defineCachedRelations;
|
|
|
|
/*!
|
|
* Normalize the include to be an array
|
|
* @param include
|
|
* @returns {*}
|
|
*/
|
|
function normalizeInclude(include) {
|
|
if (typeof include === 'string') {
|
|
return [include];
|
|
} else if (isPlainObject(include)) {
|
|
// Build an array of key/value pairs
|
|
var newInclude = [];
|
|
if (typeof include.relation === 'string' && isPlainObject(include.scope)) {
|
|
var obj = {};
|
|
obj[include.relation] = new IncludeScope(include.scope);
|
|
newInclude.push(obj);
|
|
} else {
|
|
for (var key in include) {
|
|
var obj = {};
|
|
obj[key] = include[key];
|
|
newInclude.push(obj);
|
|
}
|
|
}
|
|
return newInclude;
|
|
} 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;
|
|
};
|
|
|
|
/*!
|
|
* Include mixin for ./model.js
|
|
*/
|
|
module.exports = Inclusion;
|
|
|
|
/**
|
|
* Inclusion - Model mixin.
|
|
*
|
|
* @class
|
|
*/
|
|
|
|
function Inclusion() {
|
|
}
|
|
|
|
/**
|
|
* Normalize includes - used in DataAccessObject
|
|
*
|
|
*/
|
|
|
|
Inclusion.normalizeInclude = normalizeInclude;
|
|
|
|
/**
|
|
* Enables you to load relations of several objects and optimize numbers of requests.
|
|
*
|
|
* Examples:
|
|
*
|
|
* 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']});
|
|
*
|
|
* @param {Array} objects Array of instances
|
|
* @param {String|Object|Array} include Which relations to load.
|
|
* @param {Function} cb Callback called when relations are loaded
|
|
*
|
|
*/
|
|
Inclusion.include = function (objects, include, cb) {
|
|
var self = this;
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
include = normalizeInclude(include);
|
|
|
|
async.each(include, function(item, callback) {
|
|
processIncludeItem(objects, item, callback);
|
|
}, function(err) {
|
|
cb && cb(err, objects);
|
|
});
|
|
|
|
function processIncludeItem(objs, include, cb) {
|
|
var relations = self.relations;
|
|
|
|
var relationName;
|
|
var subInclude = null, scope = null;
|
|
|
|
if (isPlainObject(include)) {
|
|
relationName = Object.keys(include)[0];
|
|
if (include[relationName] instanceof IncludeScope) {
|
|
scope = include[relationName];
|
|
subInclude = scope.include();
|
|
} else {
|
|
subInclude = include[relationName];
|
|
}
|
|
} else {
|
|
relationName = include;
|
|
subInclude = null;
|
|
}
|
|
|
|
var relation = relations[relationName];
|
|
if (!relation) {
|
|
cb(new Error('Relation "' + relationName + '" is not defined for '
|
|
+ self.modelName + ' model'));
|
|
return;
|
|
}
|
|
|
|
// Just skip if inclusion is disabled
|
|
if (relation.options.disableInclude) {
|
|
cb();
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
return callback();
|
|
}
|
|
}
|
|
|
|
var inst = (obj instanceof self) ? obj : new self(obj);
|
|
// Calling the relation method on the instance
|
|
|
|
var related; // relation accessor function
|
|
|
|
if (relation.multiple && scope) {
|
|
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});
|
|
related = inst[relationName].bind(inst, filter);
|
|
} else {
|
|
related = inst[relationName].bind(inst);
|
|
}
|
|
|
|
related(function (err, result) {
|
|
if (err) {
|
|
return callback(err);
|
|
} else {
|
|
|
|
defineCachedRelations(obj);
|
|
obj.__cachedRelations[relationName] = result;
|
|
|
|
if(obj === inst) {
|
|
obj.__data[relationName] = result;
|
|
obj.setStrict(false);
|
|
} else {
|
|
obj[relationName] = result;
|
|
}
|
|
|
|
if (subInclude && result) {
|
|
var subItems = relation.multiple ? result : [result];
|
|
// Recursively include the related models
|
|
relation.modelTo.include(subItems, subInclude, callback);
|
|
} else {
|
|
callback(null, result);
|
|
}
|
|
}
|
|
});
|
|
}, cb);
|
|
|
|
}
|
|
};
|
|
|