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:
parent
d375d61519
commit
cfd3cdf535
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue