From 332579ec873c63b41abb6d7de880d1d65537bda6 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sat, 21 Jun 2014 11:44:33 -0700 Subject: [PATCH] Synchronize with cachedRelations --- lib/relation-definition.js | 139 ++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 31 deletions(-) diff --git a/lib/relation-definition.js b/lib/relation-definition.js index 13366d7a..fe4a6d7d 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -60,10 +60,10 @@ function RelationDefinition(definition) { this.type = normalizeType(definition.type); assert(this.type, 'Invalid relation type: ' + definition.type); this.modelFrom = definition.modelFrom; - assert(this.modelFrom); + assert(this.modelFrom, 'Source model is required'); this.keyFrom = definition.keyFrom; this.modelTo = definition.modelTo; - assert(this.modelTo); + assert(this.modelTo, 'Target model is required'); this.keyTo = definition.keyTo; this.modelThrough = definition.modelThrough; this.keyThrough = definition.keyThrough; @@ -106,6 +106,15 @@ function Relation(definition, modelInstance) { this.modelInstance = modelInstance; } +Relation.prototype.resetCache = function (cache) { + cache = cache || undefined; + this.modelInstance.__cachedRelations[this.definition.name] = cache; +}; + +Relation.prototype.getCache = function () { + return this.modelInstance.__cachedRelations[this.definition.name]; +}; + /** * HasMany subclass * @param {RelationDefinition|Object} definition @@ -124,6 +133,39 @@ function HasMany(definition, modelInstance) { util.inherits(HasMany, Relation); +HasMany.prototype.removeFromCache = function (id) { + var cache = this.modelInstance.__cachedRelations[this.definition.name]; + var idName = this.definition.modelTo.definition.idName(); + if (Array.isArray(cache)) { + for (var i = 0, n = cache.length; i < n; i++) { + if (cache[i][idName] === id) { + return cache.splice(i, 1); + } + } + } + return null; +}; + +HasMany.prototype.addToCache = function (inst) { + if (!inst) { + return; + } + var cache = this.modelInstance.__cachedRelations[this.definition.name]; + if (cache === undefined) { + cache = this.modelInstance.__cachedRelations[this.definition.name] = []; + } + var idName = this.definition.modelTo.definition.idName(); + if (Array.isArray(cache)) { + for (var i = 0, n = cache.length; i < n; i++) { + if (cache[i][idName] === inst[idName]) { + cache[i] = inst; + return; + } + } + cache.push(inst); + } +}; + /** * HasManyThrough subclass * @param {RelationDefinition|Object} definition @@ -210,7 +252,7 @@ function findBelongsTo(modelFrom, modelTo, keyTo) { var rel = relations[keys[k]]; if (rel.type === RelationTypes.belongsTo && rel.modelTo === modelTo && - rel.keyTo === keyTo) { + (keyTo === undefined || rel.keyTo === keyTo)) { return rel.keyFrom; } } @@ -356,6 +398,7 @@ HasMany.prototype.findById = function (id, cb) { }; HasMany.prototype.destroyById = function (id, cb) { + var self = this; var modelTo = this.definition.modelTo; var fk = this.definition.keyTo; var pk = this.definition.keyFrom; @@ -369,6 +412,7 @@ HasMany.prototype.destroyById = function (id, cb) { } // Check if the foreign key matches the primary key if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) { + self.removeFromCache(inst[fk]); inst.destroy(cb); } else { cb(new Error('Permission denied: foreign key does not match the primary key')); @@ -379,6 +423,7 @@ HasMany.prototype.destroyById = function (id, cb) { // Create an instance of the target model and connect it to the instance of // the source model by creating an instance of the through model HasManyThrough.prototype.create = function create(data, done) { + var self = this; var definition = this.definition; var modelTo = definition.modelTo; var modelThrough = definition.modelThrough; @@ -391,27 +436,28 @@ HasManyThrough.prototype.create = function create(data, done) { var modelInstance = this.modelInstance; // First create the target model - modelTo.create(data, function (err, ac) { + modelTo.create(data, function (err, to) { if (err) { - return done && done(err, ac); + return done && done(err, to); } // The primary key for the target model - var pk2 = modelTo.dataSource.idName(modelTo.modelName) || 'id'; + var pk2 = definition.modelTo.definition.idName(); var fk1 = findBelongsTo(modelThrough, definition.modelFrom, definition.keyFrom); var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2); var d = {}; d[fk1] = modelInstance[definition.keyFrom]; - d[fk2] = ac[pk2]; + d[fk2] = to[pk2]; // Then create the through model - modelThrough.create(d, function (e) { + modelThrough.create(d, function (e, through) { if (e) { // Undo creation of the target model - ac.destroy(function () { + to.destroy(function () { done && done(e); }); } else { - done && done(err, ac); + self.addToCache(to); + done && done(err, to); } }); }); @@ -422,9 +468,9 @@ HasManyThrough.prototype.create = function create(data, done) { * @param {Object|ID} acInst The actual instance or id value */ HasManyThrough.prototype.add = function (acInst, done) { + var self = this; var definition = this.definition; var modelThrough = definition.modelThrough; - var modelTo = definition.modelTo; var pk1 = definition.keyFrom; var data = {}; @@ -434,7 +480,7 @@ HasManyThrough.prototype.add = function (acInst, done) { definition.keyFrom); // The primary key for the target model - var pk2 = modelTo.dataSource.idName(modelTo.modelName) || 'id'; + var pk2 = definition.modelTo.definition.idName(); var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2); @@ -445,7 +491,14 @@ HasManyThrough.prototype.add = function (acInst, done) { data[fk2] = acInst[pk2] || acInst; // Create an instance of the through model - modelThrough.findOrCreate({where: query}, data, done); + modelThrough.findOrCreate({where: query}, data, function(err, ac) { + if(!err) { + if (acInst instanceof definition.modelTo) { + self.addToCache(acInst); + } + } + done(err, ac); + }); }; /** @@ -453,13 +506,30 @@ HasManyThrough.prototype.add = function (acInst, done) { * @param {Object|ID) acInst The actual instance or id value */ HasManyThrough.prototype.remove = function (acInst, done) { - var modelThrough = this.definition.modelThrough; - var fk2 = this.definition.keyThrough; - var pk = this.definition.keyFrom; + var self = this; + var definition = this.definition; + var modelThrough = definition.modelThrough; + var pk1 = definition.keyFrom; - var q = {}; - q[fk2] = acInst[pk] || acInst; - modelThrough.deleteAll(q, done ); + var query = {}; + + var fk1 = findBelongsTo(modelThrough, definition.modelFrom, + definition.keyFrom); + + // The primary key for the target model + var pk2 = definition.modelTo.definition.idName(); + + var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2); + + query[fk1] = this.modelInstance[pk1]; + query[fk2] = acInst[pk2] || acInst; + + modelThrough.deleteAll(query, function (err) { + if (!err) { + self.removeFromCache(query[fk2]); + } + done(err); + }); }; @@ -547,6 +617,7 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) { }; BelongsTo.prototype.create = function(targetModelData, cb) { + var self = this; var modelTo = this.definition.modelTo; var fk = this.definition.keyTo; var pk = this.definition.keyFrom; @@ -555,6 +626,7 @@ BelongsTo.prototype.create = function(targetModelData, cb) { modelTo.create(targetModelData, function(err, targetModel) { if(!err) { modelInstance[fk] = targetModel[pk]; + self.resetCache(targetModel); cb && cb(err, targetModel); } else { cb && cb(err); @@ -579,11 +651,11 @@ BelongsTo.prototype.build = function(targetModelData) { * @returns {*} */ BelongsTo.prototype.related = function (refresh, params) { + var self = this; var modelTo = this.definition.modelTo; var pk = this.definition.keyTo; var fk = this.definition.keyFrom; var modelInstance = this.modelInstance; - var relationName = this.definition.name; if (arguments.length === 1) { params = refresh; @@ -593,13 +665,12 @@ BelongsTo.prototype.related = function (refresh, params) { } var cachedValue; - if (!refresh && modelInstance.__cachedRelations - && (modelInstance.__cachedRelations[relationName] !== undefined)) { - cachedValue = modelInstance.__cachedRelations[relationName]; + if (!refresh) { + cachedValue = self.getCache(); } if (params instanceof ModelBaseClass) { // acts as setter modelInstance[fk] = params[pk]; - modelInstance.__cachedRelations[relationName] = params; + self.resetCache(params); } else if (typeof params === 'function') { // acts as async getter var cb = params; if (cachedValue === undefined) { @@ -612,9 +683,10 @@ BelongsTo.prototype.related = function (refresh, params) { } // Check if the foreign key matches the primary key if (inst[pk] === modelInstance[fk]) { + self.resetCache(inst); cb(null, inst); } else { - cb(new Error('Permission denied')); + cb(new Error('Permission denied: foreign key does not match the primary key')); } }); return modelInstance[fk]; @@ -626,7 +698,7 @@ BelongsTo.prototype.related = function (refresh, params) { return modelInstance[fk]; } else { // setter modelInstance[fk] = params; - delete modelInstance.__cachedRelations[relationName]; + self.resetCache(); } }; @@ -742,15 +814,19 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) { * @param {Object} The newly created target model instance */ HasOne.prototype.create = function(targetModelData, cb) { + var self = this; var modelTo = this.definition.modelTo; var fk = this.definition.keyTo; var pk = this.definition.keyFrom; var modelInstance = this.modelInstance; + var relationName = this.definition.name targetModelData = targetModelData || {}; targetModelData[fk] = modelInstance[pk]; modelTo.create(targetModelData, function(err, targetModel) { if(!err) { + // Refresh the cache + self.resetCache(targetModel); cb && cb(err, targetModel); } else { cb && cb(err); @@ -784,6 +860,7 @@ HasOne.prototype.build = function(targetModelData) { * @returns {Object} */ HasOne.prototype.related = function (refresh, params) { + var self = this; var modelTo = this.definition.modelTo; var fk = this.definition.keyTo; var pk = this.definition.keyFrom; @@ -798,13 +875,12 @@ HasOne.prototype.related = function (refresh, params) { } var cachedValue; - if (!refresh && modelInstance.__cachedRelations - && (modelInstance.__cachedRelations[relationName] !== undefined)) { - cachedValue = modelInstance.__cachedRelations[relationName]; + if (!refresh) { + cachedValue = self.getCache(); } if (params instanceof ModelBaseClass) { // acts as setter params[fk] = modelInstance[pk]; - modelInstance.__cachedRelations[relationName] = params; + self.resetCache(params); } else if (typeof params === 'function') { // acts as async getter var cb = params; if (cachedValue === undefined) { @@ -819,6 +895,7 @@ HasOne.prototype.related = function (refresh, params) { } // Check if the foreign key matches the primary key if (inst[fk] === modelInstance[pk]) { + self.resetCache(inst); cb(null, inst); } else { cb(new Error('Permission denied')); @@ -833,6 +910,6 @@ HasOne.prototype.related = function (refresh, params) { return modelInstance[pk]; } else { // setter params[fk] = modelInstance[pk]; - delete modelInstance.__cachedRelations[relationName]; + self.resetCache(); } };