From 295e6fc1f113c70afe4135cfc1a492e1d155bac5 Mon Sep 17 00:00:00 2001 From: Fabien Franzen Date: Sat, 26 Jul 2014 14:54:54 +0200 Subject: [PATCH] Implemented polymorphic hasAndBelongsToMany --- lib/relation-definition.js | 60 ++++++++++++++++--------- test/relations.test.js | 90 +++++++++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 21 deletions(-) diff --git a/lib/relation-definition.js b/lib/relation-definition.js index 5411f6a0..2db36355 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -365,13 +365,14 @@ 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 typeTo; + var polymorphic, typeTo; if (typeof params.polymorphic === 'string') { - fk = i8n.camelize(params.polymorphic + '_id', true); - var typeTo = i8n.camelize(params.polymorphic + '_type', true); + 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' }); + modelTo.dataSource.defineProperty(modelTo.modelName, typeTo, { type: 'string', index: true }); } } @@ -389,12 +390,11 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) { options: params.options }); - if (params.through) { - definition.modelThrough = params.through; - var keyThrough = definition.throughKey || i8n.camelize(modelTo.modelName + '_id', true); - definition.keyThrough = keyThrough; - } - + definition.modelThrough = params.through; + + var keyThrough = definition.throughKey || i8n.camelize(modelTo.modelName + '_id', true); + definition.keyThrough = keyThrough; + modelFrom.relations[relationName] = definition; if (!params.through) { @@ -447,6 +447,7 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) { filter.collect = i8n.camelize(modelTo.modelName, true); filter.include = filter.collect; } + return filter; }, scopeMethods); @@ -645,7 +646,7 @@ HasManyThrough.prototype.create = function create(data, done) { var definition = this.definition; var modelTo = definition.modelTo; var modelThrough = definition.modelThrough; - + if (typeof data === 'function' && !done) { done = data; data = {}; @@ -660,9 +661,16 @@ HasManyThrough.prototype.create = function create(data, done) { } // The primary key for the target model var pk2 = definition.modelTo.definition.idName(); - var fk1 = findBelongsTo(modelThrough, definition.modelFrom, - definition.keyFrom); - var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2); + + if (definition.typeTo) { // polymorphic + var fk1 = definition.keyTo; + var fk2 = definition.keyThrough; + } else { + var fk1 = findBelongsTo(modelThrough, definition.modelFrom, + definition.keyFrom); + var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2); + } + var d = {}; d[fk1] = modelInstance[definition.keyFrom]; d[fk2] = to[pk2]; @@ -841,12 +849,12 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) { var typeTo = i8n.camelize(polymorphic + '_type', true); if (typeof params.idType === 'string') { // explicit key type - modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, { type: params.idType }); + modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, { type: params.idType, index: true }); } else { // try to use the same foreign key type as modelFrom modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelFrom.modelName); } - modelFrom.dataSource.defineProperty(modelFrom.modelName, typeTo, { type: 'string' }); + modelFrom.dataSource.defineProperty(modelFrom.modelName, typeTo, { type: 'string', index: true }); } else { var idName = modelFrom.dataSource.idName(modelTo.modelName) || 'id'; var relationName = params.as || i8n.camelize(modelTo.modelName, true); @@ -1055,10 +1063,22 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, params.through = lookupModel(models, name1) || lookupModel(models, name2) || modelFrom.dataSource.define(name1); } - params.through.belongsTo(modelFrom); - params.through.belongsTo(modelTo); - - this.hasMany(modelFrom, modelTo, {as: params.as, through: params.through}); + + var options = {as: params.as, through: params.through}; + + if (typeof params.polymorphic === 'string') { + options.polymorphic = params.polymorphic; + var accessor = params.through.prototype[params.polymorphic]; + if (typeof accessor !== 'function') { // declare once + params.through.belongsTo(modelTo); + params.through.belongsTo(params.polymorphic, { polymorphic: true }); + } + } else { + params.through.belongsTo(modelFrom); + params.through.belongsTo(modelTo); + } + + this.hasMany(modelFrom, modelTo, options); }; diff --git a/test/relations.test.js b/test/relations.test.js index 1d1fcf7c..11ffb97a 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -3,7 +3,7 @@ var should = require('./init.js'); var db, Book, Chapter, Author, Reader; var Category, Product; -var Picture; +var Picture, PictureLink; describe('relations', function () { @@ -670,6 +670,94 @@ describe('relations', function () { }); }); + + describe('polymorphic hasAndBelongsToMany through', function () { + before(function (done) { + db = getSchema(); + Picture = db.define('Picture', {name: String}); + Author = db.define('Author', {name: String}); + Reader = db.define('Reader', {name: String}); + PictureLink = db.define('PictureLink', {}); + + db.automigrate(function () { + Picture.destroyAll(function () { + PictureLink.destroyAll(function () { + Author.destroyAll(function () { + Reader.destroyAll(done); + }); + }); + }); + }); + }); + + it('can be declared', function (done) { + Author.hasAndBelongsToMany(Picture, { through: PictureLink, polymorphic: 'imageable' }); + Reader.hasAndBelongsToMany(Picture, { through: PictureLink, polymorphic: 'imageable' }); + db.automigrate(done); + }); + + it('should create polymorphic relation - author', function (done) { + Author.create({ id: 3, name: 'Author 1' }, function (err, author) { + author.pictures.create({ name: 'Author Pic 1' }, function (err, p) { + should.not.exist(err); + p.id.should.equal(1); + author.pictures.create({ name: 'Author Pic 2' }, function (err, p) { + should.not.exist(err); + p.id.should.equal(2); + done(); + }); + }); + }); + }); + + it('should create polymorphic relation - reader', function (done) { + Reader.create({ id: 4, name: 'Reader 1' }, function (err, reader) { + reader.pictures.create({ name: 'Reader Pic 1' }, function (err, p) { + should.not.exist(err); + p.id.should.equal(3); + done(); + }); + }); + }); + + it('should create polymorphic through model', function (done) { + PictureLink.findOne(function(err, link) { + should.not.exist(err); + link.pictureId.should.equal(1); + link.imageableId.should.equal(3); + link.imageableType.should.equal('Author'); + done(); + }); + }); + + it('should get polymorphic relation through model - author', function (done) { + Author.findById(3, function(err, author) { + should.not.exist(err); + author.name.should.equal('Author 1'); + author.pictures(function(err, pics) { + should.not.exist(err); + pics.should.have.length(2); + pics[0].name.should.equal('Author Pic 1'); + pics[1].name.should.equal('Author Pic 2'); + done(); + }); + }); + }); + + it('should get polymorphic relation through model - reader', function (done) { + Reader.findById(4, function(err, reader) { + should.not.exist(err); + reader.name.should.equal('Reader 1'); + reader.pictures(function(err, pics) { + should.not.exist(err); + pics.should.have.length(1); + pics[0].name.should.equal('Reader Pic 1'); + done(); + }); + }); + }); + + }); describe('belongsTo', function () { var List, Item, Fear, Mind;