From dc75b51d6f473ebfb53a3cb74961df6e25da641f Mon Sep 17 00:00:00 2001 From: Aurelsicoko Date: Tue, 20 Feb 2018 19:59:05 +0100 Subject: [PATCH] [WIP] Polymorphic associations with Bookshelf --- packages/strapi-bookshelf/.editorconfig | 16 --- packages/strapi-bookshelf/lib/index.js | 103 ++++++++++++++++++ packages/strapi-bookshelf/package.json | 4 +- packages/strapi-knex/package.json | 2 +- packages/strapi-mongoose/lib/index.js | 64 ++++++----- .../models/User.settings.json | 8 ++ packages/strapi-utils/lib/models.js | 38 ++++++- packages/strapi/lib/core/store.js | 4 +- packages/strapi/lib/middlewares/mask/index.js | 3 + 9 files changed, 191 insertions(+), 51 deletions(-) delete mode 100755 packages/strapi-bookshelf/.editorconfig diff --git a/packages/strapi-bookshelf/.editorconfig b/packages/strapi-bookshelf/.editorconfig deleted file mode 100755 index 473e45184b..0000000000 --- a/packages/strapi-bookshelf/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[{package.json,*.yml}] -indent_style = space -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false diff --git a/packages/strapi-bookshelf/lib/index.js b/packages/strapi-bookshelf/lib/index.js index 883761975a..006475ef39 100755 --- a/packages/strapi-bookshelf/lib/index.js +++ b/packages/strapi-bookshelf/lib/index.js @@ -133,6 +133,30 @@ module.exports = function(strapi) { : key; }); + if (definition.globalId === 'Upload') { + loadedModel.serialize = function(options) { + const attrs = _.clone(this.attributes); + + if (options && options.shallow) { + return attrs; + } + + const relations = this.relations; + + for (let key in relations) { + const relation = relations[key]; + + attrs[key] = relation.toJSON ? relation.toJSON(options) : relation; + + if (key === 'related') { + attrs[key] = attrs[key].map(rel => rel['related']); + } + } + + return attrs; + } + } + // Initialize lifecycle callbacks. loadedModel.initialize = function() { const lifecycle = { @@ -150,6 +174,22 @@ module.exports = function(strapi) { saved: 'afterSave' }; + if (definition.globalId === 'Upload') { + this.on('fetching fetching:collection', (instance, attrs, options) => { + if (_.isArray(options.withRelated)) { + options.withRelated = options.withRelated.map(path => { + if (_.isString(path) && path === 'related') { + return 'related.related'; + } + + return path; + }) + } + + return Promise.resolve(); + }); + } + _.forEach(lifecycle, (fn, key) => { if (_.isFunction(target[model.toLowerCase()][fn])) { this.on(key, target[model.toLowerCase()][fn]); @@ -369,6 +409,69 @@ module.exports = function(strapi) { }; break; } + case 'morphOne': { + loadedModel[name] = function() { + return this.morphOne(GLOBALS[globalId], details.via); + } + break; + } + case 'morphMany': { + loadedModel[name] = function() { + return this.morphMany(GLOBALS[globalId], details.via); + } + break; + } + case 'belongsToMorph': + case 'belongsToManyMorph': { + const association = definition.associations + .find(association => association.alias === name); + + // console.log("coucou"); + // console.log(association.related.map(id => GLOBALS[id])); + + const morphValues = association.related.map(id => { + let models = Object.values(strapi.models).filter(model => model.globalId === id); + + if (models.length === 0) { + models = Object.keys(strapi.plugins).reduce((acc, current) => { + const models = Object.values(strapi.plugins[current].models).filter(model => model.globalId === id); + + if (acc.length === 0 && models.length > 0) { + acc = models; + } + + return acc; + }, []); + } + + if (models.length === 0) { + strapi.log.error('Impossible to register the `' + model + '` model.'); + strapi.log.error('The collection name cannot be found for the morphTo method.'); + strapi.stop(); + } + + return models[0].collectionName; + }); + + + // Define new model. + const options = { + tableName: `${loadedModel.tableName}_morph`, + related: function () { + return this.morphTo(name, ...association.related.map((id, index) => [GLOBALS[id], morphValues[index]])); + } + }; + + const MorphModel = ORM.Model.extend(options); + + loadedModel[name] = function () { + return this.hasMany( + MorphModel, + 'upload_id' + ); + }; + break; + } default: { break; } diff --git a/packages/strapi-bookshelf/package.json b/packages/strapi-bookshelf/package.json index 6ac489bacb..2db9f3e606 100755 --- a/packages/strapi-bookshelf/package.json +++ b/packages/strapi-bookshelf/package.json @@ -16,7 +16,7 @@ }, "main": "./lib", "dependencies": { - "bookshelf": "^0.10.3", + "bookshelf": "^0.12.1", "lodash": "^4.17.4", "pluralize": "^6.0.0", "strapi-knex": "3.0.0-alpha.9.3", @@ -55,4 +55,4 @@ "npm": ">= 5.3.0" }, "license": "MIT" -} \ No newline at end of file +} diff --git a/packages/strapi-knex/package.json b/packages/strapi-knex/package.json index 839ed9abcb..1196150f53 100755 --- a/packages/strapi-knex/package.json +++ b/packages/strapi-knex/package.json @@ -46,4 +46,4 @@ "npm": ">= 5.0.0" }, "license": "MIT" -} \ No newline at end of file +} diff --git a/packages/strapi-mongoose/lib/index.js b/packages/strapi-mongoose/lib/index.js index 1e87bec89d..d446479138 100755 --- a/packages/strapi-mongoose/lib/index.js +++ b/packages/strapi-mongoose/lib/index.js @@ -296,20 +296,6 @@ 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 { @@ -326,26 +312,13 @@ 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 && FK.nature !== 'manyToMorph') { + if ((FK && _.isUndefined(FK.via)) || details.dominant !== true) { 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 { @@ -356,6 +329,41 @@ module.exports = function (strapi) { } break; } + case 'morphOne': { + 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 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; + break; + } + case 'morphMany': { + const FK = _.find(definition.associations, {alias: name}); + const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId; + 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; + break; + } case 'belongsToMorph': { definition.loadedModel[name] = { kind: String, diff --git a/packages/strapi-plugin-users-permissions/models/User.settings.json b/packages/strapi-plugin-users-permissions/models/User.settings.json index 99479b1cba..289c93a6c1 100644 --- a/packages/strapi-plugin-users-permissions/models/User.settings.json +++ b/packages/strapi-plugin-users-permissions/models/User.settings.json @@ -38,6 +38,14 @@ "via": "users", "plugin": "users-permissions", "configurable": false + }, + "avatar": { + "model": "upload", + "via": "related" + }, + "photos": { + "collection": "upload", + "via": "related" } } } diff --git a/packages/strapi-utils/lib/models.js b/packages/strapi-utils/lib/models.js index b7872d5013..0bb00559b6 100755 --- a/packages/strapi-utils/lib/models.js +++ b/packages/strapi-utils/lib/models.js @@ -199,12 +199,12 @@ module.exports = { if (types.current === 'collection' && types.other === 'morphTo') { return { nature: 'manyToMorph', - verbose: 'belongsToMany' + verbose: 'morphMany' }; } else if (types.current === 'modelD' && types.other === 'morphTo') { return { nature: 'oneToMorph', - verbose: 'belongsTo' + verbose: 'morphOne' }; } else if (types.current === 'morphTo' && types.other === 'collection') { return { @@ -324,9 +324,43 @@ module.exports = { where: details.where, }); } else if (association.hasOwnProperty('key')) { + const pluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => { + Object.keys(strapi.plugins[current].models).forEach((entity) => { + Object.keys(strapi.plugins[current].models[entity].attributes).forEach((attribute) => { + const attr = strapi.plugins[current].models[entity].attributes[attribute]; + if ( + (attr.collection || attr.model || '').toLowerCase() === model.toLowerCase() && + strapi.plugins[current].models[entity].globalId !== definition.globalId + ) { + acc.push(strapi.plugins[current].models[entity].globalId); + } + }); + }); + + return acc; + }, []); + + const appModels = Object.keys(strapi.models).reduce((acc, entity) => { + Object.keys(strapi.models[entity].attributes).forEach((attribute) => { + const attr = strapi.models[entity].attributes[attribute]; + + if ( + (attr.collection || attr.model || '').toLowerCase() === model.toLowerCase() && + strapi.models[entity].globalId !== definition.globalId + ) { + acc.push(strapi.models[entity].globalId); + } + }); + + return acc; + }, []); + + const models = _.uniq(appModels.concat(pluginsModels)); + definition.associations.push({ alias: key, type: 'collection', + related: models, nature: infos.nature, autoPopulate: _.get(association, 'autoPopulate', true), key: association.key, diff --git a/packages/strapi/lib/core/store.js b/packages/strapi/lib/core/store.js index 5051cdac63..266a7c2b95 100644 --- a/packages/strapi/lib/core/store.js +++ b/packages/strapi/lib/core/store.js @@ -154,8 +154,8 @@ module.exports = { CREATE TABLE ${quote}${Model.tableName || Model.collectionName}${quote} ( id ${Model.client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY, - key text, - value text, + ${quote}key${quote} text, + ${quote}value${quote} text, environment text, type text, tag text diff --git a/packages/strapi/lib/middlewares/mask/index.js b/packages/strapi/lib/middlewares/mask/index.js index 5bf2e918e2..1e49818842 100644 --- a/packages/strapi/lib/middlewares/mask/index.js +++ b/packages/strapi/lib/middlewares/mask/index.js @@ -53,6 +53,9 @@ module.exports = strapi => { // Recursive to mask the private properties. const mask = (payload) => { + // Handle ORM toJSON() method to work on real JSON object. + payload = payload.toJSON ? payload.toJSON() : payload; + if (_.isArray(payload)) { return payload.map(mask); } else if (_.isPlainObject(payload)) {