0

(Update below)

I'm trying to set up model associations using Sequelize and the .associate method.

I have the setup below but this returns an error:

UnhandledPromiseRejectionWarning: SequelizeEagerLoadingError: ProductMember is not associated to Product!

What am I doing wrong?

Db setup:

const { Sequelize } = require("sequelize");
const config = require("./db.config.js");
const sequelize = new Sequelize(config[process.env]);

module.exports = sequelize;

db.config.js:

module.exports = {
    development: {
        database: DB_NAME,
        username: DB_USER,
        password: DB_PASS,
        dialect: DB_DRIVER,
        logging: false,
        options: {
            host: DB_HOST,
            port: DB_PORT,
            pool: {},
        },
    },
}

/models/product.js:

const { DataTypes } = require("sequelize");
const sequelize = require("../db");

const Product = sequelize.define(
    "Product",
    {
        id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true,
            allowNull: false,
        },
        name: {
            type: DataTypes.STRING(255),
            unique: true,
            allowNull: false,
        },
    }
    {
        tableName: "products",
    }
}

Product.associate = (models) => {
    Product.belongsToMany(models.User, {
        through: models.ProductMember,
        foreignKey: "product_id",
        otherKey: "user_id",
    });
    Product.hasMany(models.ProductMember, {
        foreignKey: "product_id",
        allowNull: false,
    });
};

module.exports = sequelize.model("Product", Product);

models/user.js:

const { DataTypes } = require("sequelize");
const sequelize = require("../db");

const User = sequelize.define(
    "User",
    {
        id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true,
            allowNull: false,
        },
        email: {
            type: DataTypes.STRING(255),
            allowNull: false,
            unique: true,
            isEmail: true,
        },
        username: {
            type: DataTypes.STRING(15),
            allowNull: false,
            unique: true,
        },
    },
    {
        tableName: "users",
    }
);

User.associate = (models) => {
    User.belongsToMany(models.Product, {
        through: models.ProductMember,
        foreignKey: "user_id",
        otherKey: "product_id",
    });
    User.hasMany(models.ProductMember, {
        foreignKey: "user_id",
        allowNull: false,
    });

    User.belongsToMany(models.Product, {
        through: models.UserFavouriteProducts,
        foreignKey: "user_id",
        otherKey: "product_id",
    });
}

module.exports = sequelize.model("User", User);

models/productMember.js:

const { DataTypes } = require("sequelize");
const sequelize = require("../db");

const ProductMember = sequelize.define(
    "ProductMember",
    {
        id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true,
            allowNull: false,
        },
        isAdmin: {
            type: DataTypes.BOOLEAN,
            defaultValue: false,
        },
    },
    {
        tableName: "product_members",

    }
);

ProductMember.associate = (models) => {
    ProductMember.belongsTo(models.User);
    ProductMember.belongsTo(models.Product);
};

module.exports = sequelize.model("ProductMember", ProductMember);

Update: Based on this post I updated the Db setup file to:

const fs = require('fs');
const path = require('path');
var basename = path.basename(module.filename);
const models = path.join(__dirname, '../models');
const db = {};

const Sequelize = require("sequelize");
const config = require("./db.config.js");
const sequelize = new Sequelize(config[process.env]);

fs
    .readdirSync(models)
    .filter(function (file) {
        return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
    })
    .forEach(function (file) {
        var model = require(path.join(models, file))(
            sequelize,
            Sequelize.DataTypes
        );
        db[model.name] = model;
    })

Object.keys(db).forEach(function (modelName) {
    if (db[modelName].associate) {
        db[modelName].associate(db);
    }
})

db.Sequelize = Sequelize;
db.sequelize = sequelize;

module.exports = db;

Model file:

module.exports = (sequelize, Sequelize) => {
    const Coupon = db.sequelize.define(

    //... continue as it was

    return Coupon;
}

So for the model file:

  • Wrapped it inside module.exports = (sequelize, Sequelize) => { }
  • return Coupon at the end
  • removed const sequelize = require("../db");

New problem: But with this new setup, Sequelize-related controller methods no longer work... For example for a controller file:

const User = require("../models/user");

const ensureLoggedIn = async (req, res, next) => {
    ...
    const user = await User.findByPk(id);

it produces the error:

User.findByPk is not a function

I've tried adding const sequelize = require("../db"); to the controller file and then const user = await sequelize.User.findByPk(id); but that produced the same error.

I've tried adding const db = require("../db"); to the controller file and then const user = await db.sequelize.User.findByPk(id); but that produced the error "Cannot read property 'findByPk' of undefined".

Nick
  • 3,496
  • 7
  • 42
  • 96

1 Answers1

1

First, you don't need to call sequelize.model to get a model that registered in Sequelize, you can just export a return value from sequelize.define:

const Product = sequelize.define(
    "Product",
...
module.exports = Product;

Second, it seems you didn't call associate methods of all registered models. Look at my answer here to get an idea of how to do it.

Update: to use models if you defined them like in my answer you need to access them like this:

const { sequelize } = require("../db");

const user = await sequelize.models.User.findByPk(id);
...

Anatoly
  • 20,799
  • 3
  • 28
  • 42
  • Thanks @Anatoly, I've implemented the solution you suggest in the post you referred to. But now Sequelize-related controller methods no longer work (see update at the end of my OP). Any idea how I should solve that? – Nick Jun 29 '22 at 14:32
  • Don't try to import model functions as models, they are already in `sequelize.models` or `database` object (if you followed my example) – Anatoly Jun 29 '22 at 18:04
  • Sorry, I'm not completely sure what you mean. My interpretation of your message: 1) I removed `const User = require("../models/user");` from the controller file. 2) I changed the line in the controller method to `const user = await sequelize.models.User.findByPk(id);`. This generated the error `sequelize is not defined`. 3) I also added to the controller `const sequelize = require("../db");`. But still got an error: `Cannot read property 'User' of undefined`. Could you perhaps show in your answer what the controller should look like? Would really appreciate it. – Nick Jun 29 '22 at 18:33
  • Awesome it works! For another ORM I used to import the model and then use `User.findByPk(id)`. I have like a 1000 occurrences I will need to adjust. What would your thoughts be if I added at the top of the controller file: `const User = sequelize.models.User;` so that I don't need to replace occurrences such as `User.findByPk(id)` with `sequelize.models.User.findByPk(id)` ? Or do you feel like that's not a good idea? – Nick Jun 29 '22 at 19:40
  • I have also posted a follow-up question on how, with this implementation, to access a model from inside another model file: https://stackoverflow.com/q/72813662/ – Nick Jun 30 '22 at 09:52