Merge pull request #211 from fabien/fix/relationTypes

Fix relationTypes
This commit is contained in:
Raymond Feng 2014-08-15 09:29:22 -07:00
commit d7900a8a21
3 changed files with 71 additions and 46 deletions

View File

@ -3,6 +3,7 @@
*/ */
var ModelBuilder = require('./model-builder.js').ModelBuilder; var ModelBuilder = require('./model-builder.js').ModelBuilder;
var ModelDefinition = require('./model-definition.js'); var ModelDefinition = require('./model-definition.js');
var RelationDefinition = require('./relation-definition.js');
var jutil = require('./jutil'); var jutil = require('./jutil');
var utils = require('./utils'); var utils = require('./utils');
var ModelBaseClass = require('./model.js'); var ModelBaseClass = require('./model.js');
@ -364,7 +365,7 @@ function isModelClass(cls) {
return cls.prototype instanceof ModelBaseClass; return cls.prototype instanceof ModelBaseClass;
} }
DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany', 'hasOne']; DataSource.relationTypes = Object.keys(RelationDefinition.RelationTypes);
function isModelDataSourceAttached(model) { function isModelDataSourceAttached(model) {
return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource); return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource);

View File

@ -9,6 +9,7 @@ var mergeQuery = require('./scope.js').mergeQuery;
var ModelBaseClass = require('./model.js'); var ModelBaseClass = require('./model.js');
var applyFilter = require('./connectors/memory').applyFilter; var applyFilter = require('./connectors/memory').applyFilter;
var ValidationError = require('./validations.js').ValidationError; var ValidationError = require('./validations.js').ValidationError;
var debug = require('debug')('loopback:relations');
exports.Relation = Relation; exports.Relation = Relation;
exports.RelationDefinition = RelationDefinition; exports.RelationDefinition = RelationDefinition;
@ -92,7 +93,6 @@ function RelationDefinition(definition) {
} }
definition = definition || {}; definition = definition || {};
this.name = definition.name; this.name = definition.name;
this.accessor = definition.accessor || this.name;
assert(this.name, 'Relation name is missing'); assert(this.name, 'Relation name is missing');
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);
@ -1502,18 +1502,22 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
modelTo = lookupModelTo(modelFrom, modelTo, params, true); modelTo = lookupModelTo(modelFrom, modelTo, params, true);
var thisClassName = modelFrom.modelName; var thisClassName = modelFrom.modelName;
var accessorName = params.as || (i8n.camelize(modelTo.modelName, true) + 'List'); var relationName = params.as || (i8n.camelize(modelTo.modelName, true) + 'List');
var relationName = params.property || i8n.camelize(modelTo.pluralModelName, true); var propertyName = params.property || i8n.camelize(modelTo.pluralModelName, true);
var fk = modelTo.dataSource.idName(modelTo.modelName) || 'id'; var idName = modelTo.dataSource.idName(modelTo.modelName) || 'id';
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
var definition = modelFrom.relations[accessorName] = new RelationDefinition({ if (relationName === propertyName) {
accessor: accessorName, propertyName = '_' + propertyName;
debug('EmbedsMany property cannot be equal to relation name: ' +
'forcing property %s for relation %s', propertyName, relationName);
}
var definition = modelFrom.relations[relationName] = new RelationDefinition({
name: relationName, name: relationName,
type: RelationTypes.embedsMany, type: RelationTypes.embedsMany,
modelFrom: modelFrom, modelFrom: modelFrom,
keyFrom: idName, keyFrom: propertyName,
keyTo: fk, keyTo: idName,
modelTo: modelTo, modelTo: modelTo,
multiple: true, multiple: true,
properties: params.properties, properties: params.properties,
@ -1522,7 +1526,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
embed: true embed: true
}); });
modelFrom.dataSource.defineProperty(modelFrom.modelName, relationName, { modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, {
type: [modelTo], default: function() { return []; } type: [modelTo], default: function() { return []; }
}); });
@ -1530,14 +1534,14 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
modelTo.validatesPresenceOf(idName); modelTo.validatesPresenceOf(idName);
if (!params.polymorphic) { if (!params.polymorphic) {
modelFrom.validate(relationName, function(err) { modelFrom.validate(propertyName, function(err) {
var embeddedList = this[relationName] || []; var embeddedList = this[propertyName] || [];
var ids = embeddedList.map(function(m) { return m[idName]; }); var ids = embeddedList.map(function(m) { return m[idName]; });
var uniqueIds = ids.filter(function(id, pos) { var uniqueIds = ids.filter(function(id, pos) {
return ids.indexOf(id) === pos; return ids.indexOf(id) === pos;
}); });
if (ids.length !== uniqueIds.length) { if (ids.length !== uniqueIds.length) {
this.errors.add(relationName, 'Contains duplicate `' + idName + '`', 'uniqueness'); this.errors.add(propertyName, 'Contains duplicate `' + idName + '`', 'uniqueness');
err(false); err(false);
} }
}, { code: 'uniqueness' }) }, { code: 'uniqueness' })
@ -1545,9 +1549,9 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
// validate all embedded items // validate all embedded items
if (definition.options.validate) { if (definition.options.validate) {
modelFrom.validate(relationName, function(err) { modelFrom.validate(propertyName, function(err) {
var self = this; var self = this;
var embeddedList = this[relationName] || []; var embeddedList = this[propertyName] || [];
var hasErrors = false; var hasErrors = false;
embeddedList.forEach(function(item) { embeddedList.forEach(function(item) {
if (item instanceof modelTo) { if (item instanceof modelTo) {
@ -1557,11 +1561,11 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
var first = Object.keys(item.errors)[0]; var first = Object.keys(item.errors)[0];
var msg = 'contains invalid item: `' + id + '`'; var msg = 'contains invalid item: `' + id + '`';
msg += ' (' + first + ' ' + item.errors[first] + ')'; msg += ' (' + first + ' ' + item.errors[first] + ')';
self.errors.add(relationName, msg, 'invalid'); self.errors.add(propertyName, msg, 'invalid');
} }
} else { } else {
hasErrors = true; hasErrors = true;
self.errors.add(relationName, 'Contains invalid item', 'invalid'); self.errors.add(propertyName, 'Contains invalid item', 'invalid');
} }
}); });
if (hasErrors) err(false); if (hasErrors) err(false);
@ -1582,19 +1586,19 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
}; };
var findByIdFunc = scopeMethods.findById; var findByIdFunc = scopeMethods.findById;
modelFrom.prototype['__findById__' + accessorName] = findByIdFunc; modelFrom.prototype['__findById__' + relationName] = findByIdFunc;
var destroyByIdFunc = scopeMethods.destroy; var destroyByIdFunc = scopeMethods.destroy;
modelFrom.prototype['__destroyById__' + accessorName] = destroyByIdFunc; modelFrom.prototype['__destroyById__' + relationName] = destroyByIdFunc;
var updateByIdFunc = scopeMethods.updateById; var updateByIdFunc = scopeMethods.updateById;
modelFrom.prototype['__updateById__' + accessorName] = updateByIdFunc; modelFrom.prototype['__updateById__' + relationName] = updateByIdFunc;
var addFunc = scopeMethods.add; var addFunc = scopeMethods.add;
modelFrom.prototype['__link__' + accessorName] = addFunc; modelFrom.prototype['__link__' + relationName] = addFunc;
var removeFunc = scopeMethods.remove; var removeFunc = scopeMethods.remove;
modelFrom.prototype['__unlink__' + accessorName] = removeFunc; modelFrom.prototype['__unlink__' + relationName] = removeFunc;
scopeMethods.create = scopeMethod(definition, 'create'); scopeMethods.create = scopeMethod(definition, 'create');
scopeMethods.build = scopeMethod(definition, 'build'); scopeMethods.build = scopeMethod(definition, 'build');
@ -1607,12 +1611,12 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
var methodName = customMethods[i]; var methodName = customMethods[i];
var method = scopeMethods[methodName]; var method = scopeMethods[methodName];
if (typeof method === 'function' && method.shared === true) { if (typeof method === 'function' && method.shared === true) {
modelFrom.prototype['__' + methodName + '__' + accessorName] = method; modelFrom.prototype['__' + methodName + '__' + relationName] = method;
} }
}; };
// Mix the property and scoped methods into the prototype class // Mix the property and scoped methods into the prototype class
var scopeDefinition = defineScope(modelFrom.prototype, modelTo, accessorName, function () { var scopeDefinition = defineScope(modelFrom.prototype, modelTo, relationName, function () {
return {}; return {};
}, scopeMethods, definition.options); }, scopeMethods, definition.options);
@ -1623,7 +1627,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) { EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name; var propertyName = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var self = receiver; var self = receiver;
@ -1642,7 +1646,7 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb
throw new Error('Method can be only called with one or two arguments'); throw new Error('Method can be only called with one or two arguments');
} }
var embeddedList = self[relationName] || []; var embeddedList = self[propertyName] || [];
this.definition.applyScope(modelInstance, actualCond); this.definition.applyScope(modelInstance, actualCond);
@ -1664,12 +1668,12 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb
}; };
EmbedsMany.prototype.findById = function (fkId, cb) { EmbedsMany.prototype.findById = function (fkId, cb) {
var pk = this.definition.keyFrom; var pk = this.definition.keyTo;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name; var propertyName = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var embeddedList = modelInstance[relationName] || []; var embeddedList = modelInstance[propertyName] || [];
var find = function(id) { var find = function(id) {
for (var i = 0; i < embeddedList.length; i++) { for (var i = 0; i < embeddedList.length; i++) {
@ -1706,10 +1710,10 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) {
} }
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name; var propertyName = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var embeddedList = modelInstance[relationName] || []; var embeddedList = modelInstance[propertyName] || [];
var inst = this.findById(fkId); var inst = this.findById(fkId);
@ -1725,7 +1729,7 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) {
} }
if (typeof cb === 'function') { if (typeof cb === 'function') {
modelInstance.updateAttribute(relationName, modelInstance.updateAttribute(propertyName,
embeddedList, function(err) { embeddedList, function(err) {
cb(err, inst); cb(err, inst);
}); });
@ -1740,10 +1744,10 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) {
EmbedsMany.prototype.destroyById = function (fkId, cb) { EmbedsMany.prototype.destroyById = function (fkId, cb) {
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name; var propertyName = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var embeddedList = modelInstance[relationName] || []; var embeddedList = modelInstance[propertyName] || [];
var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId); var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId);
@ -1751,7 +1755,7 @@ EmbedsMany.prototype.destroyById = function (fkId, cb) {
var index = embeddedList.indexOf(inst); var index = embeddedList.indexOf(inst);
if (index > -1) embeddedList.splice(index, 1); if (index > -1) embeddedList.splice(index, 1);
if (typeof cb === 'function') { if (typeof cb === 'function') {
modelInstance.updateAttribute(relationName, modelInstance.updateAttribute(propertyName,
embeddedList, function(err) { embeddedList, function(err) {
cb(err); cb(err);
}); });
@ -1768,10 +1772,10 @@ EmbedsMany.prototype.unset = EmbedsMany.prototype.destroyById;
EmbedsMany.prototype.at = function (index, cb) { EmbedsMany.prototype.at = function (index, cb) {
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name; var propertyName = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var embeddedList = modelInstance[relationName] || []; var embeddedList = modelInstance[propertyName] || [];
var item = embeddedList[parseInt(index)]; var item = embeddedList[parseInt(index)];
item = (item instanceof modelTo) ? item : null; item = (item instanceof modelTo) ? item : null;
@ -1786,9 +1790,9 @@ EmbedsMany.prototype.at = function (index, cb) {
}; };
EmbedsMany.prototype.create = function (targetModelData, cb) { EmbedsMany.prototype.create = function (targetModelData, cb) {
var pk = this.definition.keyFrom; var pk = this.definition.keyTo;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name; var propertyName = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var autoId = this.definition.options.autoId !== false; var autoId = this.definition.options.autoId !== false;
@ -1798,7 +1802,7 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
} }
targetModelData = targetModelData || {}; targetModelData = targetModelData || {};
var embeddedList = modelInstance[relationName] || []; var embeddedList = modelInstance[propertyName] || [];
var inst = this.build(targetModelData); var inst = this.build(targetModelData);
@ -1810,20 +1814,20 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
}); });
} }
modelInstance.updateAttribute(relationName, modelInstance.updateAttribute(propertyName,
embeddedList, function(err, modelInst) { embeddedList, function(err, modelInst) {
cb(err, err ? null : inst); cb(err, err ? null : inst);
}); });
}; };
EmbedsMany.prototype.build = function(targetModelData) { EmbedsMany.prototype.build = function(targetModelData) {
var pk = this.definition.keyFrom; var pk = this.definition.keyTo;
var modelTo = this.definition.modelTo; var modelTo = this.definition.modelTo;
var relationName = this.definition.name; var propertyName = this.definition.keyFrom;
var modelInstance = this.modelInstance; var modelInstance = this.modelInstance;
var autoId = this.definition.options.autoId !== false; var autoId = this.definition.options.autoId !== false;
var embeddedList = modelInstance[relationName] || []; var embeddedList = modelInstance[propertyName] || [];
targetModelData = targetModelData || {}; targetModelData = targetModelData || {};
@ -1925,7 +1929,7 @@ EmbedsMany.prototype.remove = function (acInst, cb) {
belongsTo.applyScope(modelInstance, filter); belongsTo.applyScope(modelInstance, filter);
modelInstance[definition.accessor](filter, function(err, items) { modelInstance[definition.name](filter, function(err, items) {
if (err) return cb(err); if (err) return cb(err);
items.forEach(function(item) { items.forEach(function(item) {

View File

@ -904,6 +904,26 @@ describe('Load models with relations', function () {
done(); done();
}); });
it('should set up referencesMany relations', function (done) {
var ds = new DataSource('memory');
var Post = ds.define('Post', {userId: Number, content: String});
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'referencesMany', model: 'Post'}}});
assert(User.relations['posts']);
done();
});
it('should set up embedsMany relations', function (done) {
var ds = new DataSource('memory');
var Post = ds.define('Post', {userId: Number, content: String});
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'embedsMany', model: 'Post' }}});
assert(User.relations['posts']);
done();
});
it('should set up foreign key with the correct type', function (done) { it('should set up foreign key with the correct type', function (done) {
var ds = new DataSource('memory'); var ds = new DataSource('memory');