Merge pull request #64 from strongloop/feature/fix-include

Fix the response for included models following relations
This commit is contained in:
Raymond Feng 2014-01-28 10:21:43 -08:00
commit 3b6070fe58
6 changed files with 95 additions and 33 deletions

View File

@ -542,11 +542,21 @@ DataAccessObject.find = function find(params, cb) {
obj._initProperties(d, false, params.fields); obj._initProperties(d, false, params.fields);
if (params && params.include && params.collect) { if (params && params.include) {
data[i] = obj.__cachedRelations[params.collect]; // Try to normalize the include
} else { var includes = params.include;
data[i] = obj; if(typeof includes === 'string') {
includes = [includes];
} else if(typeof includes === 'object') {
includes = Object.keys(includes);
}
includes.forEach(function (inc) {
// Promote the included model as a direct property
obj.__data[inc] = obj.__cachedRelations[inc];
});
delete obj.__data.__cachedRelations;
} }
data[i] = obj;
}); });
if (data && data.countBeforeLimit) { if (data && data.countBeforeLimit) {
data.countBeforeLimit = data.countBeforeLimit; data.countBeforeLimit = data.countBeforeLimit;

View File

@ -1,3 +1,7 @@
var utils = require('./utils');
var isPlainObject = utils.isPlainObject;
var defineCachedRelations = utils.defineCachedRelations;
/** /**
* Include mixin for ./model.js * Include mixin for ./model.js
*/ */
@ -29,8 +33,8 @@ Inclusion.include = function (objects, include, cb) {
var self = this; var self = this;
if ( if (
(include.constructor.name == 'Array' && include.length == 0) || !include || (Array.isArray(include) && include.length === 0) ||
(include.constructor.name == 'Object' && Object.keys(include).length == 0) (isPlainObject(include) && Object.keys(include).length === 0)
) { ) {
cb(null, objects); cb(null, objects);
return; return;
@ -48,7 +52,7 @@ Inclusion.include = function (objects, include, cb) {
nbCallbacks++; nbCallbacks++;
callback(function () { callback(function () {
nbCallbacks--; nbCallbacks--;
if (nbCallbacks == 0) { if (nbCallbacks === 0) {
cb(null, objects); cb(null, objects);
} }
}); });
@ -61,7 +65,7 @@ Inclusion.include = function (objects, include, cb) {
if (typeof ij === 'string') { if (typeof ij === 'string') {
ij = [ij]; ij = [ij];
} }
if (ij.constructor.name === 'Object') { if (isPlainObject(ij)) {
var newIj = []; var newIj = [];
for (var key in ij) { for (var key in ij) {
var obj = {}; var obj = {};
@ -76,12 +80,13 @@ Inclusion.include = function (objects, include, cb) {
function processIncludeItem(objs, include, keyVals, objsByKeys) { function processIncludeItem(objs, include, keyVals, objsByKeys) {
var relations = self.relations; var relations = self.relations;
if (include.constructor.name === 'Object') { var relationName, subInclude;
var relationName = Object.keys(include)[0]; if (isPlainObject(include)) {
var subInclude = include[relationName]; relationName = Object.keys(include)[0];
subInclude = include[relationName];
} else { } else {
var relationName = include; relationName = include;
var subInclude = []; subInclude = [];
} }
var relation = relations[relationName]; var relation = relations[relationName];
@ -89,7 +94,7 @@ Inclusion.include = function (objects, include, cb) {
return function () { return function () {
cb(new Error('Relation "' + relationName + '" is not defined for ' cb(new Error('Relation "' + relationName + '" is not defined for '
+ self.modelName + ' model')); + self.modelName + ' model'));
} };
} }
var req = {'where': {}}; var req = {'where': {}};
@ -117,18 +122,17 @@ Inclusion.include = function (objects, include, cb) {
} }
} }
req['where'][relation.keyTo] = {inq: inValues}; req.where[relation.keyTo] = {inq: inValues};
req['include'] = subInclude; req.include = subInclude;
return function (cb) { return function (cb) {
relation.modelTo.find(req, function (err, objsIncluded) { relation.modelTo.find(req, function (err, objsIncluded) {
var objectsFrom, j;
for (var i = 0; i < objsIncluded.length; i++) { for (var i = 0; i < objsIncluded.length; i++) {
delete keysToBeProcessed[objsIncluded[i][relation.keyTo]]; delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]]; objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
for (var j = 0; j < objectsFrom.length; j++) { for (j = 0; j < objectsFrom.length; j++) {
if (!objectsFrom[j].__cachedRelations) { defineCachedRelations(objectsFrom[j]);
objectsFrom[j].__cachedRelations = {};
}
if (relation.multiple) { if (relation.multiple) {
if (!objectsFrom[j].__cachedRelations[relationName]) { if (!objectsFrom[j].__cachedRelations[relationName]) {
objectsFrom[j].__cachedRelations[relationName] = []; objectsFrom[j].__cachedRelations[relationName] = [];
@ -142,11 +146,9 @@ Inclusion.include = function (objects, include, cb) {
// No relation have been found for these keys // No relation have been found for these keys
for (var key in keysToBeProcessed) { for (var key in keysToBeProcessed) {
var objectsFrom = objsByKeys[relation.keyFrom][key]; objectsFrom = objsByKeys[relation.keyFrom][key];
for (var j = 0; j < objectsFrom.length; j++) { for (j = 0; j < objectsFrom.length; j++) {
if (!objectsFrom[j].__cachedRelations) { defineCachedRelations(objectsFrom[j]);
objectsFrom[j].__cachedRelations = {};
}
objectsFrom[j].__cachedRelations[relationName] = objectsFrom[j].__cachedRelations[relationName] =
relation.multiple ? [] : null; relation.multiple ? [] : null;
} }
@ -158,5 +160,5 @@ Inclusion.include = function (objects, include, cb) {
return null; return null;
} }
} };

View File

@ -76,8 +76,8 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
value: {} value: {}
}); });
if (data['__cachedRelations']) { if (data.__cachedRelations) {
this.__cachedRelations = data['__cachedRelations']; this.__cachedRelations = data.__cachedRelations;
} }
// Check if the strict option is set to false for the model // Check if the strict option is set to false for the model

View File

@ -1,3 +1,5 @@
var utils = require('./utils');
var defineCachedRelations = utils.defineCachedRelations;
/** /**
* Module exports * Module exports
*/ */
@ -54,14 +56,12 @@ function defineScope(cls, targetClass, name, params, methods) {
throw new Error('Method can be only called with one or two arguments'); throw new Error('Method can be only called with one or two arguments');
} }
if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) { if (!this.__cachedRelations || (this.__cachedRelations[name] === undefined) || actualRefresh) {
var self = this; var self = this;
var params = mergeParams(actualCond, caller._scope); var params = mergeParams(actualCond, caller._scope);
return targetClass.find(params, function (err, data) { return targetClass.find(params, function (err, data) {
if (!err && saveOnCache) { if (!err && saveOnCache) {
if (!self.__cachedRelations) { defineCachedRelations(self);
self.__cachedRelations = {};
}
self.__cachedRelations[name] = data; self.__cachedRelations[name] = data;
} }
cb(err, data); cb(err, data);

View File

@ -4,6 +4,8 @@ exports.selectFields = selectFields;
exports.removeUndefined = removeUndefined; exports.removeUndefined = removeUndefined;
exports.parseSettings = parseSettings; exports.parseSettings = parseSettings;
exports.mergeSettings = mergeSettings; exports.mergeSettings = mergeSettings;
exports.isPlainObject = isPlainObject;
exports.defineCachedRelations = defineCachedRelations;
var traverse = require('traverse'); var traverse = require('traverse');
@ -176,4 +178,29 @@ function mergeSettings(target, src) {
} }
return dst; return dst;
}
/**
* Define an non-enumerable __cachedRelations property
* @param {Object} obj The obj to receive the __cachedRelations
*/
function defineCachedRelations(obj) {
if (!obj.__cachedRelations) {
Object.defineProperty(obj, '__cachedRelations', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
}
}
/**
* Check if the argument is plain object
* @param {*) obj The obj value
* @returns {boolean}
*/
function isPlainObject(obj) {
return (typeof obj === 'object') && (obj !== null)
&& (obj.constructor === Object);
} }

View File

@ -13,6 +13,12 @@ describe('include', function () {
passports.length.should.be.ok; passports.length.should.be.ok;
passports.forEach(function (p) { passports.forEach(function (p) {
p.__cachedRelations.should.have.property('owner'); p.__cachedRelations.should.have.property('owner');
// The relation should be promoted as the 'owner' property
p.should.have.property('owner');
// The __cachedRelations should be removed from json output
p.toJSON().should.not.have.property('__cachedRelations');
var owner = p.__cachedRelations.owner; var owner = p.__cachedRelations.owner;
if (!p.ownerId) { if (!p.ownerId) {
should.not.exist(owner); should.not.exist(owner);
@ -31,6 +37,11 @@ describe('include', function () {
should.exist(users); should.exist(users);
users.length.should.be.ok; users.length.should.be.ok;
users.forEach(function (u) { users.forEach(function (u) {
// The relation should be promoted as the 'owner' property
u.should.have.property('posts');
// The __cachedRelations should be removed from json output
u.toJSON().should.not.have.property('__cachedRelations');
u.__cachedRelations.should.have.property('posts'); u.__cachedRelations.should.have.property('posts');
u.__cachedRelations.posts.forEach(function (p) { u.__cachedRelations.posts.forEach(function (p) {
p.userId.should.equal(u.id); p.userId.should.equal(u.id);
@ -47,6 +58,12 @@ describe('include', function () {
passports.length.should.be.ok; passports.length.should.be.ok;
passports.forEach(function (p) { passports.forEach(function (p) {
p.__cachedRelations.should.have.property('owner'); p.__cachedRelations.should.have.property('owner');
// The relation should be promoted as the 'owner' property
p.should.have.property('owner');
// The __cachedRelations should be removed from json output
p.toJSON().should.not.have.property('__cachedRelations');
var user = p.__cachedRelations.owner; var user = p.__cachedRelations.owner;
if (!p.ownerId) { if (!p.ownerId) {
should.not.exist(user); should.not.exist(user);
@ -97,6 +114,12 @@ describe('include', function () {
should.exist(users); should.exist(users);
users.length.should.be.ok; users.length.should.be.ok;
users.forEach(function (user) { users.forEach(function (user) {
// The relation should be promoted as the 'owner' property
user.should.have.property('posts');
user.should.have.property('passports');
// The __cachedRelations should be removed from json output
user.toJSON().should.not.have.property('__cachedRelations');
user.__cachedRelations.should.have.property('posts'); user.__cachedRelations.should.have.property('posts');
user.__cachedRelations.should.have.property('passports'); user.__cachedRelations.should.have.property('passports');
user.__cachedRelations.posts.forEach(function (p) { user.__cachedRelations.posts.forEach(function (p) {