Merge branch 'master' into 2.0

This commit is contained in:
Raymond Feng 2014-06-21 12:53:46 -07:00
commit ce8254125f
3 changed files with 122 additions and 35 deletions

View File

@ -158,11 +158,19 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
var def = properties[p]['default']; var def = properties[p]['default'];
if (def !== undefined) { if (def !== undefined) {
if (typeof def === 'function') { if (typeof def === 'function') {
self.__data[p] = def(); if (def === Date) {
// FIXME: We should coerce the value in general
// This is a work around to {default: Date}
// Date() will return a string instead of Date
def = new Date();
} else { } else {
self.__data[p] = def; def = def();
} }
} }
// FIXME: We should coerce the value
// will implement it after we refactor the PropertyDefinition
self.__data[p] = def;
}
} }
// Handle complex types (JSON/Object) // Handle complex types (JSON/Object)

View File

@ -60,10 +60,10 @@ function RelationDefinition(definition) {
this.type = normalizeType(definition.type); this.type = normalizeType(definition.type);
assert(this.type, 'Invalid relation type: ' + definition.type); assert(this.type, 'Invalid relation type: ' + definition.type);
this.modelFrom = definition.modelFrom; this.modelFrom = definition.modelFrom;
assert(this.modelFrom); assert(this.modelFrom, 'Source model is required');
this.keyFrom = definition.keyFrom; this.keyFrom = definition.keyFrom;
this.modelTo = definition.modelTo; this.modelTo = definition.modelTo;
assert(this.modelTo); assert(this.modelTo, 'Target model is required');
this.keyTo = definition.keyTo; this.keyTo = definition.keyTo;
this.modelThrough = definition.modelThrough; this.modelThrough = definition.modelThrough;
this.keyThrough = definition.keyThrough; this.keyThrough = definition.keyThrough;
@ -106,6 +106,15 @@ function Relation(definition, modelInstance) {
this.modelInstance = 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 * HasMany subclass
* @param {RelationDefinition|Object} definition * @param {RelationDefinition|Object} definition
@ -124,6 +133,39 @@ function HasMany(definition, modelInstance) {
util.inherits(HasMany, Relation); 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 * HasManyThrough subclass
* @param {RelationDefinition|Object} definition * @param {RelationDefinition|Object} definition
@ -210,7 +252,7 @@ function findBelongsTo(modelFrom, modelTo, keyTo) {
var rel = relations[keys[k]]; var rel = relations[keys[k]];
if (rel.type === RelationTypes.belongsTo && if (rel.type === RelationTypes.belongsTo &&
rel.modelTo === modelTo && rel.modelTo === modelTo &&
rel.keyTo === keyTo) { (keyTo === undefined || rel.keyTo === keyTo)) {
return rel.keyFrom; return rel.keyFrom;
} }
} }
@ -356,6 +398,7 @@ HasMany.prototype.findById = function (id, cb) {
}; };
HasMany.prototype.destroyById = function (id, cb) { HasMany.prototype.destroyById = function (id, cb) {
var self = this;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var fk = this.definition.keyTo; var fk = this.definition.keyTo;
var pk = this.definition.keyFrom; var pk = this.definition.keyFrom;
@ -369,6 +412,7 @@ HasMany.prototype.destroyById = function (id, cb) {
} }
// Check if the foreign key matches the primary key // Check if the foreign key matches the primary key
if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) { if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) {
self.removeFromCache(inst[fk]);
inst.destroy(cb); inst.destroy(cb);
} else { } else {
cb(new Error('Permission denied: foreign key does not match the primary key')); 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 // 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 // the source model by creating an instance of the through model
HasManyThrough.prototype.create = function create(data, done) { HasManyThrough.prototype.create = function create(data, done) {
var self = this;
var definition = this.definition; var definition = this.definition;
var modelTo = definition.modelTo; var modelTo = definition.modelTo;
var modelThrough = definition.modelThrough; var modelThrough = definition.modelThrough;
@ -391,27 +436,28 @@ HasManyThrough.prototype.create = function create(data, done) {
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
// First create the target model // First create the target model
modelTo.create(data, function (err, ac) { modelTo.create(data, function (err, to) {
if (err) { if (err) {
return done && done(err, ac); return done && done(err, to);
} }
// The primary key for the target model // 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, var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
definition.keyFrom); definition.keyFrom);
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2); var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
var d = {}; var d = {};
d[fk1] = modelInstance[definition.keyFrom]; d[fk1] = modelInstance[definition.keyFrom];
d[fk2] = ac[pk2]; d[fk2] = to[pk2];
// Then create the through model // Then create the through model
modelThrough.create(d, function (e) { modelThrough.create(d, function (e, through) {
if (e) { if (e) {
// Undo creation of the target model // Undo creation of the target model
ac.destroy(function () { to.destroy(function () {
done && done(e); done && done(e);
}); });
} else { } 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 * @param {Object|ID} acInst The actual instance or id value
*/ */
HasManyThrough.prototype.add = function (acInst, done) { HasManyThrough.prototype.add = function (acInst, done) {
var self = this;
var definition = this.definition; var definition = this.definition;
var modelThrough = definition.modelThrough; var modelThrough = definition.modelThrough;
var modelTo = definition.modelTo;
var pk1 = definition.keyFrom; var pk1 = definition.keyFrom;
var data = {}; var data = {};
@ -434,7 +480,7 @@ HasManyThrough.prototype.add = function (acInst, done) {
definition.keyFrom); definition.keyFrom);
// The primary key for the target model // 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); var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
@ -445,7 +491,14 @@ HasManyThrough.prototype.add = function (acInst, done) {
data[fk2] = acInst[pk2] || acInst; data[fk2] = acInst[pk2] || acInst;
// Create an instance of the through model // 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 * @param {Object|ID) acInst The actual instance or id value
*/ */
HasManyThrough.prototype.remove = function (acInst, done) { HasManyThrough.prototype.remove = function (acInst, done) {
var modelThrough = this.definition.modelThrough; var self = this;
var fk2 = this.definition.keyThrough; var definition = this.definition;
var pk = this.definition.keyFrom; var modelThrough = definition.modelThrough;
var pk1 = definition.keyFrom;
var q = {}; var query = {};
q[fk2] = acInst[pk] || acInst;
modelThrough.deleteAll(q, done ); 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);
});
}; };
@ -539,6 +609,7 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
}; };
BelongsTo.prototype.create = function(targetModelData, cb) { BelongsTo.prototype.create = function(targetModelData, cb) {
var self = this;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var fk = this.definition.keyTo; var fk = this.definition.keyTo;
var pk = this.definition.keyFrom; var pk = this.definition.keyFrom;
@ -547,6 +618,7 @@ BelongsTo.prototype.create = function(targetModelData, cb) {
modelTo.create(targetModelData, function(err, targetModel) { modelTo.create(targetModelData, function(err, targetModel) {
if(!err) { if(!err) {
modelInstance[fk] = targetModel[pk]; modelInstance[fk] = targetModel[pk];
self.resetCache(targetModel);
cb && cb(err, targetModel); cb && cb(err, targetModel);
} else { } else {
cb && cb(err); cb && cb(err);
@ -571,11 +643,11 @@ BelongsTo.prototype.build = function(targetModelData) {
* @returns {*} * @returns {*}
*/ */
BelongsTo.prototype.related = function (refresh, params) { BelongsTo.prototype.related = function (refresh, params) {
var self = this;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var pk = this.definition.keyTo; var pk = this.definition.keyTo;
var fk = this.definition.keyFrom; var fk = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var relationName = this.definition.name;
if (arguments.length === 1) { if (arguments.length === 1) {
params = refresh; params = refresh;
@ -585,13 +657,12 @@ BelongsTo.prototype.related = function (refresh, params) {
} }
var cachedValue; var cachedValue;
if (!refresh && modelInstance.__cachedRelations if (!refresh) {
&& (modelInstance.__cachedRelations[relationName] !== undefined)) { cachedValue = self.getCache();
cachedValue = modelInstance.__cachedRelations[relationName];
} }
if (params instanceof ModelBaseClass) { // acts as setter if (params instanceof ModelBaseClass) { // acts as setter
modelInstance[fk] = params[pk]; modelInstance[fk] = params[pk];
modelInstance.__cachedRelations[relationName] = params; self.resetCache(params);
} else if (typeof params === 'function') { // acts as async getter } else if (typeof params === 'function') { // acts as async getter
var cb = params; var cb = params;
if (cachedValue === undefined) { if (cachedValue === undefined) {
@ -604,9 +675,10 @@ BelongsTo.prototype.related = function (refresh, params) {
} }
// Check if the foreign key matches the primary key // Check if the foreign key matches the primary key
if (inst[pk] === modelInstance[fk]) { if (inst[pk] === modelInstance[fk]) {
self.resetCache(inst);
cb(null, inst); cb(null, inst);
} else { } else {
cb(new Error('Permission denied')); cb(new Error('Permission denied: foreign key does not match the primary key'));
} }
}); });
return modelInstance[fk]; return modelInstance[fk];
@ -618,7 +690,7 @@ BelongsTo.prototype.related = function (refresh, params) {
return modelInstance[fk]; return modelInstance[fk];
} else { // setter } else { // setter
modelInstance[fk] = params; modelInstance[fk] = params;
delete modelInstance.__cachedRelations[relationName]; self.resetCache();
} }
}; };
@ -734,15 +806,19 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
* @param {Object} The newly created target model instance * @param {Object} The newly created target model instance
*/ */
HasOne.prototype.create = function(targetModelData, cb) { HasOne.prototype.create = function(targetModelData, cb) {
var self = this;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var fk = this.definition.keyTo; var fk = this.definition.keyTo;
var pk = this.definition.keyFrom; var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var relationName = this.definition.name
targetModelData = targetModelData || {}; targetModelData = targetModelData || {};
targetModelData[fk] = modelInstance[pk]; targetModelData[fk] = modelInstance[pk];
modelTo.create(targetModelData, function(err, targetModel) { modelTo.create(targetModelData, function(err, targetModel) {
if(!err) { if(!err) {
// Refresh the cache
self.resetCache(targetModel);
cb && cb(err, targetModel); cb && cb(err, targetModel);
} else { } else {
cb && cb(err); cb && cb(err);
@ -776,6 +852,7 @@ HasOne.prototype.build = function(targetModelData) {
* @returns {Object} * @returns {Object}
*/ */
HasOne.prototype.related = function (refresh, params) { HasOne.prototype.related = function (refresh, params) {
var self = this;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var fk = this.definition.keyTo; var fk = this.definition.keyTo;
var pk = this.definition.keyFrom; var pk = this.definition.keyFrom;
@ -790,13 +867,12 @@ HasOne.prototype.related = function (refresh, params) {
} }
var cachedValue; var cachedValue;
if (!refresh && modelInstance.__cachedRelations if (!refresh) {
&& (modelInstance.__cachedRelations[relationName] !== undefined)) { cachedValue = self.getCache();
cachedValue = modelInstance.__cachedRelations[relationName];
} }
if (params instanceof ModelBaseClass) { // acts as setter if (params instanceof ModelBaseClass) { // acts as setter
params[fk] = modelInstance[pk]; params[fk] = modelInstance[pk];
modelInstance.__cachedRelations[relationName] = params; self.resetCache(params);
} else if (typeof params === 'function') { // acts as async getter } else if (typeof params === 'function') { // acts as async getter
var cb = params; var cb = params;
if (cachedValue === undefined) { if (cachedValue === undefined) {
@ -811,6 +887,7 @@ HasOne.prototype.related = function (refresh, params) {
} }
// Check if the foreign key matches the primary key // Check if the foreign key matches the primary key
if (inst[fk] === modelInstance[pk]) { if (inst[fk] === modelInstance[pk]) {
self.resetCache(inst);
cb(null, inst); cb(null, inst);
} else { } else {
cb(new Error('Permission denied')); cb(new Error('Permission denied'));
@ -825,6 +902,6 @@ HasOne.prototype.related = function (refresh, params) {
return modelInstance[pk]; return modelInstance[pk];
} else { // setter } else { // setter
params[fk] = modelInstance[pk]; params[fk] = modelInstance[pk];
delete modelInstance.__cachedRelations[relationName]; self.resetCache();
} }
}; };

View File

@ -264,7 +264,7 @@ describe('DataSource define model', function () {
name: String, name: String,
bio: ModelBuilder.Text, bio: ModelBuilder.Text,
approved: Boolean, approved: Boolean,
joinedAt: Date, joinedAt: {type: Date, default: Date},
age: Number age: Number
}); });
@ -280,6 +280,8 @@ describe('DataSource define model', function () {
assert.equal(user.name, 'Joe'); assert.equal(user.name, 'Joe');
assert.equal(user.group, 'G1'); assert.equal(user.group, 'G1');
assert(user.joinedAt instanceof Date);
// setup relationships // setup relationships
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});