don't use ModelManager.forEachModel, it can't handle cyclic model references properly
jedwards1211 opened this issue · 6 comments
I have two models, Organization
and UserGroup
. Organization
has many UserGroup
s. But Organization
also "belongs to" one "All Users Group"; it has a FK constraint to this specific group. Perfectly valid DB schema.
But createContext
crashes with this error:
Error: Cyclic dependency found. Organizations is dependent of itself.
Dependency chain: APITokens -> Organizations -> UserGroups => Organizations
at visit (/Users/andy/clarity/node_modules/toposort-class/build/toposort.js:173:27)
at visit (/Users/andy/clarity/node_modules/toposort-class/build/toposort.js:212:29)
at visit (/Users/andy/clarity/node_modules/toposort-class/build/toposort.js:212:29)
at Toposort.sort (/Users/andy/clarity/node_modules/toposort-class/build/toposort.js:252:29)
at ModelManager.forEachModel (/Users/andy/clarity/node_modules/sequelize/lib/model-manager.js:86:21)
at createContext (/Users/andy/clarity/node_modules/dataloader-sequelize/lib/index.js:446:26)
I've read that I can pass constraint: false
when creating the associations, but this isn't really proper since both associations have constraints.
I don't think dataloader-sequelize
needs to go through the models in toposorted order? Nothing in sequelize itself seems to be using it right now (we don't use sequelize.sync
or whatever) and the idea of topologically sorting the models seems fundamentally flawed since it's perfectly possible to create cyclic constraints in SQL.
Is there another public API to iterate over models?
One can iterate over Object.values(sequelize.models)
(not sure if that's technically public API or not?)
I argue in that linked issue that forEachModel
shouldn't do a toposort, and the toposort should be confined to some internal API that sync
uses.
Not sure if that's public API or not either, but it works for me.
My team has spent half a day debugging this issue. It would be great if it was fixed.
It's possible to monkey patch the library before it monkey patches sequelize:
// given 'sequelize' is set to an instance of Sequelize (ie. new Sequelze({ ... }))
const originalForEachModel = sequelize.modelManager.forEachModel;
sequelize.modelManager.forEachModel = (...args) => {
Object.values(sequelize.models).forEach(...args);
};
const dataloaderContext = createContext(sequelize);
sequelize.modelManager.forEachModel = originalForEachModel;
// Proceed to use dataloaderContext in the context
This monkey patch fixed our cyclic dependency issues entirely.
I need to make a PR for this