Skip to content

Commit

Permalink
Merge branch 'master' of github.com:1602/jugglingdb
Browse files Browse the repository at this point in the history
  • Loading branch information
1602 committed Nov 3, 2012
2 parents cdef6ae + 0c24dfa commit 0b9454e
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 11 deletions.
84 changes: 74 additions & 10 deletions lib/abstract-class.js
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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];
}
};

Expand Down Expand Up @@ -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;
Expand Down
98 changes: 97 additions & 1 deletion test/common_test.js
Expand Up @@ -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;
Expand All @@ -48,7 +49,8 @@ function performTestFor(schemaName) {
});

schema.log = function (a) {
console.log(a);
console.log(a);
nbSchemaRequests++;
};

testOrm(schema);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 = {
Expand Down

0 comments on commit 0b9454e

Please sign in to comment.