Simplify the inclusion processing
This commit is contained in:
parent
cadacc44bb
commit
cc5975486d
|
@ -2,7 +2,6 @@ var DataSource = require('../index').DataSource;
|
|||
var ds = new DataSource('memory');
|
||||
|
||||
var Order = ds.createModel('Order', {
|
||||
customerId: Number,
|
||||
items: [String],
|
||||
orderDate: Date
|
||||
});
|
||||
|
@ -13,8 +12,11 @@ var Customer = ds.createModel('Customer', {
|
|||
|
||||
Order.belongsTo(Customer);
|
||||
|
||||
var order1, order2, order3;
|
||||
|
||||
Customer.create({name: 'John'}, function (err, customer) {
|
||||
Order.create({customerId: customer.id, orderDate: new Date(), items: ['Book']}, function (err, order) {
|
||||
order1 = order;
|
||||
order.customer(console.log);
|
||||
order.customer(true, console.log);
|
||||
|
||||
|
@ -24,13 +26,16 @@ Customer.create({name: 'John'}, function (err, customer) {
|
|||
});
|
||||
});
|
||||
|
||||
Order.create({orderDate: new Date(), items: ['Phone']}, function (err, order2) {
|
||||
Order.create({orderDate: new Date(), items: ['Phone']}, function (err, order) {
|
||||
|
||||
order2.customer.create({name: 'Smith'}, function(err, customer2) {
|
||||
console.log(order2, customer2);
|
||||
order.customer.create({name: 'Smith'}, function(err, customer2) {
|
||||
console.log(order, customer2);
|
||||
order.save(function(err, order) {
|
||||
order2 = order;
|
||||
});
|
||||
});
|
||||
|
||||
var customer3 = order2.customer.build({name: 'Tom'});
|
||||
var customer3 = order.customer.build({name: 'Tom'});
|
||||
console.log('Customer 3', customer3);
|
||||
});
|
||||
});
|
||||
|
@ -39,14 +44,15 @@ Customer.hasMany(Order, {as: 'orders', foreignKey: 'customerId'});
|
|||
|
||||
Customer.create({name: 'Ray'}, function (err, customer) {
|
||||
Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) {
|
||||
order3 = order;
|
||||
customer.orders(console.log);
|
||||
customer.orders.create({orderDate: new Date()}, function (err, order) {
|
||||
console.log(order);
|
||||
Customer.include([customer], 'orders', function (err, results) {
|
||||
console.log('Results: ', results);
|
||||
});
|
||||
customer.orders.findById('2', console.log);
|
||||
customer.orders.destroy('2', console.log);
|
||||
customer.orders.findById(order3.id, console.log);
|
||||
customer.orders.destroy(order3.id, console.log);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -114,15 +120,21 @@ Part.hasAndBelongsToMany(Assembly);
|
|||
Assembly.create({name: 'car'}, function (err, assembly) {
|
||||
Part.create({partNumber: 'engine'}, function (err, part) {
|
||||
assembly.parts.add(part, function (err) {
|
||||
assembly.parts(console.log);
|
||||
assembly.parts(function(err, parts) {
|
||||
console.log('Parts: ', parts);
|
||||
});
|
||||
|
||||
// Build an part?
|
||||
var part3 = assembly.parts.build({partNumber: 'door'});
|
||||
console.log('Part3: ', part3, part3.constructor.modelName);
|
||||
|
||||
// Create a part?
|
||||
assembly.parts.create({name: 'door'}, function(err, part4) {
|
||||
assembly.parts.create({partNumber: 'door'}, function(err, part4) {
|
||||
console.log('Part4: ', part4, part4.constructor.modelName);
|
||||
|
||||
Assembly.find({include: 'parts'}, function(err, assemblies) {
|
||||
console.log('Assemblies: ', assemblies);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -732,7 +732,6 @@ DataAccessObject.prototype.save = function (options, callback) {
|
|||
|
||||
var inst = this;
|
||||
var data = inst.toObject(true);
|
||||
var Model = this.constructor;
|
||||
var modelName = Model.modelName;
|
||||
|
||||
if (!getIdValue(Model, this)) {
|
||||
|
|
155
lib/include.js
155
lib/include.js
|
@ -1,3 +1,4 @@
|
|||
var async = require('async');
|
||||
var utils = require('./utils');
|
||||
var isPlainObject = utils.isPlainObject;
|
||||
var defineCachedRelations = utils.defineCachedRelations;
|
||||
|
@ -42,52 +43,46 @@ function Inclusion() {
|
|||
Inclusion.include = function (objects, include, cb) {
|
||||
var self = this;
|
||||
|
||||
if (
|
||||
!include || (Array.isArray(include) && include.length === 0) ||
|
||||
(isPlainObject(include) && Object.keys(include).length === 0)
|
||||
) {
|
||||
cb(null, objects);
|
||||
return;
|
||||
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 = processIncludeJoin(include);
|
||||
include = normalizeInclude(include);
|
||||
|
||||
var keyVals = {};
|
||||
var objsByKeys = {};
|
||||
async.each(include, function(item, callback) {
|
||||
processIncludeItem(objects, item, callback);
|
||||
}, function(err) {
|
||||
cb && cb(err, objects);
|
||||
});
|
||||
|
||||
var nbCallbacks = 0;
|
||||
for (var i = 0; i < include.length; i++) {
|
||||
var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys);
|
||||
if (callback !== null) {
|
||||
nbCallbacks++;
|
||||
callback(function () {
|
||||
nbCallbacks--;
|
||||
if (nbCallbacks === 0) {
|
||||
cb(null, objects);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cb(null, objects);
|
||||
}
|
||||
}
|
||||
|
||||
function processIncludeJoin(ij) {
|
||||
if (typeof ij === 'string') {
|
||||
ij = [ij];
|
||||
}
|
||||
if (isPlainObject(ij)) {
|
||||
var newIj = [];
|
||||
for (var key in ij) {
|
||||
/*!
|
||||
* 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 = [];
|
||||
for (var key in include) {
|
||||
var obj = {};
|
||||
obj[key] = ij[key];
|
||||
newIj.push(obj);
|
||||
obj[key] = include[key];
|
||||
newInclude.push(obj);
|
||||
}
|
||||
return newIj;
|
||||
return newInclude;
|
||||
} else {
|
||||
return include;
|
||||
}
|
||||
return ij;
|
||||
}
|
||||
|
||||
function processIncludeItem(objs, include, keyVals, objsByKeys) {
|
||||
function processIncludeItem(objs, include, cb) {
|
||||
var relations = self.relations;
|
||||
|
||||
var relationName, subInclude;
|
||||
|
@ -96,7 +91,7 @@ Inclusion.include = function (objects, include, cb) {
|
|||
subInclude = include[relationName];
|
||||
} else {
|
||||
relationName = include;
|
||||
subInclude = [];
|
||||
subInclude = null;
|
||||
}
|
||||
var relation = relations[relationName];
|
||||
|
||||
|
@ -107,68 +102,40 @@ Inclusion.include = function (objects, include, cb) {
|
|||
};
|
||||
}
|
||||
|
||||
var req = {'where': {}};
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
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]);
|
||||
// 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;
|
||||
obj[relationName] = null;
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
var inst = (obj instanceof self) ? obj : new self(obj);
|
||||
var relationMethod = inst[relationName];
|
||||
// FIXME: [rfeng] How do we pass in the refresh flag?
|
||||
relationMethod(function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
} else {
|
||||
defineCachedRelations(obj);
|
||||
obj.__cachedRelations[relationName] = result;
|
||||
obj[relationName] = result;
|
||||
|
||||
req.where[relation.keyTo] = {inq: inValues};
|
||||
req.include = subInclude;
|
||||
|
||||
return function (cb) {
|
||||
relation.modelTo.find(req, function (err, objsIncluded) {
|
||||
var objectsFrom, j;
|
||||
for (var i = 0; i < objsIncluded.length; i++) {
|
||||
delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
|
||||
objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
|
||||
for (j = 0; j < objectsFrom.length; j++) {
|
||||
defineCachedRelations(objectsFrom[j]);
|
||||
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];
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
// No relation have been found for these keys
|
||||
for (var key in keysToBeProcessed) {
|
||||
objectsFrom = objsByKeys[relation.keyFrom][key];
|
||||
for (j = 0; j < objectsFrom.length; j++) {
|
||||
defineCachedRelations(objectsFrom[j]);
|
||||
objectsFrom[j].__cachedRelations[relationName] =
|
||||
relation.multiple ? [] : null;
|
||||
}
|
||||
}
|
||||
cb(err, objsIncluded);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -116,7 +116,10 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
|||
if (i in properties && typeof data[i] !== 'function') {
|
||||
this.__data[i] = this.__dataWas[i] = clone(data[i]);
|
||||
} else if (i in ctor.relations) {
|
||||
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo];
|
||||
if (ctor.relations[i].type === 'belongsTo' && data[i] !== null && data[i] !== undefined) {
|
||||
// If the related model is populated
|
||||
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo];
|
||||
}
|
||||
this.__cachedRelations[i] = data[i];
|
||||
} else {
|
||||
if (strict === false) {
|
||||
|
|
|
@ -78,6 +78,10 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
|||
modelTo: anotherClass,
|
||||
multiple: true
|
||||
};
|
||||
|
||||
if (params.through) {
|
||||
this.relations[methodName].modelThrough = params.through;
|
||||
}
|
||||
// each instance of this class should have method named
|
||||
// pluralize(anotherClass.modelName)
|
||||
// which is actually just anotherClass.find({where: {thisModelNameId: this[idName]}}, cb);
|
||||
|
|
|
@ -47,6 +47,7 @@ function defineScope(cls, targetClass, name, params, methods) {
|
|||
*
|
||||
*/
|
||||
get: function () {
|
||||
var self = this;
|
||||
var f = function caller(condOrRefresh, cb) {
|
||||
var actualCond = {};
|
||||
var actualRefresh = false;
|
||||
|
@ -65,8 +66,9 @@ function defineScope(cls, targetClass, name, params, methods) {
|
|||
throw new Error('Method can be only called with one or two arguments');
|
||||
}
|
||||
|
||||
if (!this.__cachedRelations || (this.__cachedRelations[name] === undefined) || actualRefresh) {
|
||||
var self = this;
|
||||
if (!self.__cachedRelations || self.__cachedRelations[name] === undefined
|
||||
|| actualRefresh) {
|
||||
// It either doesn't hit the cache or reresh is required
|
||||
var params = mergeParams(actualCond, caller._scope);
|
||||
return targetClass.find(params, function (err, data) {
|
||||
if (!err && saveOnCache) {
|
||||
|
@ -76,7 +78,8 @@ function defineScope(cls, targetClass, name, params, methods) {
|
|||
cb(err, data);
|
||||
});
|
||||
} else {
|
||||
cb(null, this.__cachedRelations[name]);
|
||||
// Return from cache
|
||||
cb(null, self.__cachedRelations[name]);
|
||||
}
|
||||
};
|
||||
f._scope = typeof params === 'function' ? params.call(this) : params;
|
||||
|
|
|
@ -194,8 +194,11 @@ BaseSQL.prototype.exists = function (model, id, callback) {
|
|||
* @param {Function} callback The callback function
|
||||
*/
|
||||
BaseSQL.prototype.find = function find(model, id, callback) {
|
||||
var idQuery = (id === null || id === undefined)
|
||||
? this.idColumnEscaped(model) + ' IS NULL'
|
||||
: this.idColumnEscaped(model) + ' = ' + id;
|
||||
var sql = 'SELECT * FROM ' +
|
||||
this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id + ' LIMIT 1';
|
||||
this.tableEscaped(model) + ' WHERE ' + idQuery + ' LIMIT 1';
|
||||
|
||||
this.query(sql, function (err, data) {
|
||||
if (data && data.length === 1) {
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
// This test written in mocha+should.js
|
||||
var should = require('./init.js');
|
||||
|
||||
var db, User, Post, Passport, City, Street, Building;
|
||||
var nbSchemaRequests = 0;
|
||||
var db, User, Post, Passport, City, Street, Building, Assembly, Part;
|
||||
|
||||
describe('include', function () {
|
||||
|
||||
before(setup);
|
||||
|
||||
it('should fetch belongsTo relation', function (done) {
|
||||
Passport.all({include: 'owner'}, function (err, passports) {
|
||||
Passport.find({include: 'owner'}, function (err, passports) {
|
||||
passports.length.should.be.ok;
|
||||
passports.forEach(function (p) {
|
||||
p.__cachedRelations.should.have.property('owner');
|
||||
|
@ -32,7 +31,7 @@ describe('include', function () {
|
|||
});
|
||||
|
||||
it('should fetch hasMany relation', function (done) {
|
||||
User.all({include: 'posts'}, function (err, users) {
|
||||
User.find({include: 'posts'}, function (err, users) {
|
||||
should.not.exist(err);
|
||||
should.exist(users);
|
||||
users.length.should.be.ok;
|
||||
|
@ -52,7 +51,7 @@ describe('include', function () {
|
|||
});
|
||||
|
||||
it('should fetch Passport - Owner - Posts', function (done) {
|
||||
Passport.all({include: {owner: 'posts'}}, function (err, passports) {
|
||||
Passport.find({include: {owner: 'posts'}}, function (err, passports) {
|
||||
should.not.exist(err);
|
||||
should.exist(passports);
|
||||
passports.length.should.be.ok;
|
||||
|
@ -81,7 +80,7 @@ describe('include', function () {
|
|||
});
|
||||
|
||||
it('should fetch Passports - User - Posts - User', function (done) {
|
||||
Passport.all({
|
||||
Passport.find({
|
||||
include: {owner: {posts: 'author'}}
|
||||
}, function (err, passports) {
|
||||
should.not.exist(err);
|
||||
|
@ -109,7 +108,7 @@ describe('include', function () {
|
|||
});
|
||||
|
||||
it('should fetch User - Posts AND Passports', function (done) {
|
||||
User.all({include: ['posts', 'passports']}, function (err, users) {
|
||||
User.find({include: ['posts', 'passports']}, function (err, users) {
|
||||
should.not.exist(err);
|
||||
should.exist(users);
|
||||
users.length.should.be.ok;
|
||||
|
@ -140,6 +139,39 @@ describe('include', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should support hasAndBelongsToMany', function (done) {
|
||||
|
||||
Assembly.destroyAll(function(err) {
|
||||
Part.destroyAll(function(err) {
|
||||
Assembly.relations.parts.modelThrough.destroyAll(function(err) {
|
||||
Assembly.create({name: 'car'}, function (err, assembly) {
|
||||
Part.create({partNumber: 'engine'}, function (err, part) {
|
||||
assembly.parts.add(part, function (err, data) {
|
||||
assembly.parts(function (err, parts) {
|
||||
should.not.exist(err);
|
||||
should.exists(parts);
|
||||
parts.length.should.equal(1);
|
||||
parts[0].partNumber.should.equal('engine');
|
||||
|
||||
// Create a part
|
||||
assembly.parts.create({partNumber: 'door'}, function (err, part4) {
|
||||
|
||||
Assembly.find({include: 'parts'}, function (err, assemblies) {
|
||||
assemblies.length.should.equal(1);
|
||||
assemblies[0].parts.length.should.equal(2);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function setup(done) {
|
||||
|
@ -163,6 +195,17 @@ function setup(done) {
|
|||
User.hasMany('posts', {foreignKey: 'userId'});
|
||||
Post.belongsTo('author', {model: User, foreignKey: 'userId'});
|
||||
|
||||
Assembly = db.define('Assembly', {
|
||||
name: String
|
||||
});
|
||||
|
||||
Part = db.define('Part', {
|
||||
partNumber: String
|
||||
});
|
||||
|
||||
Assembly.hasAndBelongsToMany(Part);
|
||||
Part.hasAndBelongsToMany(Assembly);
|
||||
|
||||
db.automigrate(function () {
|
||||
var createdUsers = [];
|
||||
var createdPassports = [];
|
||||
|
|
Loading…
Reference in New Issue