Merge branch 'release/2.4.2' into production
This commit is contained in:
commit
b49915f042
|
@ -429,19 +429,39 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
||||||
for (var rn in relations) {
|
for (var rn in relations) {
|
||||||
var r = relations[rn];
|
var r = relations[rn];
|
||||||
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
|
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
|
||||||
var targetModel = isModelClass(r.model) ? r.model : this.getModel(r.model, true);
|
var targetModel, polymorphicName;
|
||||||
|
|
||||||
|
if (r.polymorphic && r.type !== 'belongsTo' && !r.model) {
|
||||||
|
throw new Error('No model specified for polymorphic ' + r.type + ': ' + rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.polymorphic) {
|
||||||
|
polymorphicName = typeof r.model === 'string' ? r.model : rn;
|
||||||
|
if (typeof r.polymorphic === 'string') {
|
||||||
|
polymorphicName = r.polymorphic;
|
||||||
|
} else if (typeof r.polymorphic === 'object' && typeof r.polymorphic.as === 'string') {
|
||||||
|
polymorphicName = r.polymorphic.as;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.model) {
|
||||||
|
targetModel = isModelClass(r.model) ? r.model : this.getModel(r.model, true);
|
||||||
|
}
|
||||||
|
|
||||||
var throughModel = null;
|
var throughModel = null;
|
||||||
if (r.through) {
|
if (r.through) {
|
||||||
throughModel = isModelClass(r.through) ? r.through : this.getModel(r.through, true);
|
throughModel = isModelClass(r.through) ? r.through : this.getModel(r.through, true);
|
||||||
}
|
}
|
||||||
if ((!r.polymorphic && !isModelDataSourceAttached(targetModel)) || (throughModel && !isModelDataSourceAttached(throughModel))) {
|
|
||||||
|
if ((targetModel && !isModelDataSourceAttached(targetModel))
|
||||||
|
|| (throughModel && !isModelDataSourceAttached(throughModel))) {
|
||||||
// Create a listener to defer the relation set up
|
// Create a listener to defer the relation set up
|
||||||
createListener(rn, r, targetModel, throughModel);
|
createListener(rn, r, targetModel, throughModel);
|
||||||
} else {
|
} else {
|
||||||
// The target model is resolved
|
// The target model is resolved
|
||||||
var params = traverse(r).clone();
|
var params = traverse(r).clone();
|
||||||
params.as = rn;
|
params.as = rn;
|
||||||
params.model = targetModel;
|
params.model = polymorphicName || targetModel;
|
||||||
if (throughModel) {
|
if (throughModel) {
|
||||||
params.through = throughModel;
|
params.through = throughModel;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ List.prototype.push = function (obj) {
|
||||||
List.prototype.toObject = function (onlySchema, removeHidden) {
|
List.prototype.toObject = function (onlySchema, removeHidden) {
|
||||||
var items = [];
|
var items = [];
|
||||||
this.forEach(function (item) {
|
this.forEach(function (item) {
|
||||||
if (item.toObject) {
|
if (item && typeof item === 'object' && item.toObject) {
|
||||||
items.push(item.toObject(onlySchema, removeHidden));
|
items.push(item.toObject(onlySchema, removeHidden));
|
||||||
} else {
|
} else {
|
||||||
items.push(item);
|
items.push(item);
|
||||||
|
|
|
@ -439,7 +439,14 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
||||||
if (mixin === true) {
|
if (mixin === true) {
|
||||||
mixin = {};
|
mixin = {};
|
||||||
}
|
}
|
||||||
if (typeof mixin === 'object') {
|
if (Array.isArray(mixin)) {
|
||||||
|
mixin.forEach(function(m) {
|
||||||
|
if (m === true) m = {};
|
||||||
|
if (typeof m === 'object') {
|
||||||
|
modelBuilder.mixins.applyMixin(ModelClass, name, m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (typeof mixin === 'object') {
|
||||||
modelBuilder.mixins.applyMixin(ModelClass, name, mixin);
|
modelBuilder.mixins.applyMixin(ModelClass, name, mixin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,7 +265,7 @@ ModelDefinition.prototype.toJSON = function (forceRebuild) {
|
||||||
this.json = null;
|
this.json = null;
|
||||||
}
|
}
|
||||||
if (this.json) {
|
if (this.json) {
|
||||||
return json;
|
return this.json;
|
||||||
}
|
}
|
||||||
var json = {
|
var json = {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
|
|
@ -115,12 +115,19 @@ function RelationDefinition(definition) {
|
||||||
}
|
}
|
||||||
|
|
||||||
RelationDefinition.prototype.toJSON = function () {
|
RelationDefinition.prototype.toJSON = function () {
|
||||||
|
var polymorphic = typeof this.polymorphic === 'object';
|
||||||
|
|
||||||
|
var modelToName = this.modelTo && this.modelTo.modelName;
|
||||||
|
if (!modelToName && polymorphic && this.type === 'belongsTo') {
|
||||||
|
modelToName = '<polymorphic>';
|
||||||
|
}
|
||||||
|
|
||||||
var json = {
|
var json = {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
modelFrom: this.modelFrom.modelName,
|
modelFrom: this.modelFrom.modelName,
|
||||||
keyFrom: this.keyFrom,
|
keyFrom: this.keyFrom,
|
||||||
modelTo: this.modelTo.modelName,
|
modelTo: modelToName,
|
||||||
keyTo: this.keyTo,
|
keyTo: this.keyTo,
|
||||||
multiple: this.multiple
|
multiple: this.multiple
|
||||||
};
|
};
|
||||||
|
@ -128,6 +135,9 @@ RelationDefinition.prototype.toJSON = function () {
|
||||||
json.modelThrough = this.modelThrough.modelName;
|
json.modelThrough = this.modelThrough.modelName;
|
||||||
json.keyThrough = this.keyThrough;
|
json.keyThrough = this.keyThrough;
|
||||||
}
|
}
|
||||||
|
if (polymorphic) {
|
||||||
|
json.polymorphic = this.polymorphic;
|
||||||
|
}
|
||||||
return json;
|
return json;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -449,14 +459,20 @@ function lookupModel(models, modelName) {
|
||||||
|
|
||||||
function lookupModelTo(modelFrom, modelTo, params, singularize) {
|
function lookupModelTo(modelFrom, modelTo, params, singularize) {
|
||||||
if ('string' === typeof modelTo) {
|
if ('string' === typeof modelTo) {
|
||||||
|
var modelToName;
|
||||||
params.as = params.as || modelTo;
|
params.as = params.as || modelTo;
|
||||||
modelTo = params.model || modelTo;
|
modelTo = params.model || modelTo;
|
||||||
if (typeof modelTo === 'string') {
|
if (typeof modelTo === 'string') {
|
||||||
var modelToName = (singularize ? i8n.singularize(modelTo) : modelTo).toLowerCase();
|
modelToName = (singularize ? i8n.singularize(modelTo) : modelTo).toLowerCase();
|
||||||
|
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
|
||||||
|
}
|
||||||
|
if (typeof modelTo === 'string') {
|
||||||
|
modelToName = (singularize ? i8n.singularize(params.as) : params.as).toLowerCase();
|
||||||
|
console.log(modelToName)
|
||||||
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
|
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
|
||||||
}
|
}
|
||||||
if (typeof modelTo !== 'function') {
|
if (typeof modelTo !== 'function') {
|
||||||
throw new Error('Could not find "' + modelTo + '" relation for ' + modelFrom.modelName);
|
throw new Error('Could not find "' + params.as + '" relation for ' + modelFrom.modelName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return modelTo;
|
return modelTo;
|
||||||
|
@ -510,7 +526,7 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
|
||||||
|
|
||||||
if (params.polymorphic) {
|
if (params.polymorphic) {
|
||||||
polymorphic = polymorphicParams(params.polymorphic);
|
polymorphic = polymorphicParams(params.polymorphic);
|
||||||
polymorphic.invert = !!params.invert;
|
if (params.invert) polymorphic.invert = true;
|
||||||
discriminator = polymorphic.discriminator;
|
discriminator = polymorphic.discriminator;
|
||||||
if (!params.invert) {
|
if (!params.invert) {
|
||||||
fk = polymorphic.foreignKey;
|
fk = polymorphic.foreignKey;
|
||||||
|
@ -692,12 +708,11 @@ HasMany.prototype.findById = function (fkId, cb) {
|
||||||
* @param {Function} cb The callback function
|
* @param {Function} cb The callback function
|
||||||
*/
|
*/
|
||||||
HasMany.prototype.exists = function (fkId, cb) {
|
HasMany.prototype.exists = function (fkId, cb) {
|
||||||
var modelTo = this.definition.modelTo;
|
|
||||||
var fk = this.definition.keyTo;
|
var fk = this.definition.keyTo;
|
||||||
var pk = this.definition.keyFrom;
|
var pk = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
|
|
||||||
modelTo.findById(fkId, function (err, inst) {
|
this.findById(fkId, function (err, inst) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
@ -1172,7 +1187,7 @@ BelongsTo.prototype.related = function (refresh, params) {
|
||||||
self.resetCache(params);
|
self.resetCache(params);
|
||||||
} else if (typeof params === 'function') { // acts as async getter
|
} else if (typeof params === 'function') { // acts as async getter
|
||||||
|
|
||||||
if (discriminator && !modelTo) {
|
if (discriminator) {
|
||||||
var modelToName = modelInstance[discriminator];
|
var modelToName = modelInstance[discriminator];
|
||||||
if (typeof modelToName !== 'string') {
|
if (typeof modelToName !== 'string') {
|
||||||
throw new Error('Polymorphic model not found: `' + discriminator + '` not set');
|
throw new Error('Polymorphic model not found: `' + discriminator + '` not set');
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-datasource-juggler",
|
"name": "loopback-datasource-juggler",
|
||||||
"version": "2.4.1",
|
"version": "2.4.2",
|
||||||
"description": "LoopBack DataSoure Juggler",
|
"description": "LoopBack DataSoure Juggler",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
|
|
|
@ -924,6 +924,50 @@ describe('Load models with relations', function () {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set up polymorphic relations', function (done) {
|
||||||
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
var Author = ds.define('Author', {name: String}, {relations: {
|
||||||
|
pictures: {type: 'hasMany', model: 'Picture', polymorphic: 'imageable'}
|
||||||
|
}});
|
||||||
|
var Picture = ds.define('Picture', {name: String}, {relations: {
|
||||||
|
imageable: {type: 'belongsTo', polymorphic: true}
|
||||||
|
}});
|
||||||
|
|
||||||
|
assert(Author.relations['pictures']);
|
||||||
|
assert.deepEqual(Author.relations['pictures'].toJSON(), {
|
||||||
|
name: 'pictures',
|
||||||
|
type: 'hasMany',
|
||||||
|
modelFrom: 'Author',
|
||||||
|
keyFrom: 'id',
|
||||||
|
modelTo: 'Picture',
|
||||||
|
keyTo: 'imageableId',
|
||||||
|
multiple: true,
|
||||||
|
polymorphic: {
|
||||||
|
as: 'imageable',
|
||||||
|
foreignKey: 'imageableId',
|
||||||
|
discriminator: 'imageableType'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(Picture.relations['imageable']);
|
||||||
|
assert.deepEqual(Picture.relations['imageable'].toJSON(), {
|
||||||
|
name: 'imageable',
|
||||||
|
type: 'belongsTo',
|
||||||
|
modelFrom: 'Picture',
|
||||||
|
keyFrom: 'imageableId',
|
||||||
|
modelTo: '<polymorphic>',
|
||||||
|
keyTo: 'id',
|
||||||
|
multiple: false,
|
||||||
|
polymorphic: {
|
||||||
|
as: 'imageable',
|
||||||
|
foreignKey: 'imageableId',
|
||||||
|
discriminator: 'imageableType'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
it('should set up foreign key with the correct type', function (done) {
|
it('should set up foreign key with the correct type', function (done) {
|
||||||
var ds = new DataSource('memory');
|
var ds = new DataSource('memory');
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,11 @@ describe('Model class', function () {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
mixins.define('Demo', function(Model, options) {
|
mixins.define('Demo', function(Model, options) {
|
||||||
Model.demoMixin = options.ok;
|
Model.demoMixin = options.value;
|
||||||
|
});
|
||||||
|
mixins.define('Multi', function(Model, options) {
|
||||||
|
Model.multiMixin = Model.multiMixin || {};
|
||||||
|
Model.multiMixin[options.key] = options.value;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -73,13 +77,22 @@ describe('Model class', function () {
|
||||||
it('should apply mixins', function(done) {
|
it('should apply mixins', function(done) {
|
||||||
var memory = new DataSource('mem', {connector: Memory}, modelBuilder);
|
var memory = new DataSource('mem', {connector: Memory}, modelBuilder);
|
||||||
var Item = memory.createModel('Item', { name: 'string' }, {
|
var Item = memory.createModel('Item', { name: 'string' }, {
|
||||||
mixins: { TimeStamp: true, Demo: { ok: true } }
|
mixins: {
|
||||||
|
TimeStamp: true, Demo: { value: true },
|
||||||
|
Multi: [
|
||||||
|
{ key: 'foo', value: 'bar' },
|
||||||
|
{ key: 'fox', value: 'baz' }
|
||||||
|
]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Item.mixin('Example', { foo: 'bar' });
|
Item.mixin('Example', { foo: 'bar' });
|
||||||
|
|
||||||
Item.demoMixin.should.be.true;
|
Item.demoMixin.should.be.true;
|
||||||
|
|
||||||
|
Item.multiMixin.foo.should.equal('bar');
|
||||||
|
Item.multiMixin.fox.should.equal('baz');
|
||||||
|
|
||||||
var properties = Item.definition.properties;
|
var properties = Item.definition.properties;
|
||||||
properties.createdAt.should.eql({ type: Date });
|
properties.createdAt.should.eql({ type: Date });
|
||||||
properties.updatedAt.should.eql({ type: Date });
|
properties.updatedAt.should.eql({ type: Date });
|
||||||
|
|
|
@ -37,6 +37,8 @@ describe('ModelDefinition class', function () {
|
||||||
assert.equal(json.properties.joinedAt.type, "Date");
|
assert.equal(json.properties.joinedAt.type, "Date");
|
||||||
assert.equal(json.properties.age.type, "Number");
|
assert.equal(json.properties.age.type, "Number");
|
||||||
|
|
||||||
|
assert.deepEqual(User.toJSON(), json);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -686,6 +686,37 @@ describe('relations', function () {
|
||||||
discriminator: 'imageableType'
|
discriminator: 'imageableType'
|
||||||
} });
|
} });
|
||||||
Picture.belongsTo('imageable', { polymorphic: true });
|
Picture.belongsTo('imageable', { polymorphic: true });
|
||||||
|
|
||||||
|
Author.relations['pictures'].toJSON().should.eql({
|
||||||
|
name: 'pictures',
|
||||||
|
type: 'hasMany',
|
||||||
|
modelFrom: 'Author',
|
||||||
|
keyFrom: 'id',
|
||||||
|
modelTo: 'Picture',
|
||||||
|
keyTo: 'imageableId',
|
||||||
|
multiple: true,
|
||||||
|
polymorphic: {
|
||||||
|
as: 'imageable',
|
||||||
|
foreignKey: 'imageableId',
|
||||||
|
discriminator: 'imageableType'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Picture.relations['imageable'].toJSON().should.eql({
|
||||||
|
name: 'imageable',
|
||||||
|
type: 'belongsTo',
|
||||||
|
modelFrom: 'Picture',
|
||||||
|
keyFrom: 'imageableId',
|
||||||
|
modelTo: '<polymorphic>',
|
||||||
|
keyTo: 'id',
|
||||||
|
multiple: false,
|
||||||
|
polymorphic: {
|
||||||
|
as: 'imageable',
|
||||||
|
foreignKey: 'imageableId',
|
||||||
|
discriminator: 'imageableType'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
db.automigrate(done);
|
db.automigrate(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue