diff --git a/packages/strapi-mongoose/lib/index.js b/packages/strapi-mongoose/lib/index.js index 9e4c475a11..1d5d5b8a2e 100755 --- a/packages/strapi-mongoose/lib/index.js +++ b/packages/strapi-mongoose/lib/index.js @@ -80,6 +80,7 @@ module.exports = function (strapi) { // Initialize lifecycle callbacks. const preLifecycle = { validate: 'beforeCreate', + findOneAndUpdate: 'beforeUpdate', findOneAndRemove: 'beforeDestroy', remove: 'beforeDestroy', update: 'beforeUpdate', @@ -88,6 +89,36 @@ module.exports = function (strapi) { 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 + } + } else { + this._mongooseOptions.populate[association.alias].path = `${association.alias}.${association.key}`; + } + } + + next(); + }); + }); + }); + } + _.forEach(preLifecycle, (fn, key) => { if (_.isFunction(target[model.toLowerCase()][fn])) { collection.schema.pre(key, function (next) { @@ -126,29 +157,21 @@ module.exports = function (strapi) { }); }); - collection.schema.set('toObject', { - virtuals: true - }); - - collection.schema.set('toJSON', { - virtuals: true - }); + collection.schema.options.toObject = collection.schema.options.toJSON = { + virtuals: true, + transform: function (doc, returned, opts) { + morphAssociations.forEach(association => { + if (Array.isArray(returned[association.alias]) && returned[association.alias].length > 0) { + returned[association.alias] = returned[association.alias].map(o => o[association.key]); + } + }); + } + }; if (!plugin) { - // Enhance schema with polymorphic relationships. - const morphs = _.pickBy(definition.loadedModel, model => { - return model.type === 'discriminator'; - }); - - if (_.size(morphs) > 0) { - const morph = morphs[Object.keys(morphs)[0]]; - - global[definition.globalName] = global[morph.ref].discriminator(morph.discriminator, collection.schema); - } else { - global[definition.globalName] = instance.model(definition.globalName, collection.schema, definition.collectionName); - } + global[definition.globalName] = instance.model(definition.globalId, collection.schema, definition.collectionName); } else { - instance.model(definition.globalName, collection.schema, definition.collectionName); + instance.model(definition.globalId, collection.schema, definition.collectionName); } // Expose ORM functions through the `target` object. @@ -206,21 +229,10 @@ module.exports = function (strapi) { // Call this callback function after we are done parsing // all attributes for relationships-- see below. const done = _.after(_.size(definition.attributes), () => { - // Extract discriminator (morphTo side) - const discriminators = _.pickBy(definition.loadedModel, model => { - return model.hasOwnProperty('discriminatorKey'); - }); - - const options = {}; - - if (_.size(discriminators) > 0) { - options.discriminatorKey = 'type'; - } - - // Generate schema without virtual populate & discriminators. - let schema = new instance.Schema(_.omitBy(definition.loadedModel, model => { - return model.type === 'virtual' || model.type === 'discriminator' || model.hasOwnProperty('discriminatorKey'); - }), options); + // Generate schema without virtual populate + const schema = new instance.Schema(_.omitBy(definition.loadedModel, model => { + return model.type === 'virtual'; + })); _.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', schema); @@ -275,7 +287,7 @@ module.exports = function (strapi) { const FK = _.find(definition.associations, {alias: name}); 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] = { type: 'virtual', ref, @@ -283,6 +295,20 @@ module.exports = function (strapi) { 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. details.isVirtual = true; } else { @@ -299,13 +325,26 @@ module.exports = function (strapi) { 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. - if ((FK && _.isUndefined(FK.via)) || details.dominant !== true) { + if ((FK && _.isUndefined(FK.via)) || details.dominant !== true && FK.nature !== 'manyToMorph') { definition.loadedModel[name] = { type: 'virtual', ref, 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. details.isVirtual = true; } else { @@ -316,21 +355,27 @@ module.exports = function (strapi) { } break; } - case 'morphOne': { - const discriminator = plugin ? `${plugin}_${model.toLowerCase()}` : model.toLowerCase(); - const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId; - + case 'belongsToMorph': { definition.loadedModel[name] = { - type: 'discriminator', - ref, - discriminator - } + kind: String, + [details.where]: String, + [details.key]: { + type: instance.Schema.Types.ObjectId, + refPath: `${name}.kind` + } + }; break; } - case 'morphTo': { - definition.loadedModel[name] = { - discriminatorKey: 'kind' - }; + case 'belongsToManyMorph': { + definition.loadedModel[name] = [{ + kind: String, + [details.where]: String, + [details.key]: { + type: instance.Schema.Types.ObjectId, + refPath: `${name}.kind` + } + }]; + break; } default: break; diff --git a/packages/strapi-mongoose/package.json b/packages/strapi-mongoose/package.json index 02a864e3d2..ec4bd77711 100755 --- a/packages/strapi-mongoose/package.json +++ b/packages/strapi-mongoose/package.json @@ -16,7 +16,7 @@ "main": "./lib", "dependencies": { "lodash": "^4.17.4", - "mongoose": "^5.0.0-rc1", + "mongoose": "^5.0.4", "mongoose-float": "^1.0.2", "pluralize": "^6.0.0", "strapi-utils": "3.0.0-alpha.9.3" diff --git a/packages/strapi-utils/lib/models.js b/packages/strapi-utils/lib/models.js index cc29f8e465..b7872d5013 100755 --- a/packages/strapi-utils/lib/models.js +++ b/packages/strapi-utils/lib/models.js @@ -114,7 +114,10 @@ module.exports = { // Break loop return false; } else if (attribute.hasOwnProperty('key')) { - types.other = 'morphTo'; // MorphTo + types.other = 'morphTo'; + + // Break loop + return false; } }); }); @@ -162,22 +165,56 @@ module.exports = { }); } 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 + return false; + } + } + }); + }); } - if (types.current === 'morphTo') { + if (types.current === 'collection' && types.other === 'morphTo') { return { - nature: 'morphTo', - verbose: 'morphTo' + nature: 'manyToMorph', + verbose: 'belongsToMany' }; } else if (types.current === 'modelD' && types.other === 'morphTo') { return { - nature: 'oneToOne', - verbose: 'morphOne' + nature: 'oneToMorph', + verbose: 'belongsTo' }; - } else if (types.current === 'collection' && types.other === 'morphTo') { + } else if (types.current === 'morphTo' && types.other === 'collection') { return { - nature: 'oneToMany', - verbose: 'morphMany' + 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 { @@ -253,7 +290,7 @@ module.exports = { } // Exclude non-relational attribute - if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model')) { + if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model') && !association.hasOwnProperty('key')) { return undefined; } @@ -272,6 +309,7 @@ module.exports = { autoPopulate: _.get(association, 'autoPopulate', true), dominant: details.dominant !== true, plugin: association.plugin || undefined, + where: details.where, }); } else if (association.hasOwnProperty('model')) { definition.associations.push({ @@ -283,6 +321,15 @@ module.exports = { autoPopulate: _.get(association, 'autoPopulate', true), dominant: details.dominant !== true, 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) {