diff --git a/lib/datasource.js b/lib/datasource.js index b488ce72..9602ec4f 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -3,6 +3,7 @@ */ var ModelBuilder = require('./model-builder.js').ModelBuilder; var ModelDefinition = require('./model-definition.js'); +var RelationDefinition = require('./relation-definition.js'); var jutil = require('./jutil'); var utils = require('./utils'); var ModelBaseClass = require('./model.js'); @@ -364,7 +365,7 @@ function isModelClass(cls) { return cls.prototype instanceof ModelBaseClass; } -DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany', 'hasOne']; +DataSource.relationTypes = Object.keys(RelationDefinition.RelationTypes); function isModelDataSourceAttached(model) { return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource); diff --git a/lib/relation-definition.js b/lib/relation-definition.js index df1fed75..93d1b6ca 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -9,6 +9,7 @@ var mergeQuery = require('./scope.js').mergeQuery; var ModelBaseClass = require('./model.js'); var applyFilter = require('./connectors/memory').applyFilter; var ValidationError = require('./validations.js').ValidationError; +var debug = require('debug')('loopback:relations'); exports.Relation = Relation; exports.RelationDefinition = RelationDefinition; @@ -92,7 +93,6 @@ function RelationDefinition(definition) { } definition = definition || {}; this.name = definition.name; - this.accessor = definition.accessor || this.name; assert(this.name, 'Relation name is missing'); this.type = normalizeType(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); var thisClassName = modelFrom.modelName; - var accessorName = params.as || (i8n.camelize(modelTo.modelName, true) + 'List'); - var relationName = params.property || i8n.camelize(modelTo.pluralModelName, true); - var fk = modelTo.dataSource.idName(modelTo.modelName) || 'id'; - var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id'; + var relationName = params.as || (i8n.camelize(modelTo.modelName, true) + 'List'); + var propertyName = params.property || i8n.camelize(modelTo.pluralModelName, true); + var idName = modelTo.dataSource.idName(modelTo.modelName) || 'id'; - var definition = modelFrom.relations[accessorName] = new RelationDefinition({ - accessor: accessorName, + if (relationName === propertyName) { + 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, type: RelationTypes.embedsMany, modelFrom: modelFrom, - keyFrom: idName, - keyTo: fk, + keyFrom: propertyName, + keyTo: idName, modelTo: modelTo, multiple: true, properties: params.properties, @@ -1522,7 +1526,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) embed: true }); - modelFrom.dataSource.defineProperty(modelFrom.modelName, relationName, { + modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, { type: [modelTo], default: function() { return []; } }); @@ -1530,14 +1534,14 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) modelTo.validatesPresenceOf(idName); if (!params.polymorphic) { - modelFrom.validate(relationName, function(err) { - var embeddedList = this[relationName] || []; + modelFrom.validate(propertyName, function(err) { + var embeddedList = this[propertyName] || []; var ids = embeddedList.map(function(m) { return m[idName]; }); var uniqueIds = ids.filter(function(id, pos) { return ids.indexOf(id) === pos; }); if (ids.length !== uniqueIds.length) { - this.errors.add(relationName, 'Contains duplicate `' + idName + '`', 'uniqueness'); + this.errors.add(propertyName, 'Contains duplicate `' + idName + '`', 'uniqueness'); err(false); } }, { code: 'uniqueness' }) @@ -1545,9 +1549,9 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) // validate all embedded items if (definition.options.validate) { - modelFrom.validate(relationName, function(err) { + modelFrom.validate(propertyName, function(err) { var self = this; - var embeddedList = this[relationName] || []; + var embeddedList = this[propertyName] || []; var hasErrors = false; embeddedList.forEach(function(item) { if (item instanceof modelTo) { @@ -1557,11 +1561,11 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) var first = Object.keys(item.errors)[0]; var msg = 'contains invalid item: `' + id + '`'; msg += ' (' + first + ' ' + item.errors[first] + ')'; - self.errors.add(relationName, msg, 'invalid'); + self.errors.add(propertyName, msg, 'invalid'); } } else { hasErrors = true; - self.errors.add(relationName, 'Contains invalid item', 'invalid'); + self.errors.add(propertyName, 'Contains invalid item', 'invalid'); } }); if (hasErrors) err(false); @@ -1582,19 +1586,19 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) }; var findByIdFunc = scopeMethods.findById; - modelFrom.prototype['__findById__' + accessorName] = findByIdFunc; + modelFrom.prototype['__findById__' + relationName] = findByIdFunc; var destroyByIdFunc = scopeMethods.destroy; - modelFrom.prototype['__destroyById__' + accessorName] = destroyByIdFunc; + modelFrom.prototype['__destroyById__' + relationName] = destroyByIdFunc; var updateByIdFunc = scopeMethods.updateById; - modelFrom.prototype['__updateById__' + accessorName] = updateByIdFunc; + modelFrom.prototype['__updateById__' + relationName] = updateByIdFunc; var addFunc = scopeMethods.add; - modelFrom.prototype['__link__' + accessorName] = addFunc; + modelFrom.prototype['__link__' + relationName] = addFunc; var removeFunc = scopeMethods.remove; - modelFrom.prototype['__unlink__' + accessorName] = removeFunc; + modelFrom.prototype['__unlink__' + relationName] = removeFunc; scopeMethods.create = scopeMethod(definition, 'create'); scopeMethods.build = scopeMethod(definition, 'build'); @@ -1607,12 +1611,12 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) var methodName = customMethods[i]; var method = scopeMethods[methodName]; 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 - var scopeDefinition = defineScope(modelFrom.prototype, modelTo, accessorName, function () { + var scopeDefinition = defineScope(modelFrom.prototype, modelTo, relationName, function () { return {}; }, scopeMethods, definition.options); @@ -1623,7 +1627,7 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) { var modelTo = this.definition.modelTo; - var relationName = this.definition.name; + var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; 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'); } - var embeddedList = self[relationName] || []; + var embeddedList = self[propertyName] || []; this.definition.applyScope(modelInstance, actualCond); @@ -1664,12 +1668,12 @@ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, cb }; EmbedsMany.prototype.findById = function (fkId, cb) { - var pk = this.definition.keyFrom; + var pk = this.definition.keyTo; var modelTo = this.definition.modelTo; - var relationName = this.definition.name; + var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var embeddedList = modelInstance[relationName] || []; + var embeddedList = modelInstance[propertyName] || []; var find = function(id) { 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 relationName = this.definition.name; + var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var embeddedList = modelInstance[relationName] || []; + var embeddedList = modelInstance[propertyName] || []; var inst = this.findById(fkId); @@ -1725,7 +1729,7 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) { } if (typeof cb === 'function') { - modelInstance.updateAttribute(relationName, + modelInstance.updateAttribute(propertyName, embeddedList, function(err) { cb(err, inst); }); @@ -1740,10 +1744,10 @@ EmbedsMany.prototype.updateById = function (fkId, data, cb) { EmbedsMany.prototype.destroyById = function (fkId, cb) { var modelTo = this.definition.modelTo; - var relationName = this.definition.name; + var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var embeddedList = modelInstance[relationName] || []; + var embeddedList = modelInstance[propertyName] || []; var inst = (fkId instanceof modelTo) ? fkId : this.findById(fkId); @@ -1751,7 +1755,7 @@ EmbedsMany.prototype.destroyById = function (fkId, cb) { var index = embeddedList.indexOf(inst); if (index > -1) embeddedList.splice(index, 1); if (typeof cb === 'function') { - modelInstance.updateAttribute(relationName, + modelInstance.updateAttribute(propertyName, embeddedList, function(err) { cb(err); }); @@ -1768,10 +1772,10 @@ EmbedsMany.prototype.unset = EmbedsMany.prototype.destroyById; EmbedsMany.prototype.at = function (index, cb) { var modelTo = this.definition.modelTo; - var relationName = this.definition.name; + var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; - var embeddedList = modelInstance[relationName] || []; + var embeddedList = modelInstance[propertyName] || []; var item = embeddedList[parseInt(index)]; item = (item instanceof modelTo) ? item : null; @@ -1786,9 +1790,9 @@ EmbedsMany.prototype.at = function (index, cb) { }; EmbedsMany.prototype.create = function (targetModelData, cb) { - var pk = this.definition.keyFrom; + var pk = this.definition.keyTo; var modelTo = this.definition.modelTo; - var relationName = this.definition.name; + var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; var autoId = this.definition.options.autoId !== false; @@ -1798,7 +1802,7 @@ EmbedsMany.prototype.create = function (targetModelData, cb) { } targetModelData = targetModelData || {}; - var embeddedList = modelInstance[relationName] || []; + var embeddedList = modelInstance[propertyName] || []; 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) { cb(err, err ? null : inst); }); }; EmbedsMany.prototype.build = function(targetModelData) { - var pk = this.definition.keyFrom; + var pk = this.definition.keyTo; var modelTo = this.definition.modelTo; - var relationName = this.definition.name; + var propertyName = this.definition.keyFrom; var modelInstance = this.modelInstance; var autoId = this.definition.options.autoId !== false; - var embeddedList = modelInstance[relationName] || []; + var embeddedList = modelInstance[propertyName] || []; targetModelData = targetModelData || {}; @@ -1925,7 +1929,7 @@ EmbedsMany.prototype.remove = function (acInst, cb) { belongsTo.applyScope(modelInstance, filter); - modelInstance[definition.accessor](filter, function(err, items) { + modelInstance[definition.name](filter, function(err, items) { if (err) return cb(err); items.forEach(function(item) { diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index 18f0e0ad..458f91db 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -903,6 +903,26 @@ describe('Load models with relations', function () { assert(Post.relations['user']); 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) { var ds = new DataSource('memory');