Implemented polymorphic hasOne

Signed-off-by: Fabien Franzen <info@atelierfabien.be>
This commit is contained in:
Fabien Franzen 2014-07-26 15:20:46 +02:00
parent 295e6fc1f1
commit 00dfe563eb
2 changed files with 119 additions and 9 deletions

View File

@ -101,8 +101,9 @@ RelationDefinition.prototype.toJSON = function () {
*/ */
RelationDefinition.prototype.applyScope = function(modelInstance, filter) { RelationDefinition.prototype.applyScope = function(modelInstance, filter) {
filter.where = filter.where || {}; filter.where = filter.where || {};
if (this.type !== 'belongsTo' && typeof this.typeTo === 'string') { if ((this.type !== 'belongsTo' || this.type === 'hasOne')
filter.where[this.typeTo] = this.modelFrom.modelName; // polymorphic && typeof this.typeTo === 'string') { // polymorphic
filter.where[this.typeTo] = this.modelFrom.modelName;
} }
if (typeof this.scope === 'function') { if (typeof this.scope === 'function') {
var scope = this.scope.call(this, modelInstance, filter); var scope = this.scope.call(this, modelInstance, filter);
@ -131,8 +132,9 @@ RelationDefinition.prototype.applyProperties = function(modelInstance, target) {
target[key] = modelInstance[k]; target[key] = modelInstance[k];
} }
} }
if (this.type !== 'belongsTo' && typeof this.typeTo === 'string') { if ((this.type !== 'belongsTo' || this.type === 'hasOne')
target[this.typeTo] = this.modelFrom.modelName; // polymorphic && 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 fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id'; var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
var polymorphic, typeTo; var typeTo;
if (typeof params.polymorphic === 'string') { if (typeof params.polymorphic === 'string') {
polymorphic = params.polymorphic; var polymorphic = params.polymorphic;
fk = i8n.camelize(polymorphic + '_id', true); fk = i8n.camelize(polymorphic + '_id', true);
typeTo = i8n.camelize(polymorphic + '_type', true); typeTo = i8n.camelize(polymorphic + '_type', true);
if (!params.through) { if (!params.through) {
@ -1049,15 +1051,17 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom,
if (params.model) { if (params.model) {
modelTo = params.model; modelTo = params.model;
} else { } else {
modelTo = lookupModel(models, i8n.singularize(modelTo)) || modelTo = lookupModel(models, i8n.singularize(modelTo)) || modelTo;
modelTo;
} }
if (typeof modelTo === 'string') { if (typeof modelTo === 'string') {
throw new Error('Could not find "' + modelTo + '" relation for ' + modelFrom.modelName); throw new Error('Could not find "' + modelTo + '" relation for ' + modelFrom.modelName);
} }
} }
var isPolymorphic = (typeof params.polymorphic === 'string');
if (!params.through) { if (!params.through) {
if (isPolymorphic) throw new Error('Polymorphic relations need a through model');
var name1 = modelFrom.modelName + modelTo.modelName; var name1 = modelFrom.modelName + modelTo.modelName;
var name2 = modelTo.modelName + modelFrom.modelName; var name2 = modelTo.modelName + modelFrom.modelName;
params.through = lookupModel(models, name1) || lookupModel(models, name2) || 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}; var options = {as: params.as, through: params.through};
if (typeof params.polymorphic === 'string') { if (isPolymorphic) {
options.polymorphic = params.polymorphic; options.polymorphic = params.polymorphic;
var accessor = params.through.prototype[params.polymorphic]; var accessor = params.through.prototype[params.polymorphic];
if (typeof accessor !== 'function') { // declare once 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 relationName = params.as || i8n.camelize(modelTo.modelName, true);
var fk = params.foreignKey || i8n.camelize(modelFrom.modelName + '_id', 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({ var relationDef = modelFrom.relations[relationName] = new RelationDefinition({
name: relationName, name: relationName,
@ -1120,6 +1134,7 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
modelFrom: modelFrom, modelFrom: modelFrom,
keyFrom: pk, keyFrom: pk,
keyTo: fk, keyTo: fk,
typeTo: typeTo,
modelTo: modelTo, modelTo: modelTo,
properties: params.properties, properties: params.properties,
options: params.options options: params.options

View File

@ -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 () { describe('polymorphic hasMany', function () {
before(function (done) { before(function (done) {
db = getSchema(); db = getSchema();