bookshelf clean populate

This commit is contained in:
Alexandre Bodin 2019-07-30 11:22:44 +02:00
parent 72c7203e18
commit fd54e71baf
3 changed files with 176 additions and 201 deletions

View File

@ -7,11 +7,6 @@
module.exports = {
find(params) {
// return strapi.query('article').find(params, {
// manyTags: () => {},
// ['linkedTags.linkedArticles.pic']: () => {},
// });
return strapi.query('article').find(params, ['ingredients']);
return strapi.query('article').find(params);
},
};

View File

@ -461,8 +461,6 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
const groups = relations[key].toJSON().map(el => el.slice);
attrs[key] = repeatable === true ? groups : _.first(groups) || null;
} else {
attrs[key] = repeatable === true ? [] : null;
}
});
@ -523,6 +521,176 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
return attrs;
};
const findModelByAssoc = ({ assoc }) => {
const target = assoc.collection || assoc.model;
return assoc.plugin === 'admin'
? strapi.admin.models[target]
: assoc.plugin
? strapi.plugins[assoc.plugin].models[target]
: strapi.models[target];
};
const isPolymorphic = ({ assoc }) => {
return assoc.nature.toLowerCase().indexOf('morph') !== -1;
};
const formatPolymorphicPopulate = ({ assoc, path, prefix = '' }) => {
if (_.isString(path) && path === assoc.via) {
return `related.${assoc.via}`;
} else if (_.isString(path) && path === assoc.alias) {
// MorphTo side.
if (assoc.related) {
return `${prefix}${assoc.alias}.related`;
}
// oneToMorph or manyToMorph side.
// Retrieve collection name because we are using it to build our hidden model.
const model = findModelByAssoc({ assoc });
return {
[`${prefix}${assoc.alias}.${model.collectionName}`]: function(
query
) {
query.orderBy('created_at', 'desc');
},
};
}
};
const createAssociationPopulate = () => {
return definition.associations
.filter(ast => ast.autoPopulate !== false)
.map(assoc => {
if (isPolymorphic({ assoc })) {
return formatPolymorphicPopulate({
assoc,
path: assoc.alias,
});
}
let path = assoc.alias;
let extraAssocs = [];
if (assoc) {
const assocModel = findModelByAssoc({ assoc });
extraAssocs = assocModel.associations
.filter(assoc => isPolymorphic({ assoc }))
.map(assoc =>
formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix: `${path}.`,
})
);
}
return [assoc.alias, ...extraAssocs];
})
.reduce((acc, val) => acc.concat(val), []);
};
const populateGroup = key => {
let paths = [];
const group = strapi.groups[definition.attributes[key].group];
const assocs = (group.associations || []).filter(
assoc => assoc.autoPopulate === true
);
// paths.push(`${key}.slice`);
assocs.forEach(assoc => {
if (isPolymorphic({ assoc })) {
const rel = formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix: `${key}.slice.`,
});
paths.push(rel);
} else {
paths.push(`${key}.slice.${assoc.alias}`);
}
});
return paths;
};
const createGroupsPopulate = () => {
const groupsToPopulate = groupAttributes.reduce((acc, key) => {
const attribute = definition.attributes[key];
const autoPopulate = _.get(attribute, ['autoPopulate'], true);
if (autoPopulate === true) {
return acc.concat(populateGroup(key));
}
return acc;
}, []);
return groupsToPopulate;
};
const isGroup = (def, key) =>
_.get(def, ['attributes', key, 'type']) === 'group';
const formatPopulateOptions = withRelated => {
if (!Array.isArray(withRelated)) withRelated = [withRelated];
const obj = withRelated.reduce((acc, key) => {
if (_.isString(key)) {
acc[key] = () => {};
return acc;
}
return _.extend(acc, key);
}, {});
// if groups are no
const finalObj = Object.keys(obj).reduce((acc, key) => {
// check the key path and update it if necessary nothing more
const parts = key.split('.');
let newKey;
let prefix = '';
let tmpModel = definition;
for (let part of parts) {
if (isGroup(tmpModel, part)) {
tmpModel = strapi.groups[tmpModel.attributes[part].group];
// add group path and there relations / images
const path = `${prefix}${part}.slice`;
newKey = path;
prefix = `${path}.`;
continue;
}
const assoc = tmpModel.associations.find(
association => association.alias === part
);
if (!assoc) return acc;
tmpModel = findModelByAssoc({ assoc });
if (isPolymorphic({ assoc })) {
const path = formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix,
});
return _.extend(acc, path);
}
newKey = `${prefix}${part}`;
prefix = `${newKey}.`;
}
acc[newKey] = obj[key];
return acc;
}, {});
return [finalObj];
};
// Initialize lifecycle callbacks.
loadedModel.initialize = function() {
const lifecycle = {
@ -546,201 +714,21 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
}
});
const findModelByAssoc = ({ assoc }) => {
const target = assoc.collection || assoc.model;
return assoc.plugin === 'admin'
? strapi.admin.models[target]
: assoc.plugin
? strapi.plugins[assoc.plugin].models[target]
: strapi.models[target];
};
const isPolymorphic = ({ assoc }) => {
return assoc.nature.toLowerCase().indexOf('morph') !== -1;
};
const formatPolymorphicPopulate = ({ assoc, path, prefix = '' }) => {
if (_.isString(path) && path === assoc.via) {
return `related.${assoc.via}`;
} else if (_.isString(path) && path === assoc.alias) {
// MorphTo side.
if (assoc.related) {
return `${prefix}${assoc.alias}.related`;
}
// oneToMorph or manyToMorph side.
// Retrieve collection name because we are using it to build our hidden model.
const model = findModelByAssoc({ assoc });
return {
[`${prefix}${assoc.alias}.${model.collectionName}`]: function(
query
) {
query.orderBy('created_at', 'desc');
},
};
}
};
// const addPolymorphicRelated = path => {
// const assoc = definition.associations.find(
// assoc => assoc.alias === path || assoc.via === path
// );
// if (assoc && isPolymorphic({ assoc })) {
// return formatPolymorphicPopulate({
// assoc,
// path,
// });
// }
// let extraAssocs = [];
// if (assoc) {
// const assocModel = findModelByAssoc({ assoc });
// extraAssocs = assocModel.associations
// .filter(assoc => isPolymorphic({ assoc }))
// .map(assoc =>
// formatPolymorphicPopulate({
// assoc,
// path: assoc.alias,
// prefix: `${path}.`,
// })
// );
// }
// return [path, ...extraAssocs];
// };
function createAssociationPopulate() {
return definition.associations
.filter(ast => ast.autoPopulate !== false)
.map(assoc => {
if (isPolymorphic({ assoc })) {
return formatPolymorphicPopulate({
assoc,
path: assoc.alias,
});
}
let path = assoc.alias;
let extraAssocs = [];
if (assoc) {
const assocModel = findModelByAssoc({ assoc });
extraAssocs = assocModel.associations
.filter(assoc => isPolymorphic({ assoc }))
.map(assoc =>
formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix: `${path}.`,
})
);
}
return [assoc.alias, ...extraAssocs];
})
.reduce((acc, val) => acc.concat(val), []);
}
function populateGroup(key) {
let paths = [];
const group = strapi.groups[definition.attributes[key].group];
const assocs = (group.associations || []).filter(
assoc => assoc.autoPopulate === true
);
assocs.forEach(assoc => {
if (isPolymorphic({ assoc })) {
const rel = formatPolymorphicPopulate({
assoc,
path: assoc.alias,
prefix: `${key}.slice.`,
});
paths.push(rel);
} else {
paths.push(`${key}.slice.${assoc.alias}`);
}
});
paths.push(`${key}.slice`);
return paths;
}
function createGroupsPopulate() {
const groupsToPopulate = groupAttributes.reduce((acc, key) => {
const attribute = definition.attributes[key];
const autoPopulate = _.get(attribute, ['autoPopulate'], true);
if (autoPopulate === true) {
return acc.concat(populateGroup(key));
}
return acc;
}, []);
return groupsToPopulate;
}
const isGroup = key => groupAttributes.includes(key);
function formatPopulateOptions(populate) {
// if groups are no
return populate
.reduce((acc, opt) => {
if (typeof opt === 'string') {
// split in parts and check if some parts of the path are morph or groups and update them
const parts = opt.split('.');
if (parts.length === 1) {
if (isGroup(opt)) {
// add group path and there relations / images
return acc.concat(populateGroup(opt));
}
}
}
if (typeof opt === 'object' && opt !== null) {
return acc.concat(opt);
}
return acc;
}, [])
.reduce((acc, val) => acc.concat(val), []);
}
// Update withRelated level to bypass many-to-many association for polymorphic relationshiips.
// Apply only during fetching.
this.on('fetching fetching:collection', (instance, attrs, options) => {
// do not populate anything
if (options.withRelated === false) return;
if (options.isEager === true) return;
if (_.isNil(options.withRelated)) {
options.withRelated = []
.concat(createGroupsPopulate())
.concat(createAssociationPopulate());
} else if (Array.isArray(options.withRelated)) {
} else {
options.withRelated = formatPopulateOptions(options.withRelated);
} else if (_.isObject(options.withRelated)) {
options.withRelated = formatPopulateOptions([options.withRelated]);
}
// if (_.isArray(options.withRelated)) {
// options.withRelated = options.withRelated
// .concat(groupAttributes.map(key => `${key}.slice`))
// .map(addPolymorphicRelated)
// .reduce((acc, paths) => acc.concat(paths), []);
// } else {
// options.withRelated = groupAttributes
// .map(key => `${key}.slice`)
// .map(addPolymorphicRelated)
// .reduce((acc, paths) => acc.concat(paths), []);
// }
});
this.on('fetching fetching:collection', () => {
return _.isFunction(target[model.toLowerCase()]['beforeFetchAll'])
? target[model.toLowerCase()]['beforeFetchAll']
: Promise.resolve();

View File

@ -19,11 +19,6 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return model.attributes[key].type === 'group';
});
// default relations to populate
const defaultPopulate = model.associations
.filter(ast => ast.autoPopulate !== false)
.map(ast => ast.alias);
// Returns an object with relation keys only to create relations in DB
const pickRelations = values => {
return _.pick(values, assocKeys);
@ -58,7 +53,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
}
const entry = await model.forge(params).fetch({
withRelated: _.isNil(populate) ? defaultPopulate : populate,
withRelated: populate,
});
return entry ? entry.toJSON() : null;
@ -73,7 +68,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return model
.query(buildQuery({ model, filters }))
.fetchAll({
withRelated: _.isNil(populate) ? defaultPopulate : populate,
withRelated: populate,
transacting,
})
.then(results => results.toJSON());
@ -199,9 +194,6 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
// Convert `params` object to filters compatible with Bookshelf.
const filters = modelUtils.convertParams(modelKey, params);
// Select field to populate.
const withRelated = populate || defaultPopulate;
return model
.query(qb => {
buildSearchQuery(qb, model, params);
@ -219,7 +211,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
}
})
.fetchAll({
withRelated,
withRelated: populate,
});
}