mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 09:56:44 +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.
 | ||||
|                   const preLifecycle = { | ||||
|                     validate: 'beforeCreate', | ||||
|                     findOneAndUpdate: 'beforeUpdate', | ||||
|                     findOneAndRemove: 'beforeDestroy', | ||||
|                     remove: 'beforeDestroy', | ||||
|                     update: 'beforeUpdate', | ||||
| @ -88,6 +89,37 @@ 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, | ||||
|                                   [`${association.via}.kind`]: definition.globalId | ||||
|                                 } | ||||
|                               } 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,18 +158,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) { | ||||
|                     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.
 | ||||
| @ -196,9 +231,11 @@ module.exports = function (strapi) { | ||||
|               // all attributes for relationships-- see below.
 | ||||
|               const done = _.after(_.size(definition.attributes), () => { | ||||
|                 // 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'; | ||||
|                 }))); | ||||
|                 })); | ||||
| 
 | ||||
|                 _.set(strapi.config.hook.settings.mongoose, 'collections.' + mongooseUtils.toCollectionName(definition.globalName) + '.schema', schema); | ||||
| 
 | ||||
|                 loadedAttributes(); | ||||
|               }); | ||||
| @ -251,7 +288,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, | ||||
| @ -259,6 +296,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 { | ||||
| @ -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; | ||||
| 
 | ||||
|                     // 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 { | ||||
| @ -292,6 +356,28 @@ module.exports = function (strapi) { | ||||
|                     } | ||||
|                     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: | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
| @ -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.10.1" | ||||
|  | ||||
| @ -94,6 +94,8 @@ module.exports = { | ||||
|           types.other = 'collectionD'; | ||||
|         } else if (relatedAttribute.hasOwnProperty('model')) { | ||||
|           types.other = 'model'; | ||||
|         } else if (relatedAttribute.hasOwnProperty('key')) { | ||||
|           types.other = 'morphTo'; | ||||
|         } | ||||
|       } else if (association.hasOwnProperty('via') && association.hasOwnProperty('model')) { | ||||
|         types.current = 'modelD'; | ||||
| @ -109,6 +111,11 @@ module.exports = { | ||||
|             } else if (attribute.hasOwnProperty('model')) { | ||||
|               types.other = 'model'; | ||||
| 
 | ||||
|               // Break loop
 | ||||
|               return false; | ||||
|             } else if (attribute.hasOwnProperty('key')) { | ||||
|               types.other = 'morphTo'; | ||||
| 
 | ||||
|               // Break loop
 | ||||
|               return false; | ||||
|             } | ||||
| @ -150,6 +157,37 @@ module.exports = { | ||||
|               } else if (attribute.hasOwnProperty('model')) { | ||||
|                 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
 | ||||
|                 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 { | ||||
|           nature: 'oneToOne', | ||||
|           verbose: 'belongsTo' | ||||
| @ -232,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; | ||||
|       } | ||||
| 
 | ||||
| @ -251,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({ | ||||
| @ -262,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) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Jim LAURIE
						Jim LAURIE