diff --git a/lib/include.js b/lib/include.js index 2b106250..7e3dab96 100644 --- a/lib/include.js +++ b/lib/include.js @@ -272,7 +272,10 @@ Inclusion.include = function (objects, include, options, cb) { } else { if (polymorphic) { - return includePolymorphic(cb); + if (relation.type === 'hasOne') { + return includePolymorphicHasOne(cb); + } + return includePolymorphicBelongsTo(cb); } if (relation.type === 'embedsOne') { return includeEmbeds(cb); @@ -523,9 +526,14 @@ Inclusion.include = function (objects, include, options, cb) { function targetLinkingTask(next) { async.each(targets, linkManyToOne, next); function linkManyToOne(target, next) { - var obj = objIdMap[target[relation.keyTo].toString()]; - obj.__cachedRelations[relationName].push(target); - processTargetObj(obj, next); + //fix for bug in hasMany with referencesMany + var targetIds = [].concat(target[relation.keyTo]); + async.each(targetIds, function (targetId, proceed) { + var obj = objIdMap[targetId.toString()]; + if (!obj) return proceed(); + obj.__cachedRelations[relationName].push(target); + processTargetObj(obj, proceed); + }, next); } } @@ -534,10 +542,10 @@ Inclusion.include = function (objects, include, options, cb) { } /** - * Handle Inclusion of Polymorphic BelongsTo/HasOne relation + * Handle Inclusion of Polymorphic BelongsTo relation * @param callback */ - function includePolymorphic(callback) { + function includePolymorphicBelongsTo(callback) { var targetIdsByType = {}; //Map for Indexing objects by their type and targetId for faster retrieval var targetObjMapByType = {}; @@ -623,6 +631,66 @@ Inclusion.include = function (objects, include, options, cb) { } } + + /** + * Handle Inclusion of Polymorphic HasOne relation + * @param callback + */ + function includePolymorphicHasOne(callback) { + var sourceIds = []; + //Map for Indexing objects by their id for faster retrieval + var objIdMap = {}; + for (var i = 0; i < objs.length; i++) { + var obj = objs[i]; + // one-to-one: foreign key reference is modelTo -> modelFrom. + // use modelFrom.keyFrom in where filter later + var sourceId = obj[relation.keyFrom]; + if (sourceId) { + sourceIds.push(sourceId); + objIdMap[sourceId.toString()] = obj; + } + // sourceId can be null. but cache empty data as result + defineCachedRelations(obj); + obj.__cachedRelations[relationName] = null; + } + filter.where[relation.keyTo] = { + inq: uniq(sourceIds) + }; + relation.applyScope(null, filter); + relation.modelTo.find(filter, targetFetchHandler); + /** + * Process fetched related objects + * @param err + * @param {Array} targets + * @returns {*} + */ + function targetFetchHandler(err, targets) { + if (err) { + return callback(err); + } + var tasks = []; + //simultaneously process subIncludes + if (subInclude && targets) { + tasks.push(function subIncludesTask(next) { + relation.modelTo.include(targets, subInclude, options, next); + }); + } + //process each target object + tasks.push(targetLinkingTask); + function targetLinkingTask(next) { + async.each(targets, linkOneToOne, next); + function linkOneToOne(target, next) { + var sourceId = target[relation.keyTo]; + var obj = objIdMap[sourceId.toString()]; + obj.__cachedRelations[relationName] = target; + processTargetObj(obj, next); + } + } + + execTasksWithInterLeave(tasks, callback); + } + } + /** * Handle Inclusion of BelongsTo/HasOne relation * @param callback diff --git a/test/relations.test.js b/test/relations.test.js index 8516492d..7a3b1238 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -1663,6 +1663,16 @@ describe('relations', function () { }); }); + it('should include polymorphic relation - author', function (done) { + Author.findOne({include: 'avatar'}, function (err, author) { + should.not.exists(err); + var avatar = author.avatar(); + should.exist(avatar); + avatar.name.should.equal('Avatar'); + done(); + }); + }); + it('should find polymorphic relation with promises - reader', function (done) { Reader.findOne() .then(function (reader) { @@ -1688,6 +1698,18 @@ describe('relations', function () { }); }); + it('should include inverse polymorphic relation - author', function (done) { + Picture.findOne({where: {name: 'Avatar'}, include: 'imageable'}, + function (err, p) { + should.not.exists(err); + var imageable = p.imageable(); + should.exist(imageable); + 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) { should.not.exists(err); @@ -1843,6 +1865,28 @@ describe('relations', function () { }); }); + it('should include polymorphic relation - reader', function (done) { + Reader.findOne({include: 'mugshot'}, + function (err, reader) { + should.not.exists(err); + var mugshot = reader.mugshot(); + should.exist(mugshot); + mugshot.name.should.equal('Mugshot'); + done(); + }); + }); + + it('should include inverse polymorphic relation - reader', function (done) { + Picture.findOne({where: {name: 'Mugshot'}, include: 'owner'}, + function (err, p) { + should.not.exists(err); + var owner = p.owner(); + should.exist(owner); + owner.should.be.instanceof(Reader); + owner.name.should.equal('Reader 1'); + done(); + }); + }); }); describe('polymorphic hasMany', function () { @@ -2030,6 +2074,19 @@ describe('relations', function () { }); }); + it('should include the inverse of polymorphic relation - author', + function (done) { + Picture.findOne({where: {name: 'Sample'}, include: 'imageable'}, + function (err, p) { + should.not.exist(err); + var imageable = p.imageable(); + should.exist(imageable); + imageable.should.be.instanceof(Author); + imageable.name.should.equal('Author 2'); + done(); + }); + }); + }); describe('polymorphic hasAndBelongsToMany through', function () {