0

I am struggling when using sequelize migrations and a many-to-many relationship between Users and Roles.

This is the Users model:

'use strict';
module.exports = (sequelize, DataTypes) => {
  const user = sequelize.define('Users', {
    username: DataTypes.STRING,
    name: DataTypes.STRING,
    email: DataTypes.STRING,
    password: DataTypes.STRING
  }, {});
  user.associate = function(models) {
    // associations can be defined here
    user.belongsToMany(models.Roles, {
      through: models.UserRoles
    });
  };
  return user;
};

This is the Roles model:

'use strict';
module.exports = (sequelize, DataTypes) => {
  const role = sequelize.define('Roles', {
    name: DataTypes.STRING
  }, {});
  role.associate = function(models) {
    // associations can be defined here
    role.belongsToMany(models.Users, {
      through: models.UserRoles
    });
  };
  return role;
};

This is the "create-user" migration:

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      username: {
        type: Sequelize.STRING,
        allowNull: true,
        len: [0, 20]
        },
        name: {
          type: Sequelize.STRING,
          allowNull: true,
        },
        email: {
          type: Sequelize.STRING,
          allowNull: false
        },
        password: {
          type: Sequelize.STRING,
          allowNull: false
        },
        createdAt: {
          allowNull: false,
          type: Sequelize.DATE
        },
        updatedAt: {
          allowNull: false,
          type: Sequelize.DATE
        }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('Users');
  }
};

This is the "create-role" migration:

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('Roles', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      name: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('Roles');
  }
};

This is the userRoles Model:

'use strict';
module.exports = (sequelize, DataTypes) => {
  const user_role = sequelize.define('UserRoles', {
    userId: DataTypes.INTEGER,
    roleId: DataTypes.INTEGER
  }, {});
  user_role.associate = function(models) {
    // associations can be defined here

  };
  return user_role;
};

And last one the "user-roles" migration:

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('UserRoles', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      userId: {
        primaryKey: true,
        type: Sequelize.INTEGER,
        allowNull: false,
        references: {        
          model: 'Users',
          key: 'id'
        }
      },
      roleId: {
        primaryKey: true,
        type: Sequelize.INTEGER,
        allowNull: false,
        references: {         
          model: 'Roles',
          key: 'id'
        }      
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('UserRoles');
  }
};

The problem happens when I try to access to the user.setRoles() from a controller:

exports.signup = (req, res) => {
  console.log('creating new user', req.body.username);
  // Save User to Database
  User.create({
    username: req.body.username,
    email: req.body.email,
    password: bcrypt.hashSync(req.body.password, 8)
  })
    .then(user => {

      console.log('USER ADDED');
      if (req.body.roles) {
        Role.findAll({
          where: {
            name: {
              [Op.or]: req.body.roles
            }
          }
        }).then(roles => {
          console.log('ROLES ', roles);
          user.setRoles(roles).then(() => {
            res.send({ message: "User was registered successfully!" });
          });
        });
      } else {
        console.log('NO ROLES > Normal User');
        // user role = 1
        user.setRoles([1]).then(() => {
          res.send({ message: "User was registered successfully!" });
        });
      }
    })
    .catch(err => {
      console.log('ERROR: ', err);
      res.status(500).send({ message: err.message });
    });
};

When I console.log the user using : console.log(Object.keys(user.__proto__)) I get this array where the special methods haven't been created, any idea what I am doing wrong?

Array(7) ["_customGetters", "_customSetters", "validators", "_hasCustomGetters", "_hasCustomSetters", "rawAttributes", "_isAttribute"]

Many thanks for your help!

vhbazan
  • 1,240
  • 1
  • 17
  • 26
  • set a breakpoint at `user.setRoles(roles)` line and look at `user`. Do you see any functions like addRoles/setRoles in it? Also check `associations` in it. – Anatoly May 10 '20 at 09:31
  • Hi @Anatoly, many thanks for your answer. No, there is no addRoles/setRoles functions in the `user` instance returned but the previous `User.create(..)` promise, that was the first thing I did check. Where I can check the `associations`? – vhbazan May 10 '20 at 10:05
  • look at User at the line User.create – Anatoly May 10 '20 at 10:08
  • `associations` in the User model at the `User.create` line is empty. Is there anything you would like me to check in the User model? – vhbazan May 10 '20 at 14:59
  • it seems you don't register associations via 'associate' functions of models. They must be called after registration of all models. – Anatoly May 10 '20 at 17:19
  • @Anatoly Many thanks for your answer! So, you mean I should call them in the UserRoles associate function? I don't get what you exactly mean when you say "they must be called after registration of all models". – vhbazan May 10 '20 at 18:03
  • 1
    First you get all models by getting them from corresponding JS-modules, second you call 'associate' function for each of them. – Anatoly May 10 '20 at 18:07
  • You are right! I got it ! I thought that when using sequelize migrations you don't need to call those associations. Many many thanks that was my problem. – vhbazan May 10 '20 at 18:11

1 Answers1

0

Just call all associate functions after registering models. For instance:

const models = path.join(__dirname, 'models')
const db = {}

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

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

Anatoly
  • 20,799
  • 3
  • 28
  • 42
  • What do you mean by registering a model? Would this code have to be run every time the application is instantiated? Or would it need to be executed just after a migration happened, in a similar "manual" way where you create a script and just call it so it can reflect on the database? – Gabriel Oliveira Mar 07 '23 at 22:00
  • 1
    By registering models I meant models' registration in Sequelize instance on the app startup and that's it. The tables could be created later but BEFORE you will try to use any models. – Anatoly Mar 08 '23 at 08:36
  • I have tried it and I found out that the models dictionary of sequelize instance is always empty, and as I understand that would be the correct way to call the associations between them. Even so I have tried to import the model classes and running associate on them too, that also did not work so I gave up on using sequelize and started migrating to prisma, where lo and behold, I was able to do all of those things first try in just 20 minutes. I would love to understand whats wrong with my sequelize setup though, but I cant bother right now – Gabriel Oliveira Mar 08 '23 at 19:15
  • 1
    Just look at my answer here https://stackoverflow.com/a/61710568/1376618 to get to know how to register models and their associations. – Anatoly Mar 08 '23 at 20:59