sequelize / cli

The Sequelize CLI
MIT License
2.53k stars 526 forks source link

Junction table is not being auto created when using belongsToMany + Fix Suggestion #945

Open listguy opened 3 years ago

listguy commented 3 years ago

What you are doing?

Trying to create a Many to Many associastion between Team and Player using belongsToMany.

create-player migration:

'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Players', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      first_name: {
        type: Sequelize.STRING
      },
      last_name: {
        type: Sequelize.STRING
      },
      shirt_number: {
        type: Sequelize.INTEGER
      },
      team_id: {
        type: Sequelize.INTEGER
      },
      created_at: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Players');
  }
};

create-team migration

"use strict";
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable("Teams", {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER,
      },
      name: {
        type: Sequelize.STRING,
      },
      year_founded: {
        type: Sequelize.INTEGER,
      },
      country: {
        type: Sequelize.STRING,
      },
      created_at: {
        allowNull: false,
        type: Sequelize.DATE,
      },
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE,
      },
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable("Teams");
  },
};

Player model

"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class Player extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
      this.belongsToMany(models.Team, { through: "PlayerTeams" });
    }
  }
  Player.init(
    {
      first_name: DataTypes.STRING,
      last_name: DataTypes.STRING,
      shirt_number: DataTypes.INTEGER,
      team_id: DataTypes.INTEGER,
    },
    {
      sequelize,
      modelName: "Player",
      tableName: "Players",
    }
  );
  return Player;
};

Team model

"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class Team extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
      this.hasOne(models.Manager, {
        foreignKey: "team_id",
      });
      this.belongsToMany(models.Player, { through: "PlayerTeams" });
    }
  }
  Team.init(
    {
      name: DataTypes.STRING,
      year_founded: DataTypes.INTEGER,
      country: DataTypes.STRING,
    },
    {
      sequelize,
      modelName: "Team",
      tableName: "Teams",
    }
  );
  return Team;
};

index.js

const { Team,} = require("./models");

Team.findOne({}).then(async (team) => {
  console.log(team.toJSON());
  const players = await team.getPlayers();
  console.log(players.map((player) => player.toJSON()));
});

config.json

{
  "development": {
    "username": "root",
    "password": "password",
    "database": "orm_day2",
    "host": "127.0.0.1",
    "dialect": "mysql",
    "port": 3307,
    "seederStorage": "sequelize",
    "define": {
      "underscored": true,
      "underscoredAll": true,
      "updatedAt": "updated_at",
      "createdAt": "created_at"
    },
    "logging": false
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

What do you expect to happen?

A PlayerTeams table to be created automatically and the Many to Many association to work when querying in index.js.

What is actually happening?

Getting an error that the junction table does not exist (was not created)

Output, either JSON or SQL (The error message)

(node:1737) UnhandledPromiseRejectionWarning: SequelizeDatabaseError: Table 'orm_day2.PlayerTeams' doesn't exist
    at Query.formatError (/home/nitzo/Documents/Cyber4s 2/f4s-2021-material/ORM/day-2/node_modules/sequelize/lib/dialects/mysql/query.js:265:16)
    at Query.run (/home/nitzo/Documents/Cyber4s 2/f4s-2021-material/ORM/day-2/node_modules/sequelize/lib/dialects/mysql/query.js:77:18)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async /home/nitzo/Documents/Cyber4s 2/f4s-2021-material/ORM/day-2/node_modules/sequelize/lib/sequelize.js:619:16
    at async MySQLQueryInterface.select (/home/nitzo/Documents/Cyber4s 2/f4s-2021-material/ORM/day-2/node_modules/sequelize/lib/dialects/abstract/query-interface.js:953:12)
    at async Function.findAll (/home/nitzo/Documents/Cyber4s 2/f4s-2021-material/ORM/day-2/node_modules/sequelize/lib/model.js:1753:21)
    at async /home/nitzo/Documents/Cyber4s 2/f4s-2021-material/ORM/day-2/index.js:15:19
(Use `node --trace-warnings ...` to show where the warning was created)

Ps.

Adding sequelize.sync() to models/index.js solved it.

I couldn't find the solution online, I was just lucky trying it out.

But I was wandering - Is there something wrong in my setup? And if not, I think you should add this line to your default configuration of models/index.js (when running npx sequelize init).

Otherwise people can't use the full power of sequelize associations when bootstrapping a project with the default configurations you provide ☹

Dialect: mysql Database version: 8.0.23 Sequelize CLI version: 6.2.0 Sequelize version: 6.6.2

wmantly commented 9 months ago

Adding sequelize.sync() to models/index.js solved it.

This is an awful way to solve this problem. The running app should not mess with the DB/schema.

A much more correct way to deal with this is to manually create the model(or just a migration file for it) and allow the migration script to do proper migrations.

Sample migration for the though table


'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('GroupMembers', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      GroupName: {
        allowNull: false,
        type: Sequelize.STRING
      },
      UserUsername: {
        allowNull: false,
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('GroupMembers');
  }
};