Overall review of polymorphic relations

In #1298, the spec/doc for polymorphic relations was reviewed

**hasX relation**
- `type`: **hasMany**
- `as`: redefines **this** relation's name (optional)
- `model`: **modelTo**
- `polymorphic`:
  - typeOf `polymorphic` === `String`
    - matching **belongsTo** relation name
      - `foreignKey` is generated as `polymorphic + 'Id'`,
      - `discriminator` is generated as `polymorphic + 'Type'`
  - typeOf `polymorphic` === `Object`
    - `as`: **DEPRECATED** should display a warning,
            replaced by `selector`
    - `selector`: should match **belongsTo** relation name if the
                  latter is defined with {polymorphic: true}
      - (required) if both foreignKey and discriminator
        are **NOT** provided
      - (extraneous) if both foreignKey and discriminator
        are provided
    - `foreignKey`:  A property of modelTo, representing the fk to
       modelFrom's id.
      - generated by default as `selector + 'Id'`
    - `discriminator`: A property of modelTo, representing the actual
                       modelFrom to be looked up and defined
                       dynamically
      - generated by default as `selector + 'Type'`

---

**belongsTo relation**
- `type`: **belongsTo**
- `as`: redefines **this** relation's name (optional)
- `model`: **NOT EXPECTED**: should throw an error at
               relation validation
- `polymorphic`:
  - typeOf `polymorphic` === `Boolean`
      - `foreignKey` is generated as `relationName + 'Id'`,
      - `discriminator` is generated as `relationName + 'Type'`
  - typeOf `polymorphic` === `Object`
    - `as`: **DEPRECATED**: should display a warning,
            replaced by `selector`
    - `selector`:
      - (required) if both foreignKey and discriminator
        are **NOT** provided
      - (extraneous) if both foreignKey and discriminator
        are provided
    - `foreignKey`: A property of modelTo, representing the fk to
                    modelFrom's id.
      - generated by default as `selector + 'Id'`
    - `discriminator`: A property of modelTo, representing the actual
                       modelFrom to be looked up and defined
                       dynamically
      - generated by default as `selector + 'Type'`
This commit is contained in:
ebarault 2017-04-01 11:13:22 +02:00
parent d375d61519
commit cfd3cdf535
4 changed files with 1174 additions and 653 deletions

View File

@ -26,6 +26,7 @@ var async = require('async');
var traverse = require('traverse');
var g = require('strong-globalize')();
var juggler = require('..');
var deprecated = require('depd')('loopback-datasource-juggler');
if (process.env.DEBUG === 'loopback') {
// For back-compatibility
@ -471,34 +472,34 @@ DataSource.prototype.defineScopes = function(modelClass, scopes) {
DataSource.prototype.defineRelations = function(modelClass, relations) {
var self = this;
// Create a function for the closure in the loop
var createListener = function(name, relation, targetModel, throughModel) {
// Wait for target/through models to be attached before setting up the relation
var deferRelationSetup = function(relationName, relation, targetModel, throughModel) {
if (!isModelDataSourceAttached(targetModel)) {
targetModel.once('dataAccessConfigured', function(model) {
targetModel.once('dataAccessConfigured', function(targetModel) {
// Check if the through model doesn't exist or resolved
if (!throughModel || isModelDataSourceAttached(throughModel)) {
// The target model is resolved
var params = traverse(relation).clone();
params.as = name;
params.model = model;
params.as = relationName;
params.model = targetModel;
if (throughModel) {
params.through = throughModel;
}
modelClass[relation.type].call(modelClass, name, params);
modelClass[relation.type].call(modelClass, relationName, params);
}
});
}
if (throughModel && !isModelDataSourceAttached(throughModel)) {
// Set up a listener to the through model
throughModel.once('dataAccessConfigured', function(model) {
throughModel.once('dataAccessConfigured', function(throughModel) {
if (isModelDataSourceAttached(targetModel)) {
// The target model is resolved
var params = traverse(relation).clone();
params.as = name;
params.as = relationName;
params.model = targetModel;
params.through = model;
modelClass[relation.type].call(modelClass, name, params);
params.through = throughModel;
modelClass[relation.type].call(modelClass, relationName, params);
}
});
}
@ -506,25 +507,11 @@ DataSource.prototype.defineRelations = function(modelClass, relations) {
// Set up the relations
if (relations) {
Object.keys(relations).forEach(function(rn) {
var r = relations[rn];
assert(DataSource.relationTypes.indexOf(r.type) !== -1, 'Invalid relation type: ' + r.type);
assert(isValidRelationName(rn), 'Invalid relation name: ' + rn);
Object.keys(relations).forEach(function(relationName) {
var targetModel;
var r = relations[relationName];
var targetModel, polymorphicName;
if (r.polymorphic && r.type !== 'belongsTo' && !r.model) {
throw new Error(g.f('No model specified for {{polymorphic}} %s: %s', 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;
}
}
validateRelation(relationName, r);
if (r.model) {
targetModel = isModelClass(r.model) ? r.model : self.getModel(r.model, true);
@ -538,24 +525,88 @@ DataSource.prototype.defineRelations = function(modelClass, relations) {
if ((targetModel && !isModelDataSourceAttached(targetModel)) ||
(throughModel && !isModelDataSourceAttached(throughModel))) {
// Create a listener to defer the relation set up
createListener(rn, r, targetModel, throughModel);
deferRelationSetup(relationName, r, targetModel, throughModel);
} else {
// The target model is resolved
var params = traverse(r).clone();
params.as = rn;
params.model = polymorphicName || targetModel;
params.as = relationName;
params.model = targetModel;
if (throughModel) {
params.through = throughModel;
}
modelClass[r.type].call(modelClass, rn, params);
modelClass[r.type].call(modelClass, relationName, params);
}
});
}
};
function validateRelation(relationName, relation) {
var rn = relationName;
var r = relation;
var msg, code;
assert(DataSource.relationTypes.indexOf(r.type) !== -1, 'Invalid relation type: ' + r.type);
assert(isValidRelationName(rn), 'Invalid relation name: ' + rn);
// VALIDATION ERRORS
// non polymorphic belongsTo relations should have `model` defined
if (!r.polymorphic && r.type === 'belongsTo' && !r.model) {
msg = g.f('%s relation: %s requires param `model`', r.type, rn);
code = 'BELONGS_TO_MISSING_MODEL';
}
// polymorphic belongsTo relations should not have `model` defined
if (r.polymorphic && r.type === 'belongsTo' && r.model) {
msg = g.f('{{polymorphic}} %s relation: %s does not expect param `model`', r.type, rn);
code = 'POLYMORPHIC_BELONGS_TO_MODEL';
}
// polymorphic relations other than belongsTo should have `model` defined
if (r.polymorphic && r.type !== 'belongsTo' && !r.model) {
msg = g.f('{{polymorphic}} %s relation: %s requires param `model`', r.type, rn);
code = 'POLYMORPHIC_NOT_BELONGS_TO_MISSING_MODEL';
}
// polymorphic relations should provide both discriminator and foreignKey or none
if (r.polymorphic && r.polymorphic.foreignKey && !r.polymorphic.discriminator) {
msg = g.f('{{polymorphic}} %s relation: %s requires param `polymorphic.discriminator` ' +
'when param `polymorphic.foreignKey` is provided', r.type, rn);
code = 'POLYMORPHIC_MISSING_DISCRIMINATOR';
}
// polymorphic relations should provide both discriminator and foreignKey or none
if (r.polymorphic && r.polymorphic.discriminator && !r.polymorphic.foreignKey) {
msg = g.f('{{polymorphic}} %s relation: %s requires param `polymorphic.foreignKey` ' +
'when param `polymorphic.discriminator` is provided', r.type, rn);
code = 'POLYMORPHIC_MISSING_FOREIGN_KEY';
}
// polymorphic relations should not provide polymorphic.as when using custom foreignKey/discriminator
if (r.polymorphic && r.polymorphic.as && r.polymorphic.foreignKey) {
msg = g.f('{{polymorphic}} %s relation: %s does not expect param `polymorphic.as` ' +
'when defing custom `foreignKey`/`discriminator` ', r.type, rn);
code = 'POLYMORPHIC_EXTRANEOUS_AS';
}
// polymorphic relations should not provide polymorphic.as when using custom foreignKey/discriminator
if (r.polymorphic && r.polymorphic.selector && r.polymorphic.foreignKey) {
msg = g.f('{{polymorphic}} %s relation: %s does not expect param `polymorphic.selector` ' +
'when defing custom `foreignKey`/`discriminator` ', r.type, rn);
code = 'POLYMORPHIC_EXTRANEOUS_SELECTOR';
}
if (msg) {
var error = new Error(msg);
error.details = {code: code, rType: r.type, rName: rn};
throw error;
}
// DEPRECATION WARNINGS
if (r.polymorphic && r.polymorphic.as) {
deprecated(g.f('WARNING: {{polymorphic}} %s relation: %s uses keyword `polymorphic.as` which will ' +
'be DEPRECATED in LoopBack.next, refer to this doc for replacement solutions ' +
'(https://loopback.io/doc/en/lb3/Polymorphic-relations.html#deprecated-polymorphic-as)',
r.type, rn), r.type);
}
}
function isValidRelationName(relationName) {
var invalidRelationNames = ['trigger'];
return invalidRelationNames.indexOf(relationName) === -1;
}

View File

@ -532,37 +532,104 @@ function lookupModel(models, modelName) {
}
}
function lookupModelTo(modelFrom, modelTo, params, singularize) {
if ('string' === typeof modelTo) {
/*
* @param {Object} modelFrom Instance of the 'from' model
* @param {Object|String} modelToRef Reference to Model object to which you are
* creating the relation: model instance, model name, or name of relation to model.
* @param {Object} params The relation params
* @param {Boolean} singularize Whether the modelToRef should be singularized when
* looking-up modelTo
* @return {Object} modelTo Instance of the 'to' model
*/
function lookupModelTo(modelFrom, modelToRef, params, singularize) {
var modelTo;
if (typeof modelToRef !== 'string') {
// modelToRef might already be an instance of model
modelTo = modelToRef;
} else {
// lookup modelTo based on relation params and modelToRef
var modelToName;
params.as = params.as || modelTo;
modelTo = params.model || modelTo;
modelTo = params.model || modelToRef; // modelToRef might be modelTo name
if (typeof modelTo === 'string') {
modelToName = (singularize ? i8n.singularize(modelTo) : modelTo).toLowerCase();
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
// lookup modelTo by name
modelToName = modelTo;
modelToName = (singularize ? i8n.singularize(modelToName) : modelToName).toLowerCase();
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
}
if (typeof modelTo === 'string') {
modelToName = (singularize ? i8n.singularize(params.as) : params.as).toLowerCase();
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo;
}
if (typeof modelTo !== 'function') {
throw new Error(g.f('Could not find "%s" relation for %s', params.as, modelFrom.modelName));
if (!modelTo) {
// lookup by modelTo name was not successful. Now looking-up by relationTo name
var relationToName = params.as || modelToRef; // modelToRef might be relationTo name
modelToName = (singularize ? i8n.singularize(relationToName) : relationToName).toLowerCase();
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
}
}
if (typeof modelTo !== 'function') {
throw new Error(g.f('Could not find relation %s for model %s', params.as, modelFrom.modelName));
}
return modelTo;
}
/*!
* Normalize polymorphic parameters
* @param {Object|String} params Name of the polymorphic relation or params
/*
* Normalize relation's parameter `as`
* @param {Object} params The relation params
* @param {String} relationName The relation name
* @returns {Object} The normalized parameters
* NOTE: normalizeRelationAs() mutates the params object
*/
function normalizeRelationAs(params, relationName) {
if (typeof relationName === 'string') {
params.as = params.as || relationName;
}
return params;
}
/*
* Normalize relation's polymorphic parameters
* @param {Object|String|Boolean} polymorphic Param `polymorphic` of the relation.
* @param {String} relationName The name of the relation we are currently setting up.
* @returns {Object} The normalized parameters
*/
function polymorphicParams(params, as) {
if (typeof params === 'string') params = {as: params};
if (typeof params.as !== 'string') params.as = 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;
function normalizePolymorphic(polymorphic, relationName) {
assert(polymorphic, 'polymorphic param can\'t be false, null or undefined');
assert(!Array.isArray(polymorphic, 'unexpected type for polymorphic param: \'Array\''));
var selector;
if (typeof polymorphic === 'string') {
// relation type is different from belongsTo (hasMany, hasManyThrough, hasAndBelongsToMany, ...)
// polymorphic is the name of the matching belongsTo relation from modelTo to modelFrom
selector = polymorphic;
}
if (polymorphic === true) {
// relation type is belongsTo: the relation name is used as the polymorphic selector
selector = relationName;
}
// NOTE: use of `polymorphic.as` keyword will be deprecated in LoopBack.next
// to avoid confusion with keyword `as` used at the root of the relation definition object
// It is replaced with the `polymorphic.selector` keyword
if (typeof polymorphic == 'object') {
selector = polymorphic.selector || polymorphic.as;
}
// relationName is eventually used as selector if provided and selector not already defined
// it ultimately defaults to 'reference'
selector = selector || relationName || 'reference';
// make sure polymorphic is an object
if (typeof polymorphic !== 'object') {
polymorphic = {};
}
polymorphic.selector = selector;
polymorphic.foreignKey = polymorphic.foreignKey || i8n.camelize(selector + '_id', true); // defaults to {{selector}}Id
polymorphic.discriminator = polymorphic.discriminator || i8n.camelize(selector + '_type', true); // defaults to {{selectorName}}Type
return polymorphic;
}
/**
@ -581,16 +648,18 @@ function polymorphicParams(params, as) {
* Book.hasMany('chapters', {model: Chapter});
* ```
* @param {Model} modelFrom Source model class
* @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship.
* @param {Object|String} modelToRef Reference to Model object to which you are
* creating the relation: model instance, model name, or name of relation to model.
* @options {Object} params Configuration parameters; see below.
* @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
* @property {String} foreignKey Property name of foreign key field.
* @property {Object} model Model object
*/
RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
RelationDefinition.hasMany = function hasMany(modelFrom, modelToRef, params) {
var thisClassName = modelFrom.modelName;
params = params || {};
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
normalizeRelationAs(params, modelToRef);
var modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);
@ -600,7 +669,7 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
var discriminator, polymorphic;
if (params.polymorphic) {
polymorphic = polymorphicParams(params.polymorphic);
polymorphic = normalizePolymorphic(params.polymorphic, relationName);
if (params.invert) {
polymorphic.invert = true;
keyThrough = polymorphic.foreignKey;
@ -711,7 +780,7 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
}
if (definition.polymorphic && definition.polymorphic.invert) {
filter.collect = definition.polymorphic.as;
filter.collect = definition.polymorphic.selector;
filter.include = filter.collect;
} else {
filter.collect = throughRelationName || i8n.camelize(modelTo.modelName, true);
@ -1206,36 +1275,26 @@ HasManyThrough.prototype.remove = function(acInst, options, cb) {
* This optional parameter default value is false, so the related object will
* be loaded from cache if available.
*
* @param {Class|String} modelTo Model object (or String name of model) to
* which you are creating the relationship.
* @param {Object|String} modelToRef Reference to Model object to which you are
* creating the relation: model instance, model name, or name of relation to model.
* @options {Object} params Configuration parameters; see below.
* @property {String} as Name of the property in the referring model that
* corresponds to the foreign key field in the related model.
* @property {String} foreignKey Name of foreign key property.
*
*/
RelationDefinition.belongsTo = function(modelFrom, modelTo, params) {
var discriminator, polymorphic;
RelationDefinition.belongsTo = function(modelFrom, modelToRef, params) {
var modelTo, discriminator, polymorphic;
params = params || {};
if ('string' === typeof modelTo && !params.polymorphic) {
modelTo = lookupModelTo(modelFrom, modelTo, params);
}
var pkName, relationName, fk;
if (params.polymorphic) {
relationName = params.as || (typeof modelTo === 'string' ? modelTo : null); // initially
relationName = params.as || (typeof modelToRef === 'string' ? modelToRef : null);
polymorphic = normalizePolymorphic(params.polymorphic, relationName);
if (params.polymorphic === true) {
// modelTo arg will be the name of the polymorphic relation (string)
polymorphic = polymorphicParams(modelTo, relationName);
} else {
polymorphic = polymorphicParams(params.polymorphic, relationName);
}
modelTo = null; // will lookup dynamically
modelTo = null; // will be looked-up dynamically
pkName = params.primaryKey || params.idName || 'id';
relationName = params.as || polymorphic.as; // finally
fk = polymorphic.foreignKey;
discriminator = polymorphic.discriminator;
@ -1247,6 +1306,9 @@ RelationDefinition.belongsTo = function(modelFrom, modelTo, params) {
modelFrom.dataSource.defineProperty(modelFrom.modelName, discriminator, {type: 'string', index: true});
} else {
// relation is not polymorphic
normalizeRelationAs(params, modelToRef);
modelTo = lookupModelTo(modelFrom, modelToRef, params);
pkName = params.primaryKey || modelTo.dataSource.idName(modelTo.modelName) || 'id';
relationName = params.as || i8n.camelize(modelTo.modelName, true);
fk = params.foreignKey || relationName + 'Id';
@ -1538,17 +1600,18 @@ BelongsTo.prototype.getAsync = function(options, cb) {
* User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'});
* ```
*
* @param {String|Object} modelTo Model object (or String name of model) to
* which you are creating the relationship.
* @param {Object|String} modelToRef Reference to Model object to which you are
* creating the relation: model instance, model name, or name of relation to model.
* @options {Object} params Configuration parameters; see below.
* @property {String} as Name of the property in the referring model that
* corresponds to the foreign key field in the related model.
* @property {String} foreignKey Property name of foreign key field.
* @property {Object} model Model object
*/
RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, modelTo, params) {
RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, modelToRef, params) {
params = params || {};
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
normalizeRelationAs(params, modelToRef);
var modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
var models = modelFrom.dataSource.modelBuilder.models;
@ -1573,12 +1636,13 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom,
options.options = params.options;
if (params.polymorphic) {
var polymorphic = polymorphicParams(params.polymorphic);
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
var polymorphic = normalizePolymorphic(params.polymorphic, relationName);
options.polymorphic = polymorphic; // pass through
var accessor = params.through.prototype[polymorphic.as];
var accessor = params.through.prototype[polymorphic.selector];
if (typeof accessor !== 'function') { // declare once
// use the name of the polymorphic rel, not modelTo
params.through.belongsTo(polymorphic.as, {polymorphic: true});
// use the name of the polymorphic selector, not modelTo
params.through.belongsTo(polymorphic.selector, {polymorphic: true});
}
} else {
params.through.belongsTo(modelFrom);
@ -1596,17 +1660,18 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom,
* has only one account.
*
* @param {Function} modelFrom The declaring model class
* @param {String|Function} modelTo Model object (or String name of model) to
* which you are creating the relationship.
* @param {Object|String} modelToRef Reference to Model object to which you are
* creating the relation: model instance, model name, or name of relation to model.
* @options {Object} params Configuration parameters; see below.
* @property {String} as Name of the property in the referring model that
* corresponds to the foreign key field in the related model.
* @property {String} foreignKey Property name of foreign key field.
* @property {Object} model Model object
*/
RelationDefinition.hasOne = function(modelFrom, modelTo, params) {
RelationDefinition.hasOne = function(modelFrom, modelToRef, params) {
params = params || {};
modelTo = lookupModelTo(modelFrom, modelTo, params);
normalizeRelationAs(params, modelToRef);
var modelTo = lookupModelTo(modelFrom, modelToRef, params);
var pk = params.primaryKey || modelFrom.dataSource.idName(modelFrom.modelName) || 'id';
var relationName = params.as || i8n.camelize(modelTo.modelName, true);
@ -1615,7 +1680,7 @@ RelationDefinition.hasOne = function(modelFrom, modelTo, params) {
var discriminator, polymorphic;
if (params.polymorphic) {
polymorphic = polymorphicParams(params.polymorphic);
polymorphic = normalizePolymorphic(params.polymorphic, relationName);
fk = polymorphic.foreignKey;
discriminator = polymorphic.discriminator;
if (!params.through) {
@ -1943,9 +2008,10 @@ HasOne.prototype.getAsync = function(options, cb) {
return cb.promise;
};
RelationDefinition.embedsOne = function(modelFrom, modelTo, params) {
RelationDefinition.embedsOne = function(modelFrom, modelToRef, params) {
params = params || {};
modelTo = lookupModelTo(modelFrom, modelTo, params);
normalizeRelationAs(params, modelToRef);
var modelTo = lookupModelTo(modelFrom, modelToRef, params);
var thisClassName = modelFrom.modelName;
var relationName = params.as || (i8n.camelize(modelTo.modelName, true) + 'Item');
@ -2339,9 +2405,10 @@ EmbedsOne.prototype.destroy = function(options, cb) {
return cb.promise;
};
RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params) {
RelationDefinition.embedsMany = function embedsMany(modelFrom, modelToRef, params) {
params = params || {};
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
normalizeRelationAs(params, modelToRef);
var modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
var thisClassName = modelFrom.modelName;
var relationName = params.as || (i8n.camelize(modelTo.modelName, true) + 'List');
@ -3014,9 +3081,10 @@ EmbedsMany.prototype.remove = function(acInst, options, cb) {
return cb.promise;
};
RelationDefinition.referencesMany = function referencesMany(modelFrom, modelTo, params) {
RelationDefinition.referencesMany = function referencesMany(modelFrom, modelToRef, params) {
params = params || {};
modelTo = lookupModelTo(modelFrom, modelTo, params, true);
normalizeRelationAs(params, modelToRef);
var modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
var thisClassName = modelFrom.modelName;
var relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);

View File

@ -1084,32 +1084,16 @@ describe('Model define with relations configuration', function() {
done();
});
it('sets up polymorphic relations', function(done) {
it('sets up belongsTo polymorphic relation with `{polymorphic: true}`', function(done) {
var ds = new DataSource('memory');
var Author = ds.define('Author', {name: String}, {relations: {
var Product = ds.define('Product', {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',
@ -1120,7 +1104,35 @@ describe('Model define with relations configuration', function() {
keyTo: 'id',
multiple: false,
polymorphic: {
as: 'imageable',
selector: 'imageable',
foreignKey: 'imageableId',
discriminator: 'imageableType',
},
});
done();
});
it('sets up hasMany polymorphic relation with `{polymorphic: belongsToRelationName}`', function(done) {
var ds = new DataSource('memory');
var Picture = ds.define('Picture', {name: String}, {relations: {
imageable: {type: 'belongsTo', polymorphic: true},
}});
var Product = ds.define('Product', {name: String}, {relations: {
pictures: {type: 'hasMany', model: 'Picture', polymorphic: 'imageable'},
}});
assert(Product.relations['pictures']);
assert.deepEqual(Product.relations['pictures'].toJSON(), {
name: 'pictures',
type: 'hasMany',
modelFrom: 'Product',
keyFrom: 'id',
modelTo: 'Picture',
keyTo: 'imageableId',
multiple: true,
polymorphic: {
selector: 'imageable',
foreignKey: 'imageableId',
discriminator: 'imageableType',
},

File diff suppressed because it is too large Load Diff