diff --git a/lib/relation-definition.js b/lib/relation-definition.js index 2db36355..92c23ab2 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -101,8 +101,9 @@ RelationDefinition.prototype.toJSON = function () { */ RelationDefinition.prototype.applyScope = function(modelInstance, filter) { filter.where = filter.where || {}; - if (this.type !== 'belongsTo' && typeof this.typeTo === 'string') { - filter.where[this.typeTo] = this.modelFrom.modelName; // polymorphic + if ((this.type !== 'belongsTo' || this.type === 'hasOne') + && typeof this.typeTo === 'string') { // polymorphic + filter.where[this.typeTo] = this.modelFrom.modelName; } if (typeof this.scope === 'function') { var scope = this.scope.call(this, modelInstance, filter); @@ -131,8 +132,9 @@ RelationDefinition.prototype.applyProperties = function(modelInstance, target) { target[key] = modelInstance[k]; } } - if (this.type !== 'belongsTo' && typeof this.typeTo === 'string') { - target[this.typeTo] = this.modelFrom.modelName; // polymorphic + if ((this.type !== 'belongsTo' || this.type === 'hasOne') + && typeof this.typeTo === 'string') { // polymorphic + target[this.typeTo] = this.modelFrom.modelName; } }; @@ -365,10 +367,10 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) { var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true); var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id'; - var polymorphic, typeTo; + var typeTo; if (typeof params.polymorphic === 'string') { - polymorphic = params.polymorphic; + var polymorphic = params.polymorphic; fk = i8n.camelize(polymorphic + '_id', true); typeTo = i8n.camelize(polymorphic + '_type', true); if (!params.through) { @@ -1049,15 +1051,17 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, if (params.model) { modelTo = params.model; } else { - modelTo = lookupModel(models, i8n.singularize(modelTo)) || - modelTo; + modelTo = lookupModel(models, i8n.singularize(modelTo)) || modelTo; } if (typeof modelTo === 'string') { throw new Error('Could not find "' + modelTo + '" relation for ' + modelFrom.modelName); } } + var isPolymorphic = (typeof params.polymorphic === 'string'); + if (!params.through) { + if (isPolymorphic) throw new Error('Polymorphic relations need a through model'); var name1 = modelFrom.modelName + modelTo.modelName; var name2 = modelTo.modelName + modelFrom.modelName; params.through = lookupModel(models, name1) || lookupModel(models, name2) || @@ -1066,7 +1070,7 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, var options = {as: params.as, through: params.through}; - if (typeof params.polymorphic === 'string') { + if (isPolymorphic) { options.polymorphic = params.polymorphic; var accessor = params.through.prototype[params.polymorphic]; if (typeof accessor !== 'function') { // declare once @@ -1113,6 +1117,16 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) { var relationName = params.as || i8n.camelize(modelTo.modelName, true); var fk = params.foreignKey || i8n.camelize(modelFrom.modelName + '_id', true); + var typeTo; + + if (typeof params.polymorphic === 'string') { + var polymorphic = params.polymorphic; + fk = i8n.camelize(polymorphic + '_id', true); + typeTo = i8n.camelize(polymorphic + '_type', true); + if (!params.through) { + modelTo.dataSource.defineProperty(modelTo.modelName, typeTo, { type: 'string', index: true }); + } + } var relationDef = modelFrom.relations[relationName] = new RelationDefinition({ name: relationName, @@ -1120,6 +1134,7 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) { modelFrom: modelFrom, keyFrom: pk, keyTo: fk, + typeTo: typeTo, modelTo: modelTo, properties: params.properties, options: params.options diff --git a/test/relations.test.js b/test/relations.test.js index 11ffb97a..824d710f 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -528,6 +528,101 @@ describe('relations', function () { }); + describe('polymorphic hasOne', function () { + before(function (done) { + db = getSchema(); + Picture = db.define('Picture', {name: String}); + Author = db.define('Author', {name: String}); + Reader = db.define('Reader', {name: String}); + + db.automigrate(function () { + Picture.destroyAll(function () { + Author.destroyAll(function () { + Reader.destroyAll(done); + }); + }); + }); + }); + + it('can be declared', function (done) { + Author.hasOne(Picture, { as: 'avatar', polymorphic: 'imageable' }); + Reader.hasOne(Picture, { as: 'mugshot', polymorphic: 'imageable' }); + Picture.belongsTo('imageable', { polymorphic: true }); + db.automigrate(done); + }); + + it('should create polymorphic relation - author', function (done) { + Author.create({ id: 8, name: 'Author 1' }, function (err, author) { + author.avatar.create({ name: 'Avatar' }, function (err, p) { + should.not.exist(err); + should.exist(p); + p.imageableId.should.equal(author.id); + p.imageableType.should.equal('Author'); + done(); + }); + }); + }); + + it('should create polymorphic relation - reader', function (done) { + Reader.create({ id: 6, name: 'Reader 1' }, function (err, reader) { + reader.mugshot.create({ name: 'Mugshot' }, function (err, p) { + should.not.exist(err); + should.exist(p); + p.imageableId.should.equal(reader.id); + p.imageableType.should.equal('Reader'); + done(); + }); + }); + }); + + it('should find polymorphic relation - author', function (done) { + Author.findOne(function (err, author) { + author.avatar(function (err, p) { + should.not.exist(err); + p.name.should.equal('Avatar'); + p.imageableId.should.equal(author.id); + p.imageableType.should.equal('Author'); + done(); + }); + }); + }); + + it('should find polymorphic relation - reader', function (done) { + Reader.findOne(function (err, reader) { + reader.mugshot(function (err, p) { + should.not.exist(err); + p.name.should.equal('Mugshot'); + p.imageableId.should.equal(reader.id); + p.imageableType.should.equal('Reader'); + done(); + }); + }); + }); + + it('should find inverse polymorphic relation - author', function (done) { + Picture.findOne({ where: { name: 'Avatar' } }, function (err, p) { + p.imageable(function (err, imageable) { + should.not.exist(err); + imageable.should.be.instanceof(Author); + imageable.name.should.equal('Author 1'); + done(); + }); + }); + }); + + it('should find inverse polymorphic relation - reader', function (done) { + Picture.findOne({ where: { name: 'Mugshot' } }, function (err, p) { + p.imageable(function (err, imageable) { + should.not.exist(err); + imageable.should.be.instanceof(Reader); + imageable.name.should.equal('Reader 1'); + done(); + }); + }); + }); + + }); + describe('polymorphic hasMany', function () { before(function (done) { db = getSchema();