Make sure type of the foreign key match the primary key

See https://github.com/strongloop/loopback/issues/353
This commit is contained in:
Raymond Feng 2014-06-26 23:38:04 -07:00
parent a1d3e72046
commit 4515491318
4 changed files with 81 additions and 24 deletions

View File

@ -1579,22 +1579,26 @@ DataSource.prototype.idProperty = function (modelName) {
* @param {String} foreignClassName The foreign model name
*/
DataSource.prototype.defineForeignKey = function defineForeignKey(className, key, foreignClassName) {
// quit if key already defined
if (this.getModelDefinition(className).rawProperties[key]) return;
var defaultType = Number;
if (foreignClassName) {
var pkType = null;
var foreignModel = this.getModelDefinition(foreignClassName);
var pkName = foreignModel && foreignModel.idName();
if (pkName) {
defaultType = foreignModel.properties[pkName].type;
pkType = foreignModel.properties[pkName].type;
}
var model = this.getModelDefinition(className);
if (model.properties[key]) {
if (pkType) {
// Reset the type of the foreign key
model.rawProperties[key].type = model.properties[key].type = pkType;
}
return;
}
if (this.connector.defineForeignKey) {
var cb = function (err, keyType) {
if (err) throw err;
// Add the foreign key property to the data source _models
this.defineProperty(className, key, {type: keyType || defaultType});
this.defineProperty(className, key, {type: keyType || pkType});
}.bind(this);
switch (this.connector.defineForeignKey.length) {
case 4:
@ -1607,7 +1611,7 @@ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key
}
} else {
// Add the foreign key property to the data source _models
this.defineProperty(className, key, {type: defaultType});
this.defineProperty(className, key, {type: pkType});
}
};

View File

@ -336,7 +336,7 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
if (!params.through) {
// obviously, modelTo should have attribute called `fk`
modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, this.modelName);
modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName);
}
var scopeMethods = {
@ -348,6 +348,9 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
scopeMethods.create = scopeMethod(definition, 'create');
scopeMethods.add = scopeMethod(definition, 'add');
scopeMethods.remove = scopeMethod(definition, 'remove');
} else {
scopeMethods.create = scopeMethod(definition, 'create');
scopeMethods.build = scopeMethod(definition, 'build');
}
// Mix the property and scoped methods into the prototype class
@ -623,6 +626,11 @@ BelongsTo.prototype.create = function(targetModelData, cb) {
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
if (typeof targetModelData === 'function' && !cb) {
cb = targetModelData;
targetModelData = {};
}
modelTo.create(targetModelData, function(err, targetModel) {
if(!err) {
modelInstance[fk] = targetModel[pk];
@ -695,7 +703,7 @@ BelongsTo.prototype.related = function (refresh, params) {
return cachedValue;
}
} else if (params === undefined) { // acts as sync getter
return modelInstance[fk];
return cachedValue;
} else { // setter
modelInstance[fk] = params;
self.resetCache();
@ -819,10 +827,22 @@ HasOne.prototype.create = function(targetModelData, cb) {
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
var relationName = this.definition.name
if (typeof targetModelData === 'function' && !cb) {
cb = targetModelData;
targetModelData = {};
}
targetModelData = targetModelData || {};
targetModelData[fk] = modelInstance[pk];
var query = {where: {}};
query.where[fk] = targetModelData[fk]
modelTo.findOne(query, function(err, result) {
if(err) {
cb(err);
} else if(result) {
cb(new Error('HasOne relation cannot create more than one instance of '
+ modelTo.modelName));
} else {
modelTo.create(targetModelData, function (err, targetModel) {
if (!err) {
// Refresh the cache
@ -832,14 +852,46 @@ HasOne.prototype.create = function(targetModelData, cb) {
cb && cb(err);
}
});
}
});
};
/**
* Create a target model instance
* @param {Object} targetModelData The target model data
* @callback {Function} [cb] Callback function
* @param {String|Object} err Error string or object
* @param {Object} The newly created target model instance
*/
HasMany.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;
if (typeof targetModelData === 'function' && !cb) {
cb = targetModelData;
targetModelData = {};
}
targetModelData = targetModelData || {};
targetModelData[fk] = modelInstance[pk];
modelTo.create(targetModelData, function(err, targetModel) {
if(!err) {
// Refresh the cache
self.addToCache(targetModel);
cb && cb(err, targetModel);
} else {
cb && cb(err);
}
});
};
/**
* Build a target model instance
* @param {Object} targetModelData The target model data
* @returns {Object} The newly built target model instance
*/
HasOne.prototype.build = function(targetModelData) {
HasMany.prototype.build = HasOne.prototype.build = function(targetModelData) {
var modelTo = this.definition.modelTo;
var pk = this.definition.keyFrom;
var fk = this.definition.keyTo;
@ -865,7 +917,6 @@ HasOne.prototype.related = function (refresh, params) {
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
var relationName = this.definition.name;
if (arguments.length === 1) {
params = refresh;
@ -907,7 +958,7 @@ HasOne.prototype.related = function (refresh, params) {
return cachedValue;
}
} else if (params === undefined) { // acts as sync getter
return modelInstance[pk];
return cachedValue;
} else { // setter
params[fk] = modelInstance[pk];
self.resetCache();

View File

@ -202,7 +202,9 @@ function defineScope(cls, targetClass, name, params, methods) {
var prop = targetClass.definition.properties[i];
if (prop) {
var val = where[i];
if (typeof val !== 'object' || val instanceof prop.type) {
if (typeof val !== 'object' || val instanceof prop.type
|| prop.type.name === 'ObjectID') // MongoDB key
{
// Only pick the {propertyName: propertyValue}
data[i] = where[i];
}

View File

@ -155,7 +155,7 @@ describe('relations', function () {
should.not.exist(e);
should.exist(l);
l.should.be.an.instanceOf(List);
todo.list().should.equal(l.id);
todo.list().id.should.equal(l.id);
done();
});
});
@ -206,7 +206,7 @@ describe('relations', function () {
should.not.exist(e);
should.exist(act);
act.should.be.an.instanceOf(Account);
supplier.account().should.equal(act.id);
supplier.account().id.should.equal(act.id);
done();
});
});