SequelizeEagerLoadingError: model is not associated to otherModel

Viewed 1781

I've seen this problem a lot in different forums but I haven't been able to fix it. I'm using Express and PostgreSQL with Sequelize.

Basically I have two models: Campus and academic manager. It's a one to one relation between the two, Campus has one academic manager.

I've implemented both models and the association works, I can see it beeing displayed in mi database in the postgres server. But when I try to implement my get request of campus, I try to include the manager information, without success. I keep receiving the same error:

SequelizeEagerLoadingError: academic_manager is not associated to campus!

I believe my problem has to do with include

Here are my code snippets:

campus.js

const campus = (sequelize, DataTypes) =>{
    const Campus = sequelize.define('campus', {
        name: {
            type: DataTypes.STRING,
            allowNull: false,
        },
        manager: {
            type: DataTypes.INTEGER,
            allowNull: false,
            references: {
                model: 'academic_manager',
                key: 'id'
            }
        } 
    });    

    Campus.associate = models => {
        Campus.hasOne(models.Manager, { foreignKey: 'id'});
    }

    return Campus; 
};

export default campus;

academic_manager.js

const manager = (sequelize, DataTypes) =>{
    const Manager = sequelize.define('academic_manager', {
        name: {
            type: DataTypes.STRING,
            allowNull: false,
        },
        email: {
            type: DataTypes.STRING,
            allowNull: false
        } 
    });    

    Manager.associate = models => {
        Manager.belongsTo(models.Campus, { foreignKey: 'id'});
    }

    return Manager; 
};

export default manager;

And the get method:

router.get('/', async (req, res) => {
    const campus = await req.context.models.Campus.findAll({
        include: [
            { model: req.context.models.Manager }
        ]
    });
    return res.send(campus);
}); 

The get request works without the include. I know the association is achieved because when I describe my campus table and my manager table I have this:

TABLE "campus" CONSTRAINT "campus_manager_fkey" FOREIGN KEY (manager) REFERENCES academic_manager(id)
2 Answers

Your model definition has several problems. Sequelize might be confused as to whether you wanted to associate the models, or just have them referencing each other.

You have added associations which implements relational constraints, but also added reference key which is meant to allow reference but not implement constraints.

Might want to read:

  1. How to implement many to many association in sequelize
  2. https://sequelize.org/v5/manual/associations.html

I have commented your code:

campus.js

const Campus = (sequelize, DataTypes) =>{
    const Campus = sequelize.define('Campus', {
        name: {
            type: DataTypes.STRING,
            allowNull: false,
        }
        // this is not correct.
        // read when to use reference key further below
        /* manager: {
            type: DataTypes.INTEGER,
            allowNull: false,
            references: {
                model: 'academic_manager',
                key: 'id' 
            }

        } */
    });    

    Campus.associate = models => {
        // Campus.hasOne(models.Manager, { foreignKey: 'id'});
        // foreignKey `id` is wrong. Your models already by default have `id`, which is their own.
        // you either define the name as `campus_id`, or just let sequelize handle it for you by not defining it at all and just use `Campus.hasOne(models.Manager)`.
        // but generally it's good practice to define, otherwise when you need it, you have to figure out what did sequelize help you name it as.
        // read: https://sequelize.org/master/manual/assocs.html#providing-the-foreign-key-name-directly
        Campus.hasOne(models.Manager, { foreignKey: 'campus_id'); // this means please inject `campus_id` into `Manager` 
    }

    return Campus; 
};

export default Campus;

academic_manager.js

const Manager = (sequelize, DataTypes) =>{
    const Manager = sequelize.define('Manager', {
        name: {
            type: DataTypes.STRING,
            allowNull: false,
        },
        email: {
            type: DataTypes.STRING,
            allowNull: false
        }
    });    

    Manager.associate = models => {
        // you don't define foreignKey here. because by doing so you are saying
        // you want to inject this foreignKey into `Campus`, which you are not.
        Manager.belongsTo(models.Campus);
    }

    return Manager; 
};

export default Manager;

Additional Info - When to use this referenceKey?

In relational databases, the rule of thumb is that between any 2 model, you should only have 1 path, otherwise it forms a cyclic dependency, which can potentially cause problems.

Imagine you have the third entity Location

Campus.hasOne(Manager)
Manager.belongsTo(Campus)

Location.hasOne(Campus)
Location.belongsTo(Campus)

All is well and good. Lets for a moment imaginatively assume that the Manager stays on the Campus, you can find out their Location like this

Manager -> Campus -> Location

Until such time you have decided that it is a true statement that Manager will always stay in that Location and nowhere else, and your application have more frequently required this information, you will be tempted to do this:

Location.hasOne(Manager)
Manager.belongsTo(Location)

You now have another path Location -> Manager, and you completed the circular reference of Location -> Manager -> Campus -> Location -> Manager....

To break this, you must make a choice between which relationship is more important, or more commonly accessed. In this case it may seem that the relationship between Location and Manager is not all that important, then just continue using Manager -> Campus -> Location.

But you want the cake and also eat it

This is where you don't associate the models, and just use reference keys or use constraints: false. Read https://sequelize.org/master/manual/constraints-and-circularities.html

constraints: false is my usual choice because it still provides me with all the sequelize methods as though the models are associated. But to be very strict, you are by right to be using reference keys at most. Then do a manual join, or do 2 queries by using the reference key you found to locate its data on its referenced table.

My solution was that I wasn't making the associations in my models index. I was missing this:

Object.keys(models).forEach(key => {
  if ('associate' in models[key]) {
    models[key].associate(models);
  }
});

Always remeber to actually make your associations.

Related