The naming schema for junction tables is read as pascal casing by default, not camel casing
Issue Creation Checklist
- [X] I have read the contribution guidelines
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.
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?
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";
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.
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
UserIdto 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.