[WIP] Polymorphic associations with Bookshelf

This commit is contained in:
Aurelsicoko 2018-02-20 19:59:05 +01:00
parent 128a3a67e6
commit dc75b51d6f
9 changed files with 191 additions and 51 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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"
}
}

View File

@ -46,4 +46,4 @@
"npm": ">= 5.0.0"
},
"license": "MIT"
}
}

View File

@ -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,

View File

@ -38,6 +38,14 @@
"via": "users",
"plugin": "users-permissions",
"configurable": false
},
"avatar": {
"model": "upload",
"via": "related"
},
"photos": {
"collection": "upload",
"via": "related"
}
}
}

View File

@ -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,

View File

@ -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

View File

@ -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)) {