5

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)
Daniel Carra
  • 81
  • 1
  • 6

2 Answers2

5

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.

Calvintwr
  • 8,308
  • 5
  • 28
  • 42
  • Thanks for the links! I'm new to sequelize so this gets rather confusing. I tried you code corrections, and still got the same error when I attempt to use include in my GET request _SequelizeEagerLoadingError: academic_manager is not associated to campus!_. I tried using aliases as one of the links explalined: I deleted the association from campus and added `Manager.belongsTo(models.Campus, { as: 'managers' }` in the manager model and modified the GET request to `include: 'managers'` but then I got the following error: _Error: Association with alias "managers" does not exist on campus_ – Daniel Carra May 17 '20 at 05:56
  • I tried adding the association in Campus again, but got the same error, specifying the foreign key. And without writing the foreign key too. – Daniel Carra May 17 '20 at 05:59
  • try making all your model names consistent. i think you defined `academic_manager` as the Manager table. And in your associate, it could be Models.academic_manager. – Calvintwr May 17 '20 at 11:28
  • 1
    I realized it was a really dumb mistake, I forgot to actually make the associations happen. This is my first post here, thanks for your answers, should I delete my question? or just update it? – Daniel Carra May 17 '20 at 16:27
  • 1
    Keep everything, the answer is clear and might help someone else (like me) – Ernest Jones Aug 10 '20 at 08:21
3

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.

Daniel Carra
  • 81
  • 1
  • 6