Permalink
Browse files

Merge pull request #368 from mshick/master

Updating include syntax to support hasAndBelongsToMany relationships
  • Loading branch information...
2 parents 97c326c + 038f2e4 commit ae290a617bb99c371fc74a1c2df7b48e350242df @1602 committed Jun 21, 2014
Showing with 231 additions and 83 deletions.
  1. +133 −78 lib/include.js
  2. +2 −2 lib/model.js
  3. +4 −0 lib/relations.js
  4. +92 −3 test/include.test.js
View
@@ -1,4 +1,9 @@
/**
+ * Dependencies
+ */
+var i8n = require('inflection');
+
+/**
* Include mixin for ./model.js
*/
var AbstractClass = require('./model.js');
@@ -22,13 +27,12 @@ var AbstractClass = require('./model.js');
* - Passport.include(passports, {owner: [{posts: 'images'}, 'passports']}); // ...
*
*/
+
+ /*jshint sub: true */
+
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)
- ) {
+ if ((include.constructor.name == 'Array' && include.length === 0) || (include.constructor.name == 'Object' && Object.keys(include).length === 0)) {
cb(null, objects);
return;
}
@@ -39,21 +43,30 @@ AbstractClass.include = function (objects, include, cb) {
var objsByKeys = {};
var nbCallbacks = 0;
+
for (var i = 0; i < include.length; i++) {
- var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys);
+ var callback = processIncludeItem(this, objects, include[i], keyVals, objsByKeys);
if (callback !== null) {
- nbCallbacks++;
- callback(function() {
- nbCallbacks--;
- if (nbCallbacks == 0) {
- cb(null, objects);
- }
- });
+ if (callback instanceof Error) {
+ cb(callback);
+ } else {
+ includeItemCallback(callback);
+ }
} else {
cb(null, objects);
}
}
+ function includeItemCallback(itemCb) {
+ nbCallbacks++;
+ itemCb(function () {
+ nbCallbacks--;
+ if (nbCallbacks === 0) {
+ cb(null, objects);
+ }
+ });
+ }
+
function processIncludeJoin(ij) {
if (typeof ij === 'string') {
ij = [ij];
@@ -69,89 +82,131 @@ AbstractClass.include = function (objects, include, cb) {
}
return ij;
}
+};
+
+function processIncludeItem(cls, objs, include, keyVals, objsByKeys) {
+ var relations = cls.relations;
+ var relationName, subInclude;
+
+ if (include.constructor.name === 'Object') {
+ relationName = Object.keys(include)[0];
+ subInclude = include[relationName];
+ } else {
+ relationName = include;
+ subInclude = [];
+ }
+ var relation = relations[relationName];
- function processIncludeItem(objs, include, keyVals, objsByKeys) {
- var relations = self.relations;
+ if (!relation) {
+ return new Error('Relation "' + relationName + '" is not defined for ' + cls.modelName + ' model');
+ }
- 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': {}
+ };
- if (!relation) {
- return function() {
- cb(new Error('Relation "' + relationName + '" is not defined for ' + self.modelName + ' model'));
+ 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]] = [];
}
+ objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj);
+ });
+ keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]);
+ }
+
+ // 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]);
}
+ }
- var req = {'where': {}};
+ var _model, _through;
- 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]] = [];
+ function done(err, objsIncluded, cb) {
+ var objectsFrom;
+
+ for (var i = 0; i < objsIncluded.length; i++) {
+ delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
+ objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
+
+ for (var j = 0; j < objectsFrom.length; j++) {
+ if (!objectsFrom[j].__cachedRelations) {
+ objectsFrom[j].__cachedRelations = {};
}
- objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj);
- });
- keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]);
+ if (relation.multiple) {
+ if (!objectsFrom[j].__cachedRelations[relationName]) {
+ objectsFrom[j].__cachedRelations[relationName] = [];
+ }
+
+ if (_through) {
+ objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i].__cachedRelations[_through]);
+ } else {
+ objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]);
+ }
+ } else {
+ if (_through) {
+ objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i].__cachedRelations[_through];
+ } else {
+ objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i];
+ }
+ }
+ }
}
- 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]);
+ // No relation have been found for these keys
+ for (var key in keysToBeProcessed) {
+ objectsFrom = objsByKeys[relation.keyFrom][key];
+ for (var k = 0; k < objectsFrom.length; k++) {
+ if (!objectsFrom[k].__cachedRelations) {
+ objectsFrom[k].__cachedRelations = {};
}
+ objectsFrom[k].__cachedRelations[relationName] = relation.multiple ? [] : null;
}
+ }
+
+ cb(err, objsIncluded);
+ }
- req['where'][relation.keyTo] = {inq: inValues};
+ if (keyVals[relation.keyFrom].length > 0) {
+
+ if (relation.modelThrough) {
+ req['where'][relation.keyTo] = { inq: inValues };
+
+ _model = cls.schema.models[relation.modelThrough.modelName];
+ _through = i8n.camelize(relation.modelTo.modelName, true);
+
+ } else {
+ req['where'][relation.keyTo] = { inq: inValues };
req['include'] = subInclude;
+ }
- return function(cb) {
- relation.modelTo.all(req, function(err, objsIncluded) {
- 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];
- }
- }
- }
+ return function (cb) {
- // 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);
+ if (_through) {
+
+ relation.modelThrough.all(req, function (err, objsIncluded) {
+
+ _model.include(objsIncluded, _through, function (err, throughIncludes) {
+
+ done(err, throughIncludes, cb);
+ });
});
- };
- }
+ } else {
- return null;
+ relation.modelTo.all(req, function (err, objsIncluded) {
+
+ done(err, objsIncluded, cb);
+ });
+ }
+ };
}
-}
+ return null;
+}
View
@@ -782,8 +782,8 @@ AbstractClass.prototype.toObject = function (onlySchema, cachedRelations) {
// Object.getOwnPropertyNames(this).indexOf(prop) !== -1;
// };
-AbstractClass.prototype.toJSON = function (cachedRelations) {
- return this.toObject(false, cachedRelations);
+AbstractClass.prototype.toJSON = function () {
+ return this.toObject(false, true);
};
View
@@ -59,6 +59,10 @@ Model.hasMany = function hasMany(anotherClass, params) {
destroy: destroy
};
if (params.through) {
+
+ // Append through relation, like modelTo
+ this.relations[methodName].modelThrough = params.through;
+
var fk2 = i8n.camelize(anotherClass.modelName + '_id', true);
scopeMethods.create = function create(data, done) {
if (typeof data !== 'object') {
Oops, something went wrong.

0 comments on commit ae290a6

Please sign in to comment.