Last active
July 8, 2021 19:48
-
-
Save jordanell/d2f6bd69a40fbe9e4976a5baf8cc1d2a to your computer and use it in GitHub Desktop.
Sequelize paranoid delete cascade
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import paranoidDeleteCascade from './helpers/paranoidDeleteCascade'; | |
// Patch the paranoid delete functionality of Sequelize | |
sequelize.addHook('afterDestroy', paranoidDeleteCascade(db)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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, });