website icon indicating copy to clipboard operation
website copied to clipboard

The naming schema for junction tables is read as pascal casing by default, not camel casing

Open adeeb1 opened this issue 4 years ago • 4 comments

Issue Creation Checklist

Issue Description

What was unclear/insufficient/not covered in the documentation

The written documentation and the code examples are inconsistent in the casing of foreign key column names regarding associations. The documentation shows camel casing, while the examples show pascal casing. The examples are correct.

Example 1

On the many-to-many relationships section under Goal, it states:

The junction table that will keep track of the associations will be called ActorMovies, which will contain the foreign keys movieId and actorId.

The example immediately after shows that it creates the MovieId and ActorId foreign keys, which is correct.

Example 2

On the one-to-many relationships section, it states under the Philosophy section:

For example, if one Foo has many Bars (and this way each Bar belongs to one Foo), then the only sensible implementation is to have a fooId column in the Bar table.

The example immediately after shows the CREATE TABLE command creating a pascal-cased foreign key (TeamId).

Example 3

The naming strategies section indicates that:

Without the underscored option, Sequelize would automatically define:

  • A createdAt attribute for each model, pointing to a column named createdAt in each table
  • An updatedAt attribute for each model, pointing to a column named updatedAt in each table
  • A userId attribute in the Task model, pointing to a column named userId in the task table

However, on the last bullet point, a pascal-cased UserId column would be created in the task table.

If possible: Provide some suggestion on how we can enhance the docs

  • The written documentation should correctly mention that the default behavior will create pascal-cased column names for associations

Additional context

I spent quite a while trying to find the problem with creating models with associations when I found this issue: https://github.com/sequelize/sequelize/issues/13330

In my case, Sequelize was by default attempting to insert values for pascal-cased column names that don't exist. My column names are camel-cased.

adeeb1 avatar Jan 10 '22 17:01 adeeb1

If your attribute is camelCase, underscored is false, and you end up with the column name for that attribute being PascalCase, that's a bug.

What do your models & associations look like?

ephys avatar Jan 11 '22 18:01 ephys

I am using TypeScript and migrations.

User model

class User extends Model {}

User.init({
  name: {
    type: DataTypes.TEXT,
    allowNull: false
  },
  email: {
    type: DataTypes.TEXT,
    allowNull: false,
    unique: true,
    validate: {
      isEmail: true
    },
  },
  password: {
    type: DataTypes.TEXT,
    allowNull: false
  },
  bio: DataTypes.TEXT,
  twitterUrl: DataTypes.TEXT,
  facebookUrl: DataTypes.TEXT,
  instagramUrl: DataTypes.TEXT
},
  {
    sequelize,
    modelName: 'User',
  }
);

Interest model

class Interest extends Model {}

Interest.init({
  name: {
    allowNull: false,
    type: DataTypes.TEXT,
  }
}, {
  sequelize,
  modelName: 'Interest',
});

UserInterest model

class UserInterest extends Model {}

UserInterest.init({
  userId: {
    allowNull: false,
    type: DataTypes.BIGINT,
    validate: {
      isInt: true
    }
  },
  interestId: {
    allowNull: false,
    type: DataTypes.BIGINT,
    validate: {
      isInt: true
    }
  },
}, {
  sequelize,
  modelName: 'UserInterest',
});

Associations (declared in a separate file)

User.belongsToMany(Interest, { through: UserInterest });
Interest.belongsToMany(User, { through: UserInterest });

UserInterest.belongsTo(User);
UserInterest.belongsTo(Interest);

UserInterest migration (can provide other migrations if needed)

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('UserInterests', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      userId: {
        allowNull: false,
        type: Sequelize.BIGINT
      },
      interestId: {
        allowNull: false,
        type: Sequelize.BIGINT
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });

    await queryInterface.addIndex(
      'UserInterests',
      {
        fields: ['userId', 'interestId'],
        unique: true
      }
    );
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('UserInterests');
  }
};

Code that causes an error:

const userInterest = await UserInterest.create({
  userId: 111,
  interestId: 222
});

// => INSERT INTO "UserInterests" ("userId","interestId","createdAt","updatedAt", "UserId", "InterestId") VALUES (111,222,'2022-01-11 16:59:21.817 +00:00','2022-01-11 16:59:21.817 +00:00') RETURNING "userId","interestId","createdAt","updatedAt","UserId","InterestId";

adeeb1 avatar Jan 11 '22 18:01 adeeb1

You set modelName to User, in the documentation modelName is user.

This is the reason why your generated column was called UserId and not userId.

We can fix the documentation by specifying that the attribute name will be generated using modelName + 'Id' (PR welcome).

Question is: do we want to lowercase UserId to enforce camelCase instead of PascalCase for the attribute/column, even when the modelName is PascalCased.

ephys avatar Jan 11 '22 18:01 ephys

You set modelName to User, in the documentation modelName is user.

Thank you for the clarification. This helps a lot, and I didn't notice it because other examples in the documentation use User as the model name.

We can fix the documentation by specifying that the attribute name will be generated using modelName + 'Id' (PR welcome).

This would be really helpful. Since JavaScript attributes usually follow camel casing, I didn't expect pascal casing to be specified for the column name.

Question is: do we want to lowercase UserId to enforce camelCase instead of PascalCase for the attribute/column, even when the modelName is PascalCased.

It doesn't seem many others have had a big problem with the current behavior. Perhaps we can start with updating the documentation. Personally, I manage my database columns through migrations, so I don't expect the model to do anything with regards to setting up column names.

adeeb1 avatar Jan 11 '22 18:01 adeeb1