From 24ccb0ffc2f4ee5e4d80c57e6560cfc254a09f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Drouyer?= Date: Fri, 14 Dec 2012 16:28:29 +0100 Subject: [PATCH 1/8] added relations key --- lib/abstract-class.js | 14 ++++++++++++++ lib/schema.js | 1 + test/common_test.js | 24 +++++++++++++++++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/abstract-class.js b/lib/abstract-class.js index 12e6bf4f..a8cb79a5 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -666,6 +666,13 @@ AbstractClass.prototype.reset = function () { AbstractClass.hasMany = function hasMany(anotherClass, params) { var methodName = params.as; // or pluralize(anotherClass.modelName) var fk = params.foreignKey; + + this.relations[params['as']] = { + type: 'hasMany', + keyFrom: 'id', + keyTo: params['foreignKey'], + multiple: true + }; // each instance of this class should have method named // pluralize(anotherClass.modelName) // which is actually just anotherClass.all({where: {thisModelNameId: this.id}}, cb); @@ -733,6 +740,13 @@ AbstractClass.belongsTo = function (anotherClass, params) { var methodName = params.as; var fk = params.foreignKey; + this.relations[params['as']] = { + type: 'belongsTo', + keyFrom: params['foreignKey'], + keyTo: 'id', + multiple: false + }; + this.schema.defineForeignKey(this.modelName, fk); this.prototype['__finders__'] = this.prototype['__finders__'] || {}; diff --git a/lib/schema.js b/lib/schema.js index 78ded775..d316eaf3 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -157,6 +157,7 @@ Schema.prototype.define = function defineClass(className, properties, settings) hiddenProperty(NewClass, 'modelName', className); hiddenProperty(NewClass, 'cache', {}); hiddenProperty(NewClass, 'mru', []); + hiddenProperty(NewClass, 'relations', {}); // inherit AbstractClass methods for (var i in AbstractClass) { diff --git a/test/common_test.js b/test/common_test.js index 1dd955f9..adc87eb9 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -432,6 +432,29 @@ function testOrm(schema) { }); }); + if ( + !schema.name.match(/redis/) && + schema.name !== 'memory' && + schema.name !== 'neo4j' && + schema.name !== 'cradle' + ) + it('relations key is working', function (test) { + test.ok(User.relations, 'Relations key should be defined'); + test.ok(User.relations.posts, 'posts relation should exist on User'); + test.equal(User.relations.posts.type, 'hasMany', 'Type of hasMany relation is hasMany'); + test.equal(User.relations.posts.multiple, true, 'hasMany relations are multiple'); + test.equal(User.relations.posts.keyFrom, 'id', 'keyFrom is primary key of model table'); + test.equal(User.relations.posts.keyTo, 'userId', 'keyTo is foreign key of related model table'); + + test.ok(Post.relations, 'Relations key should be defined'); + test.ok(Post.relations.author, 'author relation should exist on Post'); + test.equal(Post.relations.author.type, 'belongsTo', 'Type of belongsTo relation is belongsTo'); + test.equal(Post.relations.author.multiple, false, 'belongsTo relations are not multiple'); + test.equal(Post.relations.author.keyFrom, 'userId', 'keyFrom is foreign key of model table'); + test.equal(Post.relations.author.keyTo, 'id', 'keyTo is primary key of related model table'); + test.done(); + }); + it('should handle hasMany relationship', function (test) { User.create(function (err, u) { if (err) return console.log(err); @@ -449,7 +472,6 @@ function testOrm(schema) { }); }); - if ( !schema.name.match(/redis/) && schema.name !== 'memory' && From c6bddf170a0a974e206b8ab555d773b6b6810b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Drouyer?= Date: Sun, 16 Dec 2012 18:05:36 +0100 Subject: [PATCH 2/8] added include functionnality to abstract class and mysql --- lib/abstract-class.js | 206 ++++++++++++++++++++++++++++++++++++++++++ lib/adapters/mysql.js | 17 +++- 2 files changed, 220 insertions(+), 3 deletions(-) diff --git a/lib/abstract-class.js b/lib/abstract-class.js index a8cb79a5..5d4a36a4 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -58,6 +58,10 @@ AbstractClass.prototype._initProperties = function (data, applySetters) { value: {} }); + if (data['__cachedRelations']) { + this.__cachedRelations = data['__cachedRelations']; + } + for (var i in data) this.__data[i] = this.__dataWas[i] = data[i]; if (applySetters && ctor.setter) { @@ -384,6 +388,202 @@ AbstractClass.count = function (where, cb) { this.schema.adapter.count(this.modelName, cb, where); }; +/** + * + * + * @param objects + * @param include + */ +AbstractClass.include = function (objects, include, callback, preprocessDataCallback) { + var self = this; + + if ((include.constructor.name == 'Array' && include.length == 0) || (include.constructor.name == 'Object' && Object.keys(include).length == 0)) { + callback(null, objects); + return; + } + + console.log('******************* OLD INCLUDE', include); + include = processIncludeJoin(include); + console.log('******************* NEW INCLUDE', include); + + var keyVals = {}; + var objsByKeys = {}; + + var nbCallbacks = 0; + for (var i = 0; i < include.length; i++) { + var cb = processIncludeItem(objects, include[i], keyVals, objsByKeys); + if (cb !== null) { + nbCallbacks++; + cb(function() { + nbCallbacks--; + if (nbCallbacks == 0) { + callback(null, objects); + } + }); + } + } + + + + /* + async.parallel(callbacks, function(err, results) { + callback(null, objs); + }); + */ + + function processIncludeJoin(ij) { + if (typeof ij === 'string') { + ij = [ij]; + } + if (ij.constructor.name === 'Object') { + var newIj = []; + for (var key in ij) { + var obj = {}; + obj[key] = ij[key]; + newIj.push(obj); + } + return newIj; + } + return ij; + } + + function processIncludeItem(objs, include, keyVals, objsByKeys) { + var relations = self.relations; + + if (include.constructor.name === 'Object') { + var relationName = Object.keys(include)[0]; + var subInclude = include[relationName]; + } else { + var relationName = include; + var subInclude = []; + } + var relation = relations[relationName]; + + var req = {'where': {}}; + var keysToBeProcessed = {}; + if (!keyVals[relation.keyFrom]) { + objsByKeys[relation.keyFrom] = {}; + for (var j = 0; j < objs.length; j++) { + keysToBeProcessed[objs[j][relation.keyFrom]] = true; + if (!objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]]) { + objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]] = []; + } + objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]].push(objs[j]); + } + keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]); + } + + if (keyVals[relation.keyFrom].length > 0) { + // deep clone is necessary since inq seems to change the processed array + var inValues = []; + for (var j = 0; j < keyVals[relation.keyFrom].length; j++) { + if (keyVals[relation.keyFrom][j] !== 'null') { + inValues.push(keyVals[relation.keyFrom][j]); + } + } + + req['where'][relation.keyTo] = {inq: inValues}; + req['include'] = subInclude; + + return function(cb) { + relation.modelTo.all(req, function(err, dataIncluded) { + var objsIncluded = preprocessDataCallback(relation.modelTo.modelName, dataIncluded); + for (var i = 0; i < objsIncluded.length; i++) { + delete keysToBeProcessed[objsIncluded[i][relation.keyTo]]; + var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]]; + for (var j = 0; j < objectsFrom.length; j++) { + if (!objectsFrom[j].__cachedRelations) { + objectsFrom[j].__cachedRelations = {}; + } + if (relation.multiple) { + if (!objectsFrom[j].__cachedRelations[relationName]) { + objectsFrom[j].__cachedRelations[relationName] = []; + } + objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]); + } else { + objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i]; + } + } + } + + // No relation have been found for these keys + for (var key in keysToBeProcessed) { + var objectsFrom = objsByKeys[relation.keyFrom][key]; + for (var j = 0; j < objectsFrom.length; j++) { + if (!objectsFrom[j].__cachedRelations) { + objectsFrom[j].__cachedRelations = {}; + } + objectsFrom[j].__cachedRelations[relationName] = relation.multiple ? [] : null; + } + } + cb(err, objsIncluded); + }); + }; + } + + + return null; + } + + + + /* + function processIncludeItem(model, objs, include, keyVals, objsByKeys) { + var relations = model.relations; + + if (include.constructor.name === 'Object') { + var relationName = Object.keys(include)[0]; + var relation = relations[relationName]; + var subInclude = include[relationName]; + } else { + var relationName = include; + var relation = relations[relationName]; + var subInclude = []; + } + + var req = {'where': {}}; + if (!keyVals[relation.keyFrom]) { + objsByKeys[relation.keyFrom] = {}; + for (var j = 0; j < objs.length; j++) { + if (!objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]]) { + objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]] = []; + } + objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]].push(objs[j]); + } + keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]); + } + req['where'][relation.keyTo] = {inq: keyVals[relation.keyFrom]}; + req['include'] = subInclude; + return function(cb) { + relation.model.all(req, function(err, dataIncluded) { + + var objsIncluded = dataIncluded.map(function (obj) { + return self.fromDatabase(relation.model.modelName, obj); + }); + + for (var i = 0; i < objsIncluded.length; i++) { + var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]]; + for (var j = 0; j < objectsFrom.length; j++) { + if (!objectsFrom[j].__cache) { + objectsFrom[j].__cache = {}; + } + if (relation.type == 'hasMany') { + if (!objectsFrom[j].__cache[relationName]) { + objectsFrom[j].__cache[relationName] = []; + } + objectsFrom[j].__cache[relationName].push(objsIncluded[i]); + } else { + objectsFrom[j].__cache[relationName] = objsIncluded[i]; + } + } + } + cb(err, dataIncluded); + }); + }; + } + */ +} + /** * Return string representation of class * @@ -671,6 +871,7 @@ AbstractClass.hasMany = function hasMany(anotherClass, params) { type: 'hasMany', keyFrom: 'id', keyTo: params['foreignKey'], + modelTo: anotherClass, multiple: true }; // each instance of this class should have method named @@ -744,6 +945,7 @@ AbstractClass.belongsTo = function (anotherClass, params) { type: 'belongsTo', keyFrom: params['foreignKey'], keyTo: 'id', + modelTo: anotherClass, multiple: false }; @@ -751,6 +953,10 @@ AbstractClass.belongsTo = function (anotherClass, params) { this.prototype['__finders__'] = this.prototype['__finders__'] || {}; this.prototype['__finders__'][methodName] = function (id, cb) { + if (id === null) { + cb(null, null); + return; + } anotherClass.find(id, function (err,inst) { if (err) return cb(err); if (!inst) return cb(null, null); diff --git a/lib/adapters/mysql.js b/lib/adapters/mysql.js index 5d464029..f57977da 100644 --- a/lib/adapters/mysql.js +++ b/lib/adapters/mysql.js @@ -224,9 +224,20 @@ MySQL.prototype.all = function all(model, filter, callback) { if (err) { return callback(err, []); } - callback(null, data.map(function (obj) { - return self.fromDatabase(model, obj); - })); + var preprocessDataCallback = function(model, data) { + data.map(function (obj) { + return self.fromDatabase(model, obj); + }); + return data; + }; + + var objs = preprocessDataCallback(model, data); + if (filter && filter.include) { + this._models[model].model.include(objs, filter.include, callback, preprocessDataCallback); + } else { + callback(null, objs); + } + }.bind(this)); return sql; From 3ed14b2ed07d6eb21df69612e00a4359dada9468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Drouyer?= Date: Sun, 16 Dec 2012 18:07:02 +0100 Subject: [PATCH 3/8] added tests for the include functionnality --- test/common_test.js | 253 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) diff --git a/test/common_test.js b/test/common_test.js index adc87eb9..ffb179ec 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -38,6 +38,26 @@ if (process.env.ONLY && !testPerformed) { performTestFor(process.env.ONLY); } +function clearAndCreate(model, data, callback) { + var createdItems = []; + model.destroyAll(function () { + nextItem(null, null); + }); + + var itemIndex = 0; + function nextItem(err, lastItem) { + if (lastItem !== null) { + createdItems.push(lastItem); + } + if (itemIndex >= data.length) { + callback(createdItems); + return; + } + model.create(data[itemIndex], nextItem); + itemIndex++; + } +} + function performTestFor(schemaName) { testPerformed = true; context(schemaName, function () { @@ -123,6 +143,7 @@ function testOrm(schema) { }); Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'}); + User.hasMany(Passport, {as: 'passports', foreignKey: 'ownerId'}); var user = new User; @@ -455,6 +476,7 @@ function testOrm(schema) { test.done(); }); + it('should handle hasMany relationship', function (test) { User.create(function (err, u) { if (err) return console.log(err); @@ -610,6 +632,237 @@ function testOrm(schema) { }; }); + if ( + !schema.name.match(/redis/) && + schema.name !== 'memory' && + schema.name !== 'neo4j' && + schema.name !== 'cradle' + ) + it('should handle include function', function (test) { + var createdUsers = []; + var createdPassports = []; + var createdPosts = []; + var context = null; + + createUsers(); + function createUsers() { + clearAndCreate( + User, + [ + {name: 'User A', age: 21}, + {name: 'User B', age: 22}, + {name: 'User C', age: 23}, + {name: 'User D', age: 24}, + {name: 'User E', age: 25} + ], + function(items) { + createdUsers = items; + createPassports(); + } + ); + } + + function createPassports() { + clearAndCreate( + Passport, + [ + {number: '1', ownerId: createdUsers[0].id}, + {number: '2', ownerId: createdUsers[1].id}, + {number: '3'} + ], + function(items) { + createdPassports = items; + createPosts(); + } + ); + } + + function createPosts() { + clearAndCreate( + Post, + [ + {title: 'Post A', userId: createdUsers[0].id}, + {title: 'Post B', userId: createdUsers[0].id}, + {title: 'Post C', userId: createdUsers[0].id}, + {title: 'Post D', userId: createdUsers[1].id}, + {title: 'Post E'} + ], + function(items) { + createdPosts = items; + makeTests(); + } + ); + } + + function makeTests() { + var unitTests = [ + function() { + context = ' (belongsTo simple string from passports to users)'; + Passport.all({include: 'owner'}, testPassportsUser); + }, + function() { + context = ' (belongsTo simple string from posts to users)'; + Post.all({include: 'author'}, testPostsUser); + }, + function() { + context = ' (belongsTo simple array)'; + Passport.all({include: ['owner']}, testPassportsUser); + }, + function() { + context = ' (hasMany simple string from users to posts)'; + User.all({include: 'posts'}, testUsersPosts); + }, + function() { + context = ' (hasMany simple string from users to passports)'; + User.all({include: 'passports'}, testUsersPassports); + }, + function() { + context = ' (hasMany simple array)'; + User.all({include: ['posts']}, testUsersPosts); + }, + function() { + context = ' (Passports - User - Posts in object)'; + Passport.all({include: {'owner': 'posts'}}, testPassportsUserPosts); + }, + function() { + context = ' (Passports - User - Posts in array)'; + Passport.all({include: [{'owner': 'posts'}]}, testPassportsUserPosts); + }, + function() { + context = ' (Passports - User - Posts - User)'; + Passport.all({include: {'owner': {'posts': 'author'}}}, testPassportsUserPosts); + }, + function() { + context = ' (User - Posts AND Passports)'; + User.all({include: ['posts', 'passports']}, testUsersPostsAndPassports); + } + ]; + + function testPassportsUser(err, passports, callback) { + testBelongsTo(passports, 'owner', callback); + } + + function testPostsUser(err, posts, callback) { + testBelongsTo(posts, 'author', callback); + } + + function testBelongsTo(items, relationName, callback) { + if (typeof callback === 'undefined') { + callback = nextUnitTest; + } + var nbInitialRequests = nbSchemaRequests; + var nbItemsRemaining = items.length; + + for (var i = 0; i < items.length; i++) { + testItem(items[i]); + } + + function testItem(item) { + var relation = item.constructor.relations[relationName]; + var modelNameFrom = item.constructor.modelName; + var modelNameTo = relation.modelTo.modelName; + item[relationName](function(err, relatedItem) { + if (relatedItem !== null) { + test.equal(relatedItem[relation.keyTo], item[relation.keyFrom], modelNameTo + '\'s instance match ' + modelNameFrom + '\'s instance' + context); + } else { + test.ok(item[relation.keyFrom] == null, 'User match passport even when user is null.' + context); + } + nbItemsRemaining--; + if (nbItemsRemaining == 0) { + requestsAreCounted && test.equal(nbSchemaRequests, nbInitialRequests, 'No more request have been executed for loading ' + relationName + ' relation' + context) + callback(); + } + }); + } + } + + function testUsersPosts(err, users, expectedUserNumber, callback) { + if (typeof expectedUserNumber === 'undefined') { + expectedUserNumber = 5; + } + //console.log(users.length, expectedUserNumber); + test.equal(users.length, expectedUserNumber, 'Exactly ' + expectedUserNumber + ' users returned by query' + context); + testHasMany(users, 'posts', callback); + } + + function testUsersPassports(err, users, callback) { + testHasMany(users, 'passports', callback); + } + + function testHasMany(items, relationName, callback) { + if (typeof callback === 'undefined') { + callback = nextUnitTest; + } + var nbInitialRequests = nbSchemaRequests; + var nbItemRemaining = items.length; + for (var i = 0; i < items.length; i++) { + testItem(items[i]); + } + + function testItem(item) { + var relation = item.constructor.relations[relationName]; + var modelNameFrom = item.constructor.modelName; + var modelNameTo = relation.modelTo.modelName; + item[relationName](function(err, relatedItems) { + for (var j = 0; j < relatedItems.length; j++) { + test.equal(relatedItems[j][relation.keyTo], item[relation.keyFrom], modelNameTo + '\'s instances match ' + modelNameFrom + '\'s instance' + context); + } + nbItemRemaining--; + if (nbItemRemaining == 0) { + requestsAreCounted && test.equal(nbSchemaRequests, nbInitialRequests, 'No more request have been executed for loading ' + relationName + ' relation' + context) + callback(); + } + }); + } + } + + function testPassportsUserPosts(err, passports) { + testPassportsUser(err, passports, function() { + var nbPassportsRemaining = passports.length; + for (var i = 0; i < passports.length; i++) { + if (passports[i].ownerId !== null) { + passports[i].owner(function(err, user) { + testUsersPosts(null, [user], 1, function() { + nextPassport(); + }); + }); + } else { + nextPassport(); + } + } + function nextPassport() { + nbPassportsRemaining-- + if (nbPassportsRemaining == 0) { + nextUnitTest(); + } + } + }); + } + + function testUsersPostsAndPassports(err, users) { + testUsersPosts(err, users, 5, function() { + testUsersPassports(err, users, function() { + nextUnitTest(); + }); + }); + } + + var testNum = 0; + function nextUnitTest() { + if (testNum >= unitTests.length) { + test.done(); + return; + } + unitTests[testNum](); + testNum++; + + } + + nextUnitTest(); + } + + }); + it('should destroy all records', function (test) { Post.destroyAll(function (err) { if (err) { From 175ad3141d2a56df906e73c0de1b886f934bad35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Drouyer?= Date: Sun, 16 Dec 2012 18:20:39 +0100 Subject: [PATCH 4/8] small fix on relations --- lib/abstract-class.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/abstract-class.js b/lib/abstract-class.js index 5d4a36a4..0d236606 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -402,9 +402,7 @@ AbstractClass.include = function (objects, include, callback, preprocessDataCall return; } - console.log('******************* OLD INCLUDE', include); include = processIncludeJoin(include); - console.log('******************* NEW INCLUDE', include); var keyVals = {}; var objsByKeys = {}; @@ -460,11 +458,10 @@ AbstractClass.include = function (objects, include, callback, preprocessDataCall var relation = relations[relationName]; var req = {'where': {}}; - var keysToBeProcessed = {}; + if (!keyVals[relation.keyFrom]) { objsByKeys[relation.keyFrom] = {}; for (var j = 0; j < objs.length; j++) { - keysToBeProcessed[objs[j][relation.keyFrom]] = true; if (!objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]]) { objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]] = []; } @@ -475,8 +472,10 @@ AbstractClass.include = function (objects, include, callback, preprocessDataCall 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') { inValues.push(keyVals[relation.keyFrom][j]); } From 0b91727a350627053f21d16eb0b15a471037a383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Drouyer?= Date: Sun, 16 Dec 2012 18:21:07 +0100 Subject: [PATCH 5/8] removed unused console log and comments --- test/common_test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/common_test.js b/test/common_test.js index ffb179ec..e68557f4 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -780,7 +780,6 @@ function testOrm(schema) { if (typeof expectedUserNumber === 'undefined') { expectedUserNumber = 5; } - //console.log(users.length, expectedUserNumber); test.equal(users.length, expectedUserNumber, 'Exactly ' + expectedUserNumber + ' users returned by query' + context); testHasMany(users, 'posts', callback); } From 6db1a003d4f0d69425164c61960f9d33c0dff917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Drouyer?= Date: Sun, 16 Dec 2012 19:19:53 +0100 Subject: [PATCH 6/8] cleaned some old comments --- lib/abstract-class.js | 66 ------------------------------------------- 1 file changed, 66 deletions(-) diff --git a/lib/abstract-class.js b/lib/abstract-class.js index 1c0b7a77..eb278a07 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -424,14 +424,6 @@ AbstractClass.include = function (objects, include, callback) { } } - - - /* - async.parallel(callbacks, function(err, results) { - callback(null, objs); - }); - */ - function processIncludeJoin(ij) { if (typeof ij === 'string') { ij = [ij]; @@ -525,64 +517,6 @@ AbstractClass.include = function (objects, include, callback) { return null; } - - - - /* - function processIncludeItem(model, objs, include, keyVals, objsByKeys) { - var relations = model.relations; - - if (include.constructor.name === 'Object') { - var relationName = Object.keys(include)[0]; - var relation = relations[relationName]; - var subInclude = include[relationName]; - } else { - var relationName = include; - var relation = relations[relationName]; - var subInclude = []; - } - - var req = {'where': {}}; - if (!keyVals[relation.keyFrom]) { - objsByKeys[relation.keyFrom] = {}; - for (var j = 0; j < objs.length; j++) { - if (!objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]]) { - objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]] = []; - } - objsByKeys[relation.keyFrom][objs[j][relation.keyFrom]].push(objs[j]); - } - keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]); - } - req['where'][relation.keyTo] = {inq: keyVals[relation.keyFrom]}; - req['include'] = subInclude; - return function(cb) { - relation.model.all(req, function(err, dataIncluded) { - - var objsIncluded = dataIncluded.map(function (obj) { - return self.fromDatabase(relation.model.modelName, obj); - }); - - for (var i = 0; i < objsIncluded.length; i++) { - var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]]; - for (var j = 0; j < objectsFrom.length; j++) { - if (!objectsFrom[j].__cache) { - objectsFrom[j].__cache = {}; - } - if (relation.type == 'hasMany') { - if (!objectsFrom[j].__cache[relationName]) { - objectsFrom[j].__cache[relationName] = []; - } - objectsFrom[j].__cache[relationName].push(objsIncluded[i]); - } else { - objectsFrom[j].__cache[relationName] = objsIncluded[i]; - } - } - } - cb(err, dataIncluded); - }); - }; - } - */ } /** From fbe04233ccd9a2b2394e40c93492f0d4e0d15729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Drouyer?= Date: Sun, 16 Dec 2012 19:44:14 +0100 Subject: [PATCH 7/8] added some documentation for include and all function --- lib/abstract-class.js | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/abstract-class.js b/lib/abstract-class.js index eb278a07..708af0c2 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -301,6 +301,7 @@ AbstractClass.find = function find(id, cb) { * @param {Object} params (optional) * * - where: Object `{ key: val, key2: {gt: 'val2'}}` + * - include: String, Object or Array. See AbstractClass.include documentation. * - order: String * - limit: Number * - skip: Number @@ -392,16 +393,32 @@ AbstractClass.count = function (where, cb) { }; /** + * Allows you to load relations of several objects and optimize numbers of requests. * + * @param {Array} objects - array of instances + * @param {String}, {Object} or {Array} include - which relations you want to load. + * @param {Function} cb - Callback called when relations are loaded + * + * Examples: + * + * - User.include(users, 'posts', function() {}); will load all users posts with only one additional request. + * - User.include(users, ['posts'], function() {}); // same + * - User.include(users, ['posts', 'passports'], function() {}); // will load all users posts and passports with two + * additional requests. + * - Passport.include(passports, {owner: 'posts'}, function() {}); // will load all passports owner (users), and all + * posts of each owner loaded + * - Passport.include(passports, {owner: ['posts', 'passports']}); // ... + * - Passport.include(passports, {owner: [{posts: 'images'}, 'passports']}); // ... * - * @param objects - * @param include */ -AbstractClass.include = function (objects, include, callback) { +AbstractClass.include = function (objects, include, cb) { var self = this; - if ((include.constructor.name == 'Array' && include.length == 0) || (include.constructor.name == 'Object' && Object.keys(include).length == 0)) { - callback(null, objects); + if ( + (include.constructor.name == 'Array' && include.length == 0) || + (include.constructor.name == 'Object' && Object.keys(include).length == 0) + ) { + cb(null, objects); return; } @@ -412,13 +429,13 @@ AbstractClass.include = function (objects, include, callback) { var nbCallbacks = 0; for (var i = 0; i < include.length; i++) { - var cb = processIncludeItem(objects, include[i], keyVals, objsByKeys); - if (cb !== null) { + var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys); + if (callback !== null) { nbCallbacks++; - cb(function() { + callback(function() { nbCallbacks--; if (nbCallbacks == 0) { - callback(null, objects); + cb(null, objects); } }); } From 61ffdbc5138ff48b80f8972f4e68062978725bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Drouyer?= Date: Mon, 17 Dec 2012 00:02:38 +0100 Subject: [PATCH 8/8] added filter on schema though I am not sure it will be used --- test/common_test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/common_test.js b/test/common_test.js index 825151ac..1002da4a 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -636,10 +636,9 @@ function testOrm(schema) { }); if ( - !schema.name.match(/redis/) && - schema.name !== 'memory' && - schema.name !== 'neo4j' && - schema.name !== 'cradle' + schema.name === 'mysql' || + schema.name === 'sqlite3' || + schema.name === 'postgres' ) it('should handle include function', function (test) { var createdUsers = [];