Merge pull request #141 from sdrdis/master
Added caching in relationships (+ test cases) (enhanced)
This commit is contained in:
commit
0c24dfa035
|
@ -37,6 +37,13 @@ AbstractClass.prototype._initProperties = function (data, applySetters) {
|
|||
var properties = ds.properties;
|
||||
data = data || {};
|
||||
|
||||
Object.defineProperty(this, '__cachedRelations', {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: {}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, '__data', {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
|
@ -704,6 +711,23 @@ AbstractClass.hasMany = function hasMany(anotherClass, params) {
|
|||
*
|
||||
* @param {Class} anotherClass - class to belong
|
||||
* @param {Object} params - configuration {as: 'propertyName', foreignKey: 'keyName'}
|
||||
*
|
||||
* **Usage examples**
|
||||
* Suppose model Post have a *belongsTo* relationship with User (the author of the post). You could declare it this way:
|
||||
* Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
||||
*
|
||||
* When a post is loaded, you can load the related author with:
|
||||
* post.author(function(err, user) {
|
||||
* // the user variable is your user object
|
||||
* });
|
||||
*
|
||||
* The related object is cached, so if later you try to get again the author, no additional request will be made.
|
||||
* But there is an optional boolean parameter in first position that set whether or not you want to reload the cache:
|
||||
* post.author(true, function(err, user) {
|
||||
* // The user is reloaded, even if it was already cached.
|
||||
* });
|
||||
*
|
||||
* This optional parameter default value is false, so the related object will be loaded from cache if available.
|
||||
*/
|
||||
AbstractClass.belongsTo = function (anotherClass, params) {
|
||||
var methodName = params.as;
|
||||
|
@ -724,16 +748,39 @@ AbstractClass.belongsTo = function (anotherClass, params) {
|
|||
}.bind(this));
|
||||
};
|
||||
|
||||
this.prototype[methodName] = function (p) {
|
||||
this.prototype[methodName] = function (refresh, p) {
|
||||
if (arguments.length === 1) {
|
||||
p = refresh;
|
||||
refresh = false;
|
||||
} else if (arguments.length > 2) {
|
||||
throw new Error('Method can\'t be called with more than two arguments');
|
||||
}
|
||||
var self = this;
|
||||
var cachedValue;
|
||||
if (!refresh && this.__cachedRelations && (typeof this.__cachedRelations[methodName] !== 'undefined')) {
|
||||
cachedValue = this.__cachedRelations[methodName];
|
||||
}
|
||||
if (p instanceof AbstractClass) { // acts as setter
|
||||
this[fk] = p.id;
|
||||
this.__cachedRelations[methodName] = p;
|
||||
} else if (typeof p === 'function') { // acts as async getter
|
||||
this.__finders__[methodName](this[fk], p);
|
||||
return this[fk];
|
||||
if (typeof cachedValue === 'undefined') {
|
||||
this.__finders__[methodName](this[fk], function(err, inst) {
|
||||
if (!err) {
|
||||
self.__cachedRelations[methodName] = inst;
|
||||
}
|
||||
p(err, inst);
|
||||
});
|
||||
return this[fk];
|
||||
} else {
|
||||
p(null, cachedValue);
|
||||
return cachedValue;
|
||||
}
|
||||
} else if (typeof p === 'undefined') { // acts as sync getter
|
||||
return this[fk];
|
||||
} else { // setter
|
||||
this[fk] = p;
|
||||
delete this.__cachedRelations[methodName];
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -768,18 +815,35 @@ function defineScope(cls, targetClass, name, params, methods) {
|
|||
enumerable: false,
|
||||
configurable: true,
|
||||
get: function () {
|
||||
var f = function caller(cond, cb) {
|
||||
var actualCond;
|
||||
var f = function caller(condOrRefresh, cb) {
|
||||
var actualCond = {};
|
||||
var actualRefresh = false;
|
||||
var saveOnCache = true;
|
||||
if (arguments.length === 1) {
|
||||
actualCond = {};
|
||||
cb = cond;
|
||||
cb = condOrRefresh;
|
||||
} else if (arguments.length === 2) {
|
||||
actualCond = cond;
|
||||
if (typeof condOrRefresh === 'boolean') {
|
||||
actualRefresh = condOrRefresh;
|
||||
} else {
|
||||
actualCond = condOrRefresh;
|
||||
actualRefresh = true;
|
||||
saveOnCache = false;
|
||||
}
|
||||
} else {
|
||||
throw new Error('Method only can be called with one or two arguments');
|
||||
throw new Error('Method can be only called with one or two arguments');
|
||||
}
|
||||
|
||||
return targetClass.all(mergeParams(actualCond, caller._scope), cb);
|
||||
if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) {
|
||||
var self = this;
|
||||
return targetClass.all(mergeParams(actualCond, caller._scope), function(err, data) {
|
||||
if (!err && saveOnCache) {
|
||||
self.__cachedRelations[name] = data;
|
||||
}
|
||||
cb(err, data);
|
||||
});
|
||||
} else {
|
||||
cb(null, this.__cachedRelations[name]);
|
||||
}
|
||||
};
|
||||
f._scope = typeof params === 'function' ? params.call(this) : params;
|
||||
f.build = build;
|
||||
|
|
|
@ -26,6 +26,7 @@ var schemas = {
|
|||
|
||||
var specificTest = getSpecificTests();
|
||||
var testPerformed = false;
|
||||
var nbSchemaRequests = 0;
|
||||
|
||||
Object.keys(schemas).forEach(function (schemaName) {
|
||||
if (process.env.ONLY && process.env.ONLY !== schemaName) return;
|
||||
|
@ -48,7 +49,8 @@ function performTestFor(schemaName) {
|
|||
});
|
||||
|
||||
schema.log = function (a) {
|
||||
console.log(a);
|
||||
console.log(a);
|
||||
nbSchemaRequests++;
|
||||
};
|
||||
|
||||
testOrm(schema);
|
||||
|
@ -446,6 +448,58 @@ function testOrm(schema) {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
it('hasMany should be cached', function (test) {
|
||||
|
||||
User.find(1, function(err, user) {
|
||||
User.create(function(err, voidUser) {
|
||||
Post.create({userId: user.id}, function() {
|
||||
|
||||
// There can't be any concurrency because we are counting requests
|
||||
// We are first testing cases when user has posts
|
||||
user.posts(function(err, data) {
|
||||
var nbInitialRequests = nbSchemaRequests;
|
||||
user.posts(function(err, data2) {
|
||||
test.equal(data.length, 2, 'There should be 2 posts.');
|
||||
test.equal(data.length, data2.length, 'Posts should be the same, since we are loading on the same object.');
|
||||
test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
|
||||
|
||||
user.posts({where: {id: 12}}, function(err, data) {
|
||||
test.equal(data.length, 1, 'There should be only one post.');
|
||||
test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we added conditions.');
|
||||
|
||||
user.posts(function(err, data) {
|
||||
test.equal(data.length, 2, 'Previous get shouldn\'t have changed cached value though, since there was additional conditions.');
|
||||
test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should not be any request because value is cached.');
|
||||
|
||||
// We are now testing cases when user doesn't have any post
|
||||
voidUser.posts(function(err, data) {
|
||||
var nbInitialRequests = nbSchemaRequests;
|
||||
voidUser.posts(function(err, data2) {
|
||||
test.equal(data.length, 0, 'There shouldn\'t be any posts (1/2).');
|
||||
test.equal(data2.length, 0, 'There shouldn\'t be any posts (2/2).');
|
||||
test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
|
||||
|
||||
voidUser.posts(true, function(err, data3) {
|
||||
test.equal(data3.length, 0, 'There shouldn\'t be any posts.');
|
||||
test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we forced refresh.');
|
||||
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// it('should handle hasOne relationship', function (test) {
|
||||
// User.create(function (err, u) {
|
||||
// if (err) return console.log(err);
|
||||
|
@ -882,6 +936,48 @@ function testOrm(schema) {
|
|||
});
|
||||
});
|
||||
|
||||
it('belongsTo should be cached', function (test) {
|
||||
User.findOne(function(err, user) {
|
||||
|
||||
var passport = new Passport({ownerId: user.id});
|
||||
var passport2 = new Passport({ownerId: null});
|
||||
|
||||
// There can't be any concurrency because we are counting requests
|
||||
// We are first testing cases when passport has an owner
|
||||
passport.owner(function(err, data) {
|
||||
var nbInitialRequests = nbSchemaRequests;
|
||||
passport.owner(function(err, data2) {
|
||||
test.equal(data.id, data2.id, 'The value should remain the same');
|
||||
test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
|
||||
|
||||
// We are now testing cases when passport has not an owner
|
||||
passport2.owner(function(err, data) {
|
||||
var nbInitialRequests2 = nbSchemaRequests;
|
||||
passport2.owner(function(err, data2) {
|
||||
test.equal(data, null, 'The value should be null since there is no owner');
|
||||
test.equal(data, data2, 'The value should remain the same (null)');
|
||||
test.equal(nbInitialRequests2, nbSchemaRequests, 'There should not be any request because value is cached.');
|
||||
|
||||
passport2.owner(user.id);
|
||||
passport2.owner(function(err, data3) {
|
||||
test.equal(data3.id, user.id, 'Owner should now be the user.');
|
||||
test.equal(nbInitialRequests2 + 1, nbSchemaRequests, 'If we changed owner id, there should be one more request.');
|
||||
|
||||
passport2.owner(true, function(err, data4) {
|
||||
test.equal(data3.id, data3.id, 'The value should remain the same');
|
||||
test.equal(nbInitialRequests2 + 2, nbSchemaRequests, 'If we forced refreshing, there should be one more request.');
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
if (schema.name !== 'mongoose' && schema.name !== 'neo4j')
|
||||
it('should update or create record', function (test) {
|
||||
var newData = {
|
||||
|
|
Loading…
Reference in New Issue