Skip to content

Instantly share code, notes, and snippets.

@jordanell
Last active July 8, 2021 19:48
Show Gist options
  • Save jordanell/d2f6bd69a40fbe9e4976a5baf8cc1d2a to your computer and use it in GitHub Desktop.
Save jordanell/d2f6bd69a40fbe9e4976a5baf8cc1d2a to your computer and use it in GitHub Desktop.
Sequelize paranoid delete cascade
import paranoidDeleteCascade from './helpers/paranoidDeleteCascade';
// Patch the paranoid delete functionality of Sequelize
sequelize.addHook('afterDestroy', paranoidDeleteCascade(db));
import isArray from 'lodash/isArray';
import map from 'lodash/map';
const paranoidDeleteCascade = (models) =>
async (instance, options, next) => {
// Only operate on paranoid models
if (!instance.$modelOptions.paranoid) {
return next();
}
const modelName = instance.$modelOptions.name.singular;
await Promise.all(
// Go over all associations of the instance model, and delete if needed
map(models[modelName].associations, async (association) => {
try {
// Only delete if cascade is set up correctly
if (association.options.onDelete !== 'CASCADE') {
return true;
}
let relationModel = association.target;
const getOptions = { transaction: options.transaction };
// Handle "through" cases
if (association.through) {
relationModel = association.through.model;
// Include the id of the through model instance
getOptions.include = [{
model: relationModel,
}];
}
// Load id(s) of association
const instances = await instance[`get${association.as}`](getOptions);
if (isArray(instances)) {
// Association has no results so nothing to delete
if (instances.length === 0) {
return true;
}
// Delete all individually as bulk delete doesn't cascase in sequelize
return await Promise.all(instances.map(i => i.destroy(Object.assign({}, options, { individualHooks: true }))));
}
// Association is not set, so nothing to delete
if (!instances) {
return true;
}
return await instances.destroy(options);
} catch (error) {
// If we had issues deleting, we have bigger problems
Promise.resolve(true);
}
return true;
})
);
return next();
};
export default paranoidDeleteCascade;
Copy link

ghost commented Mar 6, 2021

I confirm that this does work, great idea man, it might put some load on your server though specially if you're soft deleting large amount of data with a lot of associations.
One hint though, you need to the "individualHooks: true," property to the destroy options for the "afterDestroy" hook to run, otherwise it'll the "afterDestroy" won't fire but the "afterBulkDestroy" will fire instead.
I'm using sequelize version 5.22.3.
await db[model].destroy({ where: { id: item.id }, transaction: transaction, individualHooks: true, });

@jordanell
Copy link
Author

Thanks for the feedback @gostavee and @AntoineGrandchamp . I've incorporated your suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment