Skip to content

Commit 43ab707

Browse files
RobertYoungsushantdhiman
authored andcommitted
feat(associations): enable overwrite unique constraint key name (#10045)
1 parent 8fe475b commit 43ab707

File tree

6 files changed

+105
-2
lines changed

6 files changed

+105
-2
lines changed

docs/associations.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,11 @@ User.findAll({
285285
}]
286286
});
287287
```
288+
Belongs-To-Many creates a unique key when primary key is not present on through model. This unique key name can be overridden using **uniqueKey** option.
289+
290+
```js
291+
Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' })
292+
```
288293

289294
## Scopes
290295
This section concerns association scopes. For a definition of association scopes vs. scopes on associated models, see [Scopes](/manual/tutorial/scopes.html).

lib/associations/belongs-to-many.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,12 @@ class BelongsToMany extends Association {
259259
if (this.primaryKeyDeleted === true) {
260260
targetAttribute.primaryKey = sourceAttribute.primaryKey = true;
261261
} else if (this.through.unique !== false) {
262-
const uniqueKey = [this.through.model.tableName, this.foreignKey, this.otherKey, 'unique'].join('_');
262+
let uniqueKey;
263+
if (typeof this.options.uniqueKey === 'string' && this.options.uniqueKey !== '') {
264+
uniqueKey = this.options.uniqueKey;
265+
} else {
266+
uniqueKey = [this.through.model.tableName, this.foreignKey, this.otherKey, 'unique'].join('_');
267+
}
263268
targetAttribute.unique = sourceAttribute.unique = uniqueKey;
264269
}
265270

lib/model.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3212,6 +3212,7 @@ class Model {
32123212
*/
32133213
setDataValue(key, value) {
32143214
const originalValue = this._previousDataValues[key];
3215+
32153216
if (!Utils.isPrimitive(value) || value !== originalValue) {
32163217
this.changed(key, true);
32173218
}
@@ -4177,6 +4178,7 @@ class Model {
41774178
* @param {string} [options.onDelete='SET NULL|CASCADE'] SET NULL if foreignKey allows nulls, CASCADE if otherwise
41784179
* @param {string} [options.onUpdate='CASCADE']
41794180
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
4181+
* @param {string} [options.uniqueKey] The custom name for unique constraint.
41804182
* @returns {HasOne}
41814183
* @example
41824184
* User.hasOne(Profile) // This will add userId to the profile table

test/integration/associations/belongs-to-many.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,6 +2157,35 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => {
21572157
expect(ut2).to.have.length(1);
21582158
});
21592159
});
2160+
2161+
it('create custom unique identifier', function() {
2162+
this.UserTasksLong = this.sequelize.define('table_user_task_with_very_long_name', {
2163+
id_user_very_long_field: {
2164+
type: DataTypes.INTEGER(1)
2165+
},
2166+
id_task_very_long_field: {
2167+
type: DataTypes.INTEGER(1)
2168+
}
2169+
},
2170+
{ tableName: 'table_user_task_with_very_long_name' }
2171+
);
2172+
this.User.belongsToMany(this.Task, {
2173+
as: 'MyTasks',
2174+
through: this.UserTasksLong,
2175+
foreignKey: 'id_user_very_long_field'
2176+
});
2177+
this.Task.belongsToMany(this.User, {
2178+
as: 'MyUsers',
2179+
through: this.UserTasksLong,
2180+
foreignKey: 'id_task_very_long_field',
2181+
uniqueKey: 'custom_user_group_unique'
2182+
});
2183+
2184+
return this.sequelize.sync({ force: true }).then(() => {
2185+
expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('custom_user_group_unique');
2186+
expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_task_very_long_field.unique).to.equal('custom_user_group_unique');
2187+
});
2188+
});
21602189
});
21612190

21622191
describe('Association options', () => {

test/integration/model/upsert.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
214214
if (dialect === 'sqlite') {
215215
expect(created).to.be.undefined;
216216
} else {
217-
expect(created).to.be.okay;
217+
expect(created).to.be.ok;
218218
}
219219
});
220220
});

test/unit/associations/belongs-to-many.test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,5 +585,67 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => {
585585
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onUpdate).to.equal('SET NULL');
586586
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onDelete).to.equal('RESTRICT');
587587
});
588+
589+
it('generate unique identifier with very long length', function() {
590+
const User = this.sequelize.define('User', {}, { tableName: 'table_user_with_very_long_name' }),
591+
Group = this.sequelize.define('Group', {}, { tableName: 'table_group_with_very_long_name' }),
592+
UserGroup = this.sequelize.define(
593+
'GroupUser',
594+
{
595+
id_user_very_long_field: {
596+
type: DataTypes.INTEGER(1)
597+
},
598+
id_group_very_long_field: {
599+
type: DataTypes.INTEGER(1)
600+
}
601+
},
602+
{tableName: 'table_user_group_with_very_long_name'}
603+
);
604+
605+
User.belongsToMany(Group, { as: 'MyGroups', through: UserGroup, foreignKey: 'id_user_very_long_field' });
606+
Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup, foreignKey: 'id_group_very_long_field' });
607+
608+
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
609+
expect(Group.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.have.lengthOf(92);
610+
expect(Group.associations.MyUsers.through.model.rawAttributes.id_group_very_long_field.unique).to.have.lengthOf(92);
611+
expect(Group.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('table_user_group_with_very_long_name_id_group_very_long_field_id_user_very_long_field_unique');
612+
expect(Group.associations.MyUsers.through.model.rawAttributes.id_group_very_long_field.unique).to.equal('table_user_group_with_very_long_name_id_group_very_long_field_id_user_very_long_field_unique');
613+
});
614+
615+
it('generate unique identifier with custom name', function() {
616+
const User = this.sequelize.define('User', {}, { tableName: 'table_user_with_very_long_name' }),
617+
Group = this.sequelize.define('Group', {}, { tableName: 'table_group_with_very_long_name' }),
618+
UserGroup = this.sequelize.define(
619+
'GroupUser',
620+
{
621+
id_user_very_long_field: {
622+
type: DataTypes.INTEGER(1)
623+
},
624+
id_group_very_long_field: {
625+
type: DataTypes.INTEGER(1)
626+
}
627+
},
628+
{tableName: 'table_user_group_with_very_long_name'}
629+
);
630+
631+
User.belongsToMany(Group, {
632+
as: 'MyGroups',
633+
through: UserGroup,
634+
foreignKey: 'id_user_very_long_field',
635+
uniqueKey: 'custom_user_group_unique'
636+
});
637+
Group.belongsToMany(User, {
638+
as: 'MyUsers',
639+
through: UserGroup,
640+
foreignKey: 'id_group_very_long_field',
641+
uniqueKey: 'custom_user_group_unique'
642+
});
643+
644+
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
645+
expect(Group.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.have.lengthOf(24);
646+
expect(Group.associations.MyUsers.through.model.rawAttributes.id_group_very_long_field.unique).to.have.lengthOf(24);
647+
expect(Group.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('custom_user_group_unique');
648+
expect(Group.associations.MyUsers.through.model.rawAttributes.id_group_very_long_field.unique).to.equal('custom_user_group_unique');
649+
});
588650
});
589651
});

0 commit comments

Comments
 (0)