Skip to content

Commit 2b1657c

Browse files
sushantdhimanjanmeier
authored andcommittedMay 28, 2016
Task 5267, Make all association to prefer as their foreign key name (sequelize#5957)
* sync behaviour of as for all association type * changelog and docs for sequelize#5267 * fixed N:M failing test
1 parent 5baafc5 commit 2b1657c

13 files changed

+99
-39
lines changed
 

‎changelog.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- [CHANGED] `instance.equals` now only checks primary keys, instead of all attributes.
1818
- [REWRITE] Rewrite model and instance to a single class - instance instanceof Model [#5924](https://github.com/sequelize/sequelize/issues/5924)
1919
- [REMOVED] Counter cache plugin
20+
- [FIXED] All associations now prefer aliases to construct foreign key [#5267](https://github.com/sequelize/sequelize/issues/5267)
2021

2122
## BC breaks:
2223
- `hookValidate` removed in favor of `validate` with `hooks: true | false`. `validate` returns a promise which is rejected if validation fails
@@ -28,10 +29,10 @@
2829
- Removed support for `pool:false`, if you still want to use single connection set `pool.max` to `1`
2930
- Removed default `REPEATABLE_READ` transaction isolation, use config option to explicitly set it
3031
- Removed MariaDB dialect - this was just a thin wrapper around MySQL, so using `dialect: 'mysql'` instead should work with no further changes
31-
- `hasOne` now prefer `as` option to generate foreign key name, otherwise it defaults to source model name
3232
- `instance.equals` now provides reference equality (do two instances refer to the same row, i.e. are their primary key(s) equal). Use `instance.get()` to get and compare all values.
3333
- Instances (database rows) are now instances of the model, instead of being a separate class. This means you can replace User.build() with new User() and sequelize.define with User extends Sequelize.Model. See #5924
3434
- The counter cache plugin, and consequently the `counterCache` option for associations has been removed. The plugin is seeking a new maintainer - You can find the code [here](https://github.com/sequelize/sequelize/blob/aace1250dfa8cd81a4edfd2086c9058b513f6ee0/lib/plugins/counter-cache.js)
35+
- All associations type will prefer `as` when constructing the `foreignKey` name. You can override this by `foreignKey` option.
3536

3637
# 3.23.2
3738
- [FIXED] Type validation now works with non-strings due to updated validator@5.0.0 [#5861](https://github.com/sequelize/sequelize/pull/5861)

‎docs/docs/associations.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ var Project = sequelize.define('project', {/* ... */})
189189
Project.hasMany(User, {as: 'Workers'})
190190
```
191191

192-
This will add the attribute `projectId` or `project_id` to User. Instances of Project will get the accessors getWorkers and setWorkers. We could just leave it the way it is and let it be a one-way association.
192+
This will add the attribute `WorkersId` or `Workers_id` to User. Instances of Project will get the accessors getWorkers and setWorkers. We could just leave it the way it is and let it be a one-way association.
193193
But we want more! Let's define it the other way around by creating a many to many association in the next section:
194194

195195
## Belongs-To-Many associations

‎lib/associations/belongs-to-many.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ var BelongsToMany = function(source, target, options) {
112112
this.foreignKeyAttribute = {};
113113
this.foreignKey = this.options.foreignKey || Utils.camelizeIf(
114114
[
115-
Utils.underscoredIf(this.source.options.name.singular, this.source.options.underscored),
115+
Utils.underscoredIf(this.options.as || this.source.options.name.singular, this.source.options.underscored),
116116
this.source.primaryKeyAttribute
117117
].join('_'),
118118
!this.source.options.underscored

‎lib/associations/belongs-to.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ var BelongsTo = function(source, target, options) {
4747
if (!this.foreignKey) {
4848
this.foreignKey = Utils.camelizeIf(
4949
[
50-
Utils.underscoredIf(this.as, this.source.options.underscored),
50+
Utils.underscoredIf(this.options.as || this.as, this.source.options.underscored),
5151
this.target.primaryKeyAttribute
5252
].join('_'),
5353
!this.source.options.underscored

‎lib/associations/has-many.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ var HasMany = function(source, target, options) {
7171
if (!this.foreignKey) {
7272
this.foreignKey = Utils.camelizeIf(
7373
[
74-
Utils.underscoredIf(this.source.options.name.singular, this.source.options.underscored),
74+
Utils.underscoredIf(this.options.as || this.source.options.name.singular, this.source.options.underscored),
7575
this.source.primaryKeyAttribute
7676
].join('_'),
7777
!this.source.options.underscored

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

+31-19
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,14 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
470470
}
471471
});
472472

473-
return expect(this.user.countActiveTasks({})).to.eventually.equal(1);
473+
var self = this;
474+
return this.UserTask.sync({ force: true })
475+
.then(function() {
476+
return self.user.setActiveTasks(self.tasks);
477+
})
478+
.then(function() {
479+
return expect(self.user.countActiveTasks({})).to.eventually.equal(1);
480+
});
474481
});
475482

476483
it('should count scoped through associations', function () {
@@ -486,18 +493,23 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
486493
}
487494
});
488495

489-
return Promise.join(
490-
this.Task.create().then(function (task) {
491-
return user.addTask(task, {
492-
started: true
493-
});
494-
}),
495-
this.Task.create().then(function (task) {
496-
return user.addTask(task, {
497-
started: true
498-
});
499-
})
500-
).then(function () {
496+
return this.UserTask.sync({ force: true})
497+
.bind(this)
498+
.then(function() {
499+
return Promise.join(
500+
this.Task.create().then(function (task) {
501+
return user.addStartedTask(task, {
502+
started: true
503+
});
504+
}),
505+
this.Task.create().then(function (task) {
506+
return user.addStartedTask(task, {
507+
started: true
508+
});
509+
})
510+
);
511+
})
512+
.then(function () {
501513
return expect(user.countStartedTasks({})).to.eventually.equal(2);
502514
});
503515
});
@@ -1825,8 +1837,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
18251837
Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user'});
18261838

18271839
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
1828-
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId).to.exist;
1829-
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId).to.exist;
1840+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId).to.exist;
1841+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId).to.exist;
18301842
});
18311843

18321844
it('correctly identifies its counterpart when through is a model', function() {
@@ -1839,8 +1851,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
18391851

18401852
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
18411853

1842-
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId).to.exist;
1843-
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId).to.exist;
1854+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId).to.exist;
1855+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId).to.exist;
18441856
});
18451857
});
18461858

@@ -2146,7 +2158,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
21462158

21472159
Children = Person.belongsToMany(Person, { as: 'Children', through: PersonChildren});
21482160

2149-
expect(Children.foreignKey).to.equal('PersonId');
2161+
expect(Children.foreignKey).to.equal('ChildrenId');
21502162
expect(Children.otherKey).to.equal('ChildId');
21512163
expect(PersonChildren.rawAttributes[Children.foreignKey]).to.be.ok;
21522164
expect(PersonChildren.rawAttributes[Children.otherKey]).to.be.ok;
@@ -2156,7 +2168,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
21562168
PersonChildren = this.sequelize.define('PersonChildren', {}, {underscored: true});
21572169
Children = Person.belongsToMany(Person, { as: 'Children', through: PersonChildren});
21582170

2159-
expect(Children.foreignKey).to.equal('person_id');
2171+
expect(Children.foreignKey).to.equal('children_id');
21602172
expect(Children.otherKey).to.equal('child_id');
21612173
expect(PersonChildren.rawAttributes[Children.foreignKey]).to.be.ok;
21622174
expect(PersonChildren.rawAttributes[Children.otherKey]).to.be.ok;

‎test/integration/associations/scope.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ describe(Support.getTestDialectTeaser('associations'), function() {
261261
this.PostTag = this.sequelize.define('post_tag');
262262

263263
this.Tag.belongsToMany(this.Post, {through: this.PostTag});
264-
this.Post.belongsToMany(this.Tag, {as: 'categories', through: this.PostTag, scope: { type: 'category' }});
265-
this.Post.belongsToMany(this.Tag, {as: 'tags', through: this.PostTag, scope: { type: 'tag' }});
264+
this.Post.belongsToMany(this.Tag, {as: 'categories', through: this.PostTag, foreignKey: 'PostId', scope: { type: 'category' }});
265+
this.Post.belongsToMany(this.Tag, {as: 'tags', through: this.PostTag, foreignKey: 'PostId', scope: { type: 'tag' }});
266266
});
267267

268268
it('should create, find and include associations with scope values', function() {

‎test/integration/include/findAll.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,8 @@ describe(Support.getTestDialectTeaser('Include'), function() {
405405
]).spread(function(user, products) {
406406
return Promise.all([
407407
GroupMember.bulkCreate([
408-
{UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id},
409-
{UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id}
408+
{MembershipsId: user.id, GroupId: groups[0].id, RankId: ranks[0].id},
409+
{MembershipsId: user.id, GroupId: groups[1].id, RankId: ranks[1].id}
410410
]),
411411
user.setProducts([
412412
products[(i * 2) + 0],

‎test/integration/include/schema.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), function() {
276276
]).spread(function(user, products) {
277277
return Promise.all([
278278
GroupMember.bulkCreate([
279-
{AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id},
280-
{AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id}
279+
{MembershipsId: user.id, GroupId: groups[0].id, RankId: ranks[0].id},
280+
{MembershipsId: user.id, GroupId: groups[1].id, RankId: ranks[1].id}
281281
]),
282282
user.setProducts([
283283
products[(i * 2) + 0],

‎test/integration/model/scope/associations.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
9696
this.ScopeMe.hasOne(this.Profile, { foreignKey: 'userId' });
9797

9898
this.ScopeMe.belongsTo(this.Company);
99-
this.UserAssociation = this.Company.hasMany(this.ScopeMe, { as: 'users' });
99+
this.UserAssociation = this.Company.hasMany(this.ScopeMe, { as: 'users', foreignKey: 'companyId'});
100100

101101
return this.sequelize.sync({force: true}).bind(this).then(function() {
102102
return Promise.all([

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

+23-8
Original file line numberDiff line numberDiff line change
@@ -512,10 +512,10 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() {
512512
Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user', onUpdate: 'SET NULL', onDelete: 'RESTRICT' });
513513

514514
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
515-
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onUpdate).to.equal('RESTRICT');
516-
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onDelete).to.equal('SET NULL');
517-
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onUpdate).to.equal('SET NULL');
518-
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onDelete).to.equal('RESTRICT');
515+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId.onUpdate).to.equal('RESTRICT');
516+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId.onDelete).to.equal('SET NULL');
517+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId.onUpdate).to.equal('SET NULL');
518+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId.onDelete).to.equal('RESTRICT');
519519
});
520520

521521
it('work properly when through is a model', function() {
@@ -527,10 +527,25 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() {
527527
Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup, onUpdate: 'SET NULL', onDelete: 'RESTRICT' });
528528

529529
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
530-
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onUpdate).to.equal('RESTRICT');
531-
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onDelete).to.equal('SET NULL');
532-
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onUpdate).to.equal('SET NULL');
533-
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onDelete).to.equal('RESTRICT');
530+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId.onUpdate).to.equal('RESTRICT');
531+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId.onDelete).to.equal('SET NULL');
532+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId.onUpdate).to.equal('SET NULL');
533+
expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId.onDelete).to.equal('RESTRICT');
534534
});
535535
});
536+
537+
it('properly use the `as` key to generate foreign key name', function(){
538+
var City = current.define('City', { cityname: DataTypes.STRING })
539+
, State = current.define('State', { statename: DataTypes.STRING })
540+
, CityStateMap = current.define('CityStateMap', { rating: DataTypes.STRING });
541+
542+
State.belongsToMany(City, { through: CityStateMap });
543+
expect(CityStateMap.attributes.StateId).not.to.be.empty;
544+
expect(CityStateMap.attributes.CityId).not.to.be.empty;
545+
546+
State.belongsToMany(City, { through: CityStateMap, as: 'Test'});
547+
expect(CityStateMap.attributes.TestId).not.to.be.empty;
548+
expect(CityStateMap.attributes.CityId).not.to.be.empty;
549+
550+
});
536551
});
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
/* jshint -W030 */
4+
var chai = require('chai')
5+
, expect = chai.expect
6+
, Support = require(__dirname + '/../support')
7+
, DataTypes = require(__dirname + '/../../../lib/data-types')
8+
, current = Support.sequelize;
9+
10+
describe(Support.getTestDialectTeaser('hasOne'), function() {
11+
it('properly use the `as` key to generate foreign key name', function(){
12+
var User = current.define('User', { username: DataTypes.STRING })
13+
, Task = current.define('Task', { title: DataTypes.STRING });
14+
15+
User.belongsTo(Task);
16+
expect(User.attributes.TaskId).not.to.be.empty;
17+
18+
User.belongsTo(Task, {as : 'Naam'});
19+
expect(User.attributes.NaamId).not.to.be.empty;
20+
});
21+
});

‎test/unit/associations/has-many.test.js

+11
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,15 @@ describe(Support.getTestDialectTeaser('hasMany'), function() {
162162
});
163163
});
164164
});
165+
166+
it('properly use the `as` key to generate foreign key name', function(){
167+
var City = current.define('City', { cityname: DataTypes.STRING })
168+
, State = current.define('State', { statename: DataTypes.STRING });
169+
170+
State.hasMany(City);
171+
expect(City.attributes.StateId).not.to.be.empty;
172+
173+
State.hasMany(City, {as : 'Capital'});
174+
expect(City.attributes.CapitalId).not.to.be.empty;
175+
});
165176
});

0 commit comments

Comments
 (0)
Please sign in to comment.