mirror of
https://github.com/strapi/strapi.git
synced 2025-08-14 03:34:53 +00:00
Merge pull request #649 from strapi/relations/polymorphic
Relations/polymorphic
This commit is contained in:
commit
c98338942b
@ -80,6 +80,7 @@ module.exports = function (strapi) {
|
|||||||
// Initialize lifecycle callbacks.
|
// Initialize lifecycle callbacks.
|
||||||
const preLifecycle = {
|
const preLifecycle = {
|
||||||
validate: 'beforeCreate',
|
validate: 'beforeCreate',
|
||||||
|
findOneAndUpdate: 'beforeUpdate',
|
||||||
findOneAndRemove: 'beforeDestroy',
|
findOneAndRemove: 'beforeDestroy',
|
||||||
remove: 'beforeDestroy',
|
remove: 'beforeDestroy',
|
||||||
update: 'beforeUpdate',
|
update: 'beforeUpdate',
|
||||||
@ -88,6 +89,37 @@ module.exports = function (strapi) {
|
|||||||
save: 'beforeSave'
|
save: 'beforeSave'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Override populate path for polymorphic association.
|
||||||
|
|
||||||
|
It allows us to make Upload.find().populate('related')
|
||||||
|
instead of Upload.find().populate('related.item')
|
||||||
|
*/
|
||||||
|
const morphAssociations = definition.associations.filter(association => association.nature.toLowerCase().indexOf('morph') !== -1);
|
||||||
|
|
||||||
|
if (morphAssociations.length > 0) {
|
||||||
|
morphAssociations.forEach(association => {
|
||||||
|
Object.keys(preLifecycle)
|
||||||
|
.filter(key => key.indexOf('find') !== -1)
|
||||||
|
.forEach(key => {
|
||||||
|
collection.schema.pre(key, function (next) {
|
||||||
|
if (this._mongooseOptions.populate && this._mongooseOptions.populate[association.alias]) {
|
||||||
|
if (association.nature === 'oneToMorph' || association.nature === 'manyToMorph') {
|
||||||
|
this._mongooseOptions.populate[association.alias].match = {
|
||||||
|
[`${association.via}.${association.where}`]: association.alias,
|
||||||
|
[`${association.via}.kind`]: definition.globalId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._mongooseOptions.populate[association.alias].path = `${association.alias}.${association.key}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_.forEach(preLifecycle, (fn, key) => {
|
_.forEach(preLifecycle, (fn, key) => {
|
||||||
if (_.isFunction(target[model.toLowerCase()][fn])) {
|
if (_.isFunction(target[model.toLowerCase()][fn])) {
|
||||||
collection.schema.pre(key, function (next) {
|
collection.schema.pre(key, function (next) {
|
||||||
@ -126,18 +158,21 @@ module.exports = function (strapi) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
collection.schema.set('toObject', {
|
collection.schema.options.toObject = collection.schema.options.toJSON = {
|
||||||
virtuals: true
|
virtuals: true,
|
||||||
});
|
transform: function (doc, returned, opts) {
|
||||||
|
morphAssociations.forEach(association => {
|
||||||
collection.schema.set('toJSON', {
|
if (Array.isArray(returned[association.alias]) && returned[association.alias].length > 0) {
|
||||||
virtuals: true
|
returned[association.alias] = returned[association.alias].map(o => o[association.key]);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
global[definition.globalName] = instance.model(definition.globalName, collection.schema, definition.collectionName);
|
global[definition.globalName] = instance.model(definition.globalId, collection.schema, definition.collectionName);
|
||||||
} else {
|
} else {
|
||||||
instance.model(definition.globalName, collection.schema, definition.collectionName);
|
instance.model(definition.globalId, collection.schema, definition.collectionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose ORM functions through the `target` object.
|
// Expose ORM functions through the `target` object.
|
||||||
@ -196,9 +231,11 @@ module.exports = function (strapi) {
|
|||||||
// all attributes for relationships-- see below.
|
// all attributes for relationships-- see below.
|
||||||
const done = _.after(_.size(definition.attributes), () => {
|
const done = _.after(_.size(definition.attributes), () => {
|
||||||
// Generate schema without virtual populate
|
// Generate schema without virtual populate
|
||||||
_.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', new instance.Schema(_.omitBy(definition.loadedModel, model => {
|
const schema = new instance.Schema(_.omitBy(definition.loadedModel, model => {
|
||||||
return model.type === 'virtual';
|
return model.type === 'virtual';
|
||||||
})));
|
}));
|
||||||
|
|
||||||
|
_.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', schema);
|
||||||
|
|
||||||
loadedAttributes();
|
loadedAttributes();
|
||||||
});
|
});
|
||||||
@ -251,7 +288,7 @@ module.exports = function (strapi) {
|
|||||||
const FK = _.find(definition.associations, {alias: name});
|
const FK = _.find(definition.associations, {alias: name});
|
||||||
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId;
|
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId;
|
||||||
|
|
||||||
if (FK && FK.nature !== 'oneToOne' && FK.nature !== 'manyToOne' && FK.nature !== 'oneWay') {
|
if (FK && FK.nature !== 'oneToOne' && FK.nature !== 'manyToOne' && FK.nature !== 'oneWay' && FK.nature !== 'oneToMorph') {
|
||||||
definition.loadedModel[name] = {
|
definition.loadedModel[name] = {
|
||||||
type: 'virtual',
|
type: 'virtual',
|
||||||
ref,
|
ref,
|
||||||
@ -259,6 +296,20 @@ module.exports = function (strapi) {
|
|||||||
justOne: true
|
justOne: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Set this info to be able to see if this field is a real database's field.
|
||||||
|
details.isVirtual = true;
|
||||||
|
} else if (FK.nature === 'oneToMorph') {
|
||||||
|
const key = details.plugin ?
|
||||||
|
strapi.plugins[details.plugin].models[details.model].attributes[details.via].key:
|
||||||
|
strapi.models[details.model].attributes[details.via].key;
|
||||||
|
|
||||||
|
definition.loadedModel[name] = {
|
||||||
|
type: 'virtual',
|
||||||
|
ref,
|
||||||
|
via: `${FK.via}.${key}`,
|
||||||
|
justOne: true
|
||||||
|
};
|
||||||
|
|
||||||
// Set this info to be able to see if this field is a real database's field.
|
// Set this info to be able to see if this field is a real database's field.
|
||||||
details.isVirtual = true;
|
details.isVirtual = true;
|
||||||
} else {
|
} else {
|
||||||
@ -275,13 +326,26 @@ module.exports = function (strapi) {
|
|||||||
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId;
|
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId;
|
||||||
|
|
||||||
// One-side of the relationship has to be a virtual field to be bidirectional.
|
// One-side of the relationship has to be a virtual field to be bidirectional.
|
||||||
if ((FK && _.isUndefined(FK.via)) || details.dominant !== true) {
|
if ((FK && _.isUndefined(FK.via)) || details.dominant !== true && FK.nature !== 'manyToMorph') {
|
||||||
definition.loadedModel[name] = {
|
definition.loadedModel[name] = {
|
||||||
type: 'virtual',
|
type: 'virtual',
|
||||||
ref,
|
ref,
|
||||||
via: FK.via
|
via: FK.via
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Set this info to be able to see if this field is a real database's field.
|
||||||
|
details.isVirtual = true;
|
||||||
|
} else if (FK.nature === 'manyToMorph') {
|
||||||
|
const key = details.plugin ?
|
||||||
|
strapi.plugins[details.plugin].models[details.collection].attributes[details.via].key:
|
||||||
|
strapi.models[details.collection].attributes[details.via].key;
|
||||||
|
|
||||||
|
definition.loadedModel[name] = {
|
||||||
|
type: 'virtual',
|
||||||
|
ref,
|
||||||
|
via: `${FK.via}.${key}`
|
||||||
|
};
|
||||||
|
|
||||||
// Set this info to be able to see if this field is a real database's field.
|
// Set this info to be able to see if this field is a real database's field.
|
||||||
details.isVirtual = true;
|
details.isVirtual = true;
|
||||||
} else {
|
} else {
|
||||||
@ -292,6 +356,28 @@ module.exports = function (strapi) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'belongsToMorph': {
|
||||||
|
definition.loadedModel[name] = {
|
||||||
|
kind: String,
|
||||||
|
[details.where]: String,
|
||||||
|
[details.key]: {
|
||||||
|
type: instance.Schema.Types.ObjectId,
|
||||||
|
refPath: `${name}.kind`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'belongsToManyMorph': {
|
||||||
|
definition.loadedModel[name] = [{
|
||||||
|
kind: String,
|
||||||
|
[details.where]: String,
|
||||||
|
[details.key]: {
|
||||||
|
type: instance.Schema.Types.ObjectId,
|
||||||
|
refPath: `${name}.kind`
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"main": "./lib",
|
"main": "./lib",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"mongoose": "^5.0.0-rc1",
|
"mongoose": "^5.0.4",
|
||||||
"mongoose-float": "^1.0.2",
|
"mongoose-float": "^1.0.2",
|
||||||
"pluralize": "^6.0.0",
|
"pluralize": "^6.0.0",
|
||||||
"strapi-utils": "3.0.0-alpha.10.1"
|
"strapi-utils": "3.0.0-alpha.10.1"
|
||||||
|
@ -94,6 +94,8 @@ module.exports = {
|
|||||||
types.other = 'collectionD';
|
types.other = 'collectionD';
|
||||||
} else if (relatedAttribute.hasOwnProperty('model')) {
|
} else if (relatedAttribute.hasOwnProperty('model')) {
|
||||||
types.other = 'model';
|
types.other = 'model';
|
||||||
|
} else if (relatedAttribute.hasOwnProperty('key')) {
|
||||||
|
types.other = 'morphTo';
|
||||||
}
|
}
|
||||||
} else if (association.hasOwnProperty('via') && association.hasOwnProperty('model')) {
|
} else if (association.hasOwnProperty('via') && association.hasOwnProperty('model')) {
|
||||||
types.current = 'modelD';
|
types.current = 'modelD';
|
||||||
@ -109,6 +111,11 @@ module.exports = {
|
|||||||
} else if (attribute.hasOwnProperty('model')) {
|
} else if (attribute.hasOwnProperty('model')) {
|
||||||
types.other = 'model';
|
types.other = 'model';
|
||||||
|
|
||||||
|
// Break loop
|
||||||
|
return false;
|
||||||
|
} else if (attribute.hasOwnProperty('key')) {
|
||||||
|
types.other = 'morphTo';
|
||||||
|
|
||||||
// Break loop
|
// Break loop
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -150,6 +157,37 @@ module.exports = {
|
|||||||
} else if (attribute.hasOwnProperty('model')) {
|
} else if (attribute.hasOwnProperty('model')) {
|
||||||
types.other = 'modelD';
|
types.other = 'modelD';
|
||||||
|
|
||||||
|
// Break loop
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (association.hasOwnProperty('key')) {
|
||||||
|
types.current = 'morphTo';
|
||||||
|
|
||||||
|
const flattenedPluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
|
||||||
|
Object.keys(strapi.plugins[current].models).forEach((model) => {
|
||||||
|
acc[`${current}_${model}`] = strapi.plugins[current].models[model];
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const allModels = _.merge({}, strapi.models, flattenedPluginsModels);
|
||||||
|
|
||||||
|
// We have to find if they are a model linked to this key
|
||||||
|
_.forIn(allModels, model => {
|
||||||
|
_.forIn(model.attributes, attribute => {
|
||||||
|
if (attribute.hasOwnProperty('via') && attribute.via === key) {
|
||||||
|
if (attribute.hasOwnProperty('collection')) {
|
||||||
|
types.other = 'collection';
|
||||||
|
|
||||||
|
// Break loop
|
||||||
|
return false;
|
||||||
|
} else if (attribute.hasOwnProperty('model')) {
|
||||||
|
types.other = 'model';
|
||||||
|
|
||||||
// Break loop
|
// Break loop
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -158,7 +196,27 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (types.current === 'modelD' && types.other === 'model') {
|
if (types.current === 'collection' && types.other === 'morphTo') {
|
||||||
|
return {
|
||||||
|
nature: 'manyToMorph',
|
||||||
|
verbose: 'belongsToMany'
|
||||||
|
};
|
||||||
|
} else if (types.current === 'modelD' && types.other === 'morphTo') {
|
||||||
|
return {
|
||||||
|
nature: 'oneToMorph',
|
||||||
|
verbose: 'belongsTo'
|
||||||
|
};
|
||||||
|
} else if (types.current === 'morphTo' && types.other === 'collection') {
|
||||||
|
return {
|
||||||
|
nature: 'morphToMany',
|
||||||
|
verbose: 'belongsToMorph'
|
||||||
|
};
|
||||||
|
} else if (types.current === 'morphTo' && types.other === 'model') {
|
||||||
|
return {
|
||||||
|
nature: 'morphToOne',
|
||||||
|
verbose: 'belongsToManyMorph'
|
||||||
|
};
|
||||||
|
} else if (types.current === 'modelD' && types.other === 'model') {
|
||||||
return {
|
return {
|
||||||
nature: 'oneToOne',
|
nature: 'oneToOne',
|
||||||
verbose: 'belongsTo'
|
verbose: 'belongsTo'
|
||||||
@ -232,7 +290,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exclude non-relational attribute
|
// Exclude non-relational attribute
|
||||||
if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model')) {
|
if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model') && !association.hasOwnProperty('key')) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,6 +309,7 @@ module.exports = {
|
|||||||
autoPopulate: _.get(association, 'autoPopulate', true),
|
autoPopulate: _.get(association, 'autoPopulate', true),
|
||||||
dominant: details.dominant !== true,
|
dominant: details.dominant !== true,
|
||||||
plugin: association.plugin || undefined,
|
plugin: association.plugin || undefined,
|
||||||
|
where: details.where,
|
||||||
});
|
});
|
||||||
} else if (association.hasOwnProperty('model')) {
|
} else if (association.hasOwnProperty('model')) {
|
||||||
definition.associations.push({
|
definition.associations.push({
|
||||||
@ -262,6 +321,15 @@ module.exports = {
|
|||||||
autoPopulate: _.get(association, 'autoPopulate', true),
|
autoPopulate: _.get(association, 'autoPopulate', true),
|
||||||
dominant: details.dominant !== true,
|
dominant: details.dominant !== true,
|
||||||
plugin: association.plugin || undefined,
|
plugin: association.plugin || undefined,
|
||||||
|
where: details.where,
|
||||||
|
});
|
||||||
|
} else if (association.hasOwnProperty('key')) {
|
||||||
|
definition.associations.push({
|
||||||
|
alias: key,
|
||||||
|
type: 'collection',
|
||||||
|
nature: infos.nature,
|
||||||
|
autoPopulate: _.get(association, 'autoPopulate', true),
|
||||||
|
key: association.key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user