Merge branch 'fabien-feature/polymorphic-rel'

This commit is contained in:
Raymond Feng 2014-07-28 14:24:24 -07:00
commit c8c499320c
2 changed files with 671 additions and 54 deletions

View File

@ -64,8 +64,11 @@ function RelationDefinition(definition) {
assert(this.modelFrom, 'Source model is required');
this.keyFrom = definition.keyFrom;
this.modelTo = definition.modelTo;
assert(this.modelTo, 'Target model is required');
this.keyTo = definition.keyTo;
this.discriminator = definition.discriminator;
if (!this.discriminator) {
assert(this.modelTo, 'Target model is required');
}
this.modelThrough = definition.modelThrough;
this.keyThrough = definition.keyThrough;
this.multiple = (this.type !== 'belongsTo' && this.type !== 'hasOne');
@ -97,13 +100,18 @@ RelationDefinition.prototype.toJSON = function () {
* @param {Object} filter (where, order, limit, fields, ...)
*/
RelationDefinition.prototype.applyScope = function(modelInstance, filter) {
filter.where = filter.where || {};
if ((this.type !== 'belongsTo' || this.type === 'hasOne')
&& typeof this.discriminator === 'string') { // polymorphic
filter.where[this.discriminator] = this.modelFrom.modelName;
}
if (typeof this.scope === 'function') {
var scope = this.scope.call(this, modelInstance, filter);
if (typeof scope === 'object') {
mergeQuery(filter, scope);
}
} else if (typeof this.scope === 'object') {
mergeQuery(filter, this.scope);
} else {
var scope = this.scope;
}
if (typeof scope === 'object') {
mergeQuery(filter, scope);
}
};
@ -124,6 +132,10 @@ RelationDefinition.prototype.applyProperties = function(modelInstance, target) {
target[key] = modelInstance[k];
}
}
if ((this.type !== 'belongsTo' || this.type === 'hasOne')
&& typeof this.discriminator === 'string') { // polymorphic
target[this.discriminator] = this.modelFrom.modelName;
}
};
/**
@ -316,6 +328,19 @@ function lookupModel(models, modelName) {
}
}
/*!
* Normalize polymorphic parameters
* @param {Object|String} params Name of the polymorphic relation or params
* @returns {Object} The normalized parameters
*/
function polymorphicParams(params) {
if (typeof params === 'string') params = { as: params };
if (typeof params.as !== 'string') params.as = 'reference'; // default
params.foreignKey = params.foreignKey || i8n.camelize(params.as + '_id', true);
params.discriminator = params.discriminator || i8n.camelize(params.as + '_type', true);
return params;
}
/**
* Define a "one to many" relationship by specifying the model name
*
@ -350,10 +375,23 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
}
}
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
var discriminator;
if (params.polymorphic) {
var polymorphic = polymorphicParams(params.polymorphic);
discriminator = polymorphic.discriminator;
if (!params.invert) {
fk = polymorphic.foreignKey;
}
if (!params.through) {
modelTo.dataSource.defineProperty(modelTo.modelName, discriminator, { type: 'string', index: true });
}
}
var definition = new RelationDefinition({
name: relationName,
@ -361,6 +399,7 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
modelFrom: modelFrom,
keyFrom: idName,
keyTo: fk,
discriminator: discriminator,
modelTo: modelTo,
multiple: true,
properties: params.properties,
@ -368,16 +407,17 @@ 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) {
// obviously, modelTo should have attribute called `fk`
// for polymorphic relations, it is assumed to share the same fk type for all
// polymorphic models
modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName);
}
@ -423,10 +463,15 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
definition.applyScope(this, filter);
if (params.through) {
if (params.through && params.polymorphic && params.invert) {
filter.where[discriminator] = modelTo.modelName; // overwrite
filter.collect = params.polymorphic;
filter.include = filter.collect;
} else if (params.through) {
filter.collect = i8n.camelize(modelTo.modelName, true);
filter.include = filter.collect;
}
return filter;
}, scopeMethods);
@ -555,6 +600,21 @@ HasMany.prototype.destroyById = function (fkId, cb) {
});
};
var throughKeys = function(definition) {
var modelThrough = definition.modelThrough;
var pk2 = definition.modelTo.definition.idName();
if (definition.discriminator) { // 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);
}
return [fk1, fk2];
}
/**
* Find a related item by foreign key
* @param {*} fkId The foreign key value
@ -629,7 +689,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 = {};
@ -644,9 +704,11 @@ 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);
var keys = throughKeys(definition);
var fk1 = keys[0];
var fk2 = keys[1];
var d = {};
d[fk1] = modelInstance[definition.keyFrom];
d[fk2] = to[pk2];
@ -668,6 +730,8 @@ HasManyThrough.prototype.create = function create(data, done) {
});
};
/**
* Add the target model instance to the 'hasMany' relation
* @param {Object|ID} acInst The actual instance or id value
@ -680,14 +744,13 @@ HasManyThrough.prototype.add = function (acInst, done) {
var data = {};
var query = {};
var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
definition.keyFrom);
// The primary key for the target model
var pk2 = definition.modelTo.definition.idName();
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
var keys = throughKeys(definition);
var fk1 = keys[0];
var fk2 = keys[1];
query[fk1] = this.modelInstance[pk1];
query[fk2] = (acInst instanceof definition.modelTo) ? acInst[pk2] : acInst;
@ -698,6 +761,7 @@ HasManyThrough.prototype.add = function (acInst, done) {
data[fk1] = this.modelInstance[pk1];
data[fk2] = (acInst instanceof definition.modelTo) ? acInst[pk2] : acInst;
definition.applyProperties(this.modelInstance, data);
// Create an instance of the through model
@ -722,18 +786,21 @@ HasManyThrough.prototype.exists = function (acInst, done) {
var query = {};
var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
definition.keyFrom);
// The primary key for the target model
var pk2 = definition.modelTo.definition.idName();
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
var keys = throughKeys(definition);
var fk1 = keys[0];
var fk2 = keys[1];
query[fk1] = this.modelInstance[pk1];
query[fk2] = (acInst instanceof definition.modelTo) ? acInst[pk2] : acInst;
modelThrough.count(query, function(err, ac) {
var filter = { where: query };
definition.applyScope(this.modelInstance, filter);
modelThrough.count(filter.where, function(err, ac) {
done(err, ac > 0);
});
};
@ -750,13 +817,12 @@ HasManyThrough.prototype.remove = function (acInst, done) {
var query = {};
var fk1 = findBelongsTo(modelThrough, definition.modelFrom,
definition.keyFrom);
// The primary key for the target model
var pk2 = definition.modelTo.definition.idName();
var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);
var keys = throughKeys(definition);
var fk1 = keys[0];
var fk2 = keys[1];
query[fk1] = this.modelInstance[pk1];
query[fk2] = (acInst instanceof definition.modelTo) ? acInst[pk2] : acInst;
@ -764,7 +830,7 @@ HasManyThrough.prototype.remove = function (acInst, done) {
var filter = { where: query };
definition.applyScope(this.modelInstance, filter);
modelThrough.deleteAll(filter.where, function (err) {
if (!err) {
self.removeFromCache(query[fk2]);
@ -799,8 +865,9 @@ HasManyThrough.prototype.remove = function (acInst, done) {
*
*/
RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
var discriminator, polymorphic;
params = params || {};
if ('string' === typeof modelTo) {
if ('string' === typeof modelTo && !params.polymorphic) {
params.as = modelTo;
if (params.model) {
modelTo = params.model;
@ -810,9 +877,36 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
}
}
var idName = modelFrom.dataSource.idName(modelTo.modelName) || 'id';
var relationName = params.as || i8n.camelize(modelTo.modelName, true);
var fk = params.foreignKey || relationName + 'Id';
var idName, relationName, fk;
if (params.polymorphic) {
if (params.polymorphic === true) {
// modelTo arg will be the name of the polymorphic relation (string)
polymorphic = polymorphicParams(modelTo);
} else {
polymorphic = polymorphicParams(params.polymorphic);
}
modelTo = null; // will lookup dynamically
idName = params.idName || 'id';
relationName = params.as || polymorphic.as;
fk = polymorphic.foreignKey;
discriminator = polymorphic.discriminator;
if (typeof polymorphic.idType === 'string') { // explicit key type
modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, { type: polymorphic.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, discriminator, { type: 'string', index: true });
} else {
idName = modelFrom.dataSource.idName(modelTo.modelName) || 'id';
relationName = params.as || i8n.camelize(modelTo.modelName, true);
fk = params.foreignKey || relationName + 'Id';
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelTo.modelName);
}
var relationDef = modelFrom.relations[relationName] = new RelationDefinition({
name: relationName,
@ -820,14 +914,13 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
modelFrom: modelFrom,
keyFrom: fk,
keyTo: idName,
discriminator: discriminator,
modelTo: modelTo,
properties: params.properties,
scope: params.scope,
options: params.options
});
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelTo.modelName);
// Define a property for the scope so that we have 'this' for the scoped methods
Object.defineProperty(modelFrom.prototype, relationName, {
enumerable: true,
@ -837,7 +930,9 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
var relationMethod = relation.related.bind(relation);
relationMethod.create = relation.create.bind(relation);
relationMethod.build = relation.build.bind(relation);
relationMethod._targetClass = relationDef.modelTo.modelName;
if (relationDef.modelTo) {
relationMethod._targetClass = relationDef.modelTo.modelName;
}
return relationMethod;
}
});
@ -865,7 +960,7 @@ BelongsTo.prototype.create = function(targetModelData, cb) {
}
this.definition.applyProperties(modelInstance, targetModelData || {});
modelTo.create(targetModelData, function(err, targetModel) {
if(!err) {
modelInstance[fk] = targetModel[pk];
@ -896,7 +991,9 @@ BelongsTo.prototype.build = function(targetModelData) {
*/
BelongsTo.prototype.related = function (refresh, params) {
var self = this;
var modelFrom = this.definition.modelFrom;
var modelTo = this.definition.modelTo;
var discriminator = this.definition.discriminator;
var pk = this.definition.keyTo;
var fk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
@ -907,15 +1004,30 @@ BelongsTo.prototype.related = function (refresh, params) {
} else if (arguments.length > 2) {
throw new Error('Method can\'t be called with more than two arguments');
}
var cachedValue;
if (!refresh) {
cachedValue = self.getCache();
}
if (params instanceof ModelBaseClass) { // acts as setter
modelTo = params.constructor;
modelInstance[fk] = params[pk];
if (discriminator) modelInstance[discriminator] = params.constructor.modelName;
self.resetCache(params);
} else if (typeof params === 'function') { // acts as async getter
if (discriminator && !modelTo) {
var modelToName = modelInstance[discriminator];
if (typeof modelToName !== 'string') {
throw new Error('Polymorphic model not found: `' + discriminator + '` not set');
}
modelToName = modelToName.toLowerCase();
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
if (!modelTo) {
throw new Error('Polymorphic model not found: `' + modelToName + '`');
}
}
var cb = params;
if (cachedValue === undefined) {
var query = {where: {}};
@ -987,24 +1099,40 @@ 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);
}
}
if (!params.through) {
if (params.polymorphic) 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) ||
modelFrom.dataSource.define(name1);
}
params.through.belongsTo(modelFrom);
var options = {as: params.as, through: params.through};
options.properties = params.properties;
options.scope = params.scope;
if (params.polymorphic) {
var polymorphic = polymorphicParams(params.polymorphic);
options.polymorphic = polymorphic; // pass through
var accessor = params.through.prototype[polymorphic.as];
if (typeof accessor !== 'function') { // declare once
// use the name of the polymorphic rel, not modelTo
params.through.belongsTo(polymorphic.as, { polymorphic: true });
}
} else {
params.through.belongsTo(modelFrom);
}
params.through.belongsTo(modelTo);
this.hasMany(modelFrom, modelTo, {as: params.as, through: params.through});
this.hasMany(modelFrom, modelTo, options);
};
@ -1039,6 +1167,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 discriminator;
if (params.polymorphic) {
var polymorphic = polymorphicParams(params.polymorphic);
fk = polymorphic.foreignKey;
discriminator = polymorphic.discriminator;
if (!params.through) {
modelTo.dataSource.defineProperty(modelTo.modelName, discriminator, { type: 'string', index: true });
}
}
var relationDef = modelFrom.relations[relationName] = new RelationDefinition({
name: relationName,
@ -1046,6 +1184,7 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
modelFrom: modelFrom,
keyFrom: pk,
keyTo: fk,
discriminator: discriminator,
modelTo: modelTo,
properties: params.properties,
options: params.options

View File

@ -3,6 +3,7 @@ var should = require('./init.js');
var db, Book, Chapter, Author, Reader;
var Category, Product;
var Picture, PictureLink;
describe('relations', function () {
@ -526,6 +527,481 @@ 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({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({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();
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.hasMany(Picture, { polymorphic: 'imageable' });
Reader.hasMany(Picture, { polymorphic: { // alt syntax
as: 'imageable', foreignKey: 'imageableId',
discriminator: 'imageableType'
} });
Picture.belongsTo('imageable', { polymorphic: true });
db.automigrate(done);
});
it('should create polymorphic relation - author', function (done) {
Author.create({ name: 'Author 1' }, function (err, author) {
author.pictures.create({ name: 'Author Pic' }, 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({ name: 'Reader 1' }, function (err, reader) {
reader.pictures.create({ name: 'Reader Pic' }, 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 items - author', function (done) {
Author.findOne(function (err, author) {
author.pictures(function (err, pics) {
should.not.exist(err);
pics.should.have.length(1);
pics[0].name.should.equal('Author Pic');
done();
});
});
});
it('should find polymorphic items - reader', function (done) {
Reader.findOne(function (err, reader) {
reader.pictures(function (err, pics) {
should.not.exist(err);
pics.should.have.length(1);
pics[0].name.should.equal('Reader Pic');
done();
});
});
});
it('should find the inverse of polymorphic relation - author', function (done) {
Picture.findOne({ where: { name: 'Author Pic' } }, function (err, p) {
should.not.exist(err);
p.imageableType.should.equal('Author');
p.imageable(function(err, imageable) {
should.not.exist(err);
imageable.should.be.instanceof(Author);
imageable.name.should.equal('Author 1');
done();
});
});
});
it('should find the inverse of polymorphic relation - reader', function (done) {
Picture.findOne({ where: { name: 'Reader Pic' } }, function (err, p) {
should.not.exist(err);
p.imageableType.should.equal('Reader');
p.imageable(function(err, imageable) {
should.not.exist(err);
imageable.should.be.instanceof(Reader);
imageable.name.should.equal('Reader 1');
done();
});
});
});
it('should include the inverse of polymorphic relation', function (done) {
Picture.find({ include: 'imageable' }, function (err, pics) {
should.not.exist(err);
pics.should.have.length(2);
pics[0].name.should.equal('Author Pic');
pics[0].imageable().name.should.equal('Author 1');
pics[1].name.should.equal('Reader Pic');
pics[1].imageable().name.should.equal('Reader 1');
done();
});
});
it('should assign a polymorphic relation', function(done) {
Author.create({ name: 'Author 2' }, function(err, author) {
var p = new Picture({ name: 'Sample' });
p.imageable(author); // assign
p.imageableId.should.equal(author.id);
p.imageableType.should.equal('Author');
p.save(done);
});
});
it('should find polymorphic items - author', function (done) {
Author.findOne({ where: { name: 'Author 2' } }, function (err, author) {
author.pictures(function (err, pics) {
should.not.exist(err);
pics.should.have.length(1);
pics[0].name.should.equal('Sample');
done();
});
});
});
it('should find the inverse of polymorphic relation - author', function (done) {
Picture.findOne({ where: { name: 'Sample' } }, function (err, p) {
should.not.exist(err);
p.imageableType.should.equal('Author');
p.imageable(function(err, imageable) {
should.not.exist(err);
imageable.should.be.instanceof(Author);
imageable.name.should.equal('Author 2');
done();
});
});
});
});
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' });
// Optionally, define inverse relations:
Picture.hasMany(Author, { through: PictureLink, polymorphic: 'imageable', invert: true });
Picture.hasMany(Reader, { through: PictureLink, polymorphic: 'imageable', invert: true });
db.automigrate(done);
});
var author, reader, pictures = [];
it('should create polymorphic relation - author', function (done) {
Author.create({ name: 'Author 1' }, function (err, a) {
should.not.exist(err);
author = a;
author.pictures.create({ name: 'Author Pic 1' }, function (err, p) {
should.not.exist(err);
pictures.push(p);
author.pictures.create({ name: 'Author Pic 2' }, function (err, p) {
should.not.exist(err);
pictures.push(p);
done();
});
});
});
});
it('should create polymorphic relation - reader', function (done) {
Reader.create({ name: 'Reader 1' }, function (err, r) {
should.not.exist(err);
reader = r;
reader.pictures.create({ name: 'Reader Pic 1' }, function (err, p) {
should.not.exist(err);
pictures.push(p);
done();
});
});
});
it('should create polymorphic through model', function (done) {
PictureLink.findOne(function(err, link) {
should.not.exist(err);
link.pictureId.should.eql(pictures[0].id); // eql for mongo ObjectId
link.imageableId.should.eql(author.id);
link.imageableType.should.equal('Author');
link.imageable(function(err, imageable) {
imageable.should.be.instanceof(Author);
imageable.id.should.eql(author.id);
done();
});
});
});
it('should get polymorphic relation through model - author', function (done) {
Author.findById(author.id, 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(reader.id, 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();
});
});
});
it('should include polymorphic items', function (done) {
Author.find({ include: 'pictures' }, function(err, authors) {
authors.should.have.length(1);
authors[0].pictures(function(err, pics) {
pics.should.have.length(2);
pics[0].name.should.equal('Author Pic 1');
pics[1].name.should.equal('Author Pic 2');
done();
});
});
});
var anotherPicture;
it('should add to a polymorphic relation - author', function (done) {
Author.findById(author.id, function(err, author) {
Picture.create({name: 'Example' }, function(err, p) {
should.not.exist(err);
pictures.push(p);
anotherPicture = p;
author.pictures.add(p, function(err, link) {
link.should.be.instanceof(PictureLink);
link.pictureId.should.eql(p.id);
link.imageableId.should.eql(author.id);
link.imageableType.should.equal('Author');
done();
});
});
});
});
it('should create polymorphic through model', function (done) {
PictureLink.findOne({ where: { pictureId: anotherPicture.id, imageableType: 'Author' } }, function(err, link) {
should.not.exist(err);
link.pictureId.should.eql(anotherPicture.id);
link.imageableId.should.eql(author.id);
link.imageableType.should.equal('Author');
done();
});
});
var anotherAuthor, anotherReader;
it('should add to a polymorphic relation - author', function (done) {
Author.create({ name: 'Author 2' }, function (err, author) {
should.not.exist(err);
anotherAuthor = author;
author.pictures.add(anotherPicture.id, function (err, p) {
should.not.exist(err);
done();
});
});
});
it('should add to a polymorphic relation - author', function (done) {
Reader.create({name: 'Reader 2' }, function (err, reader) {
should.not.exist(err);
anotherReader = reader;
reader.pictures.add(anotherPicture.id, function (err, p) {
should.not.exist(err);
done();
});
});
});
it('should get the inverse polymorphic relation - author', function (done) {
Picture.findById(anotherPicture.id, function(err, p) {
p.authors(function(err, authors) {
authors.should.have.length(2);
authors[0].name.should.equal('Author 1');
authors[1].name.should.equal('Author 2');
done();
});
});
});
it('should get the inverse polymorphic relation - reader', function (done) {
Picture.findById(anotherPicture.id, function(err, p) {
p.readers(function(err, readers) {
readers.should.have.length(1);
readers[0].name.should.equal('Reader 2');
done();
});
});
});
it('should find polymorphic items - author', function (done) {
Author.findById(author.id, function(err, author) {
author.pictures(function(err, pics) {
pics.should.have.length(3);
pics[0].name.should.equal('Author Pic 1');
pics[1].name.should.equal('Author Pic 2');
pics[2].name.should.equal('Example');
done();
});
});
});
it('should check if polymorphic relation exists - author', function (done) {
Author.findById(author.id, function(err, author) {
author.pictures.exists(anotherPicture.id, function(err, exists) {
exists.should.be.true;
done();
});
});
});
it('should remove from a polymorphic relation - author', function (done) {
Author.findById(author.id, function(err, author) {
author.pictures.remove(anotherPicture.id, function(err) {
should.not.exist(err);
done();
});
});
});
it('should find polymorphic items - author', function (done) {
Author.findById(author.id, function(err, author) {
author.pictures(function(err, pics) {
pics.should.have.length(2);
pics[0].name.should.equal('Author Pic 1');
pics[1].name.should.equal('Author Pic 2');
done();
});
});
});
it('should check if polymorphic relation exists - author', function (done) {
Author.findById(author.id, function(err, author) {
author.pictures.exists(7, function(err, exists) {
exists.should.be.false;
done();
});
});
});
});
describe('belongsTo', function () {
var List, Item, Fear, Mind;
@ -596,10 +1072,12 @@ describe('relations', function () {
});
db.automigrate(done);
});
var personCreated;
it('should create record on scope', function (done) {
var p = new Passport({ name: 'Passport', notes: 'Some notes...' });
p.person.create({ id: 3, name: 'Fred', age: 36 }, function(err, person) {
p.person.create({name: 'Fred', age: 36 }, function(err, person) {
personCreated = person;
p.personId.should.equal(person.id);
p.save(function (err, p) {
person.name.should.equal('Fred');
@ -611,7 +1089,7 @@ describe('relations', function () {
it('should find record on scope', function (done) {
Passport.findOne(function (err, p) {
p.personId.should.equal(3);
p.personId.should.equal(personCreated.id);
p.person(function(err, person) {
person.name.should.equal('Fred');
person.should.not.have.property('age');