不要怂,就是干,撸起袖子干!

Commit 2b1657cc by Sushant Committed by Jan Aagaard Meier

Task 5267, Make all association to prefer `as` their foreign key name (#5957)

* sync behaviour of as for all association type

* changelog and docs for #5267

* fixed N:M failing test
1 parent 5baafc53
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
- [CHANGED] `instance.equals` now only checks primary keys, instead of all attributes. - [CHANGED] `instance.equals` now only checks primary keys, instead of all attributes.
- [REWRITE] Rewrite model and instance to a single class - instance instanceof Model [#5924](https://github.com/sequelize/sequelize/issues/5924) - [REWRITE] Rewrite model and instance to a single class - instance instanceof Model [#5924](https://github.com/sequelize/sequelize/issues/5924)
- [REMOVED] Counter cache plugin - [REMOVED] Counter cache plugin
- [FIXED] All associations now prefer aliases to construct foreign key [#5267](https://github.com/sequelize/sequelize/issues/5267)
## BC breaks: ## BC breaks:
- `hookValidate` removed in favor of `validate` with `hooks: true | false`. `validate` returns a promise which is rejected if validation fails - `hookValidate` removed in favor of `validate` with `hooks: true | false`. `validate` returns a promise which is rejected if validation fails
...@@ -28,10 +29,10 @@ ...@@ -28,10 +29,10 @@
- Removed support for `pool:false`, if you still want to use single connection set `pool.max` to `1` - Removed support for `pool:false`, if you still want to use single connection set `pool.max` to `1`
- Removed default `REPEATABLE_READ` transaction isolation, use config option to explicitly set it - Removed default `REPEATABLE_READ` transaction isolation, use config option to explicitly set it
- Removed MariaDB dialect - this was just a thin wrapper around MySQL, so using `dialect: 'mysql'` instead should work with no further changes - Removed MariaDB dialect - this was just a thin wrapper around MySQL, so using `dialect: 'mysql'` instead should work with no further changes
- `hasOne` now prefer `as` option to generate foreign key name, otherwise it defaults to source model name
- `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. - `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.
- 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 - 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
- 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) - 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)
- All associations type will prefer `as` when constructing the `foreignKey` name. You can override this by `foreignKey` option.
# 3.23.2 # 3.23.2
- [FIXED] Type validation now works with non-strings due to updated validator@5.0.0 [#5861](https://github.com/sequelize/sequelize/pull/5861) - [FIXED] Type validation now works with non-strings due to updated validator@5.0.0 [#5861](https://github.com/sequelize/sequelize/pull/5861)
......
...@@ -189,7 +189,7 @@ var Project = sequelize.define('project', {/* ... */}) ...@@ -189,7 +189,7 @@ var Project = sequelize.define('project', {/* ... */})
Project.hasMany(User, {as: 'Workers'}) Project.hasMany(User, {as: 'Workers'})
``` ```
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. 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.
But we want more! Let's define it the other way around by creating a many to many association in the next section: But we want more! Let's define it the other way around by creating a many to many association in the next section:
## Belongs-To-Many associations ## Belongs-To-Many associations
......
...@@ -112,7 +112,7 @@ var BelongsToMany = function(source, target, options) { ...@@ -112,7 +112,7 @@ var BelongsToMany = function(source, target, options) {
this.foreignKeyAttribute = {}; this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey || Utils.camelizeIf( this.foreignKey = this.options.foreignKey || Utils.camelizeIf(
[ [
Utils.underscoredIf(this.source.options.name.singular, this.source.options.underscored), Utils.underscoredIf(this.options.as || this.source.options.name.singular, this.source.options.underscored),
this.source.primaryKeyAttribute this.source.primaryKeyAttribute
].join('_'), ].join('_'),
!this.source.options.underscored !this.source.options.underscored
......
...@@ -47,7 +47,7 @@ var BelongsTo = function(source, target, options) { ...@@ -47,7 +47,7 @@ var BelongsTo = function(source, target, options) {
if (!this.foreignKey) { if (!this.foreignKey) {
this.foreignKey = Utils.camelizeIf( this.foreignKey = Utils.camelizeIf(
[ [
Utils.underscoredIf(this.as, this.source.options.underscored), Utils.underscoredIf(this.options.as || this.as, this.source.options.underscored),
this.target.primaryKeyAttribute this.target.primaryKeyAttribute
].join('_'), ].join('_'),
!this.source.options.underscored !this.source.options.underscored
......
...@@ -71,7 +71,7 @@ var HasMany = function(source, target, options) { ...@@ -71,7 +71,7 @@ var HasMany = function(source, target, options) {
if (!this.foreignKey) { if (!this.foreignKey) {
this.foreignKey = Utils.camelizeIf( this.foreignKey = Utils.camelizeIf(
[ [
Utils.underscoredIf(this.source.options.name.singular, this.source.options.underscored), Utils.underscoredIf(this.options.as || this.source.options.name.singular, this.source.options.underscored),
this.source.primaryKeyAttribute this.source.primaryKeyAttribute
].join('_'), ].join('_'),
!this.source.options.underscored !this.source.options.underscored
......
...@@ -470,7 +470,14 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() { ...@@ -470,7 +470,14 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
} }
}); });
return expect(this.user.countActiveTasks({})).to.eventually.equal(1); var self = this;
return this.UserTask.sync({ force: true })
.then(function() {
return self.user.setActiveTasks(self.tasks);
})
.then(function() {
return expect(self.user.countActiveTasks({})).to.eventually.equal(1);
});
}); });
it('should count scoped through associations', function () { it('should count scoped through associations', function () {
...@@ -486,18 +493,23 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() { ...@@ -486,18 +493,23 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
} }
}); });
return Promise.join( return this.UserTask.sync({ force: true})
this.Task.create().then(function (task) { .bind(this)
return user.addTask(task, { .then(function() {
started: true return Promise.join(
}); this.Task.create().then(function (task) {
}), return user.addStartedTask(task, {
this.Task.create().then(function (task) { started: true
return user.addTask(task, { });
started: true }),
}); this.Task.create().then(function (task) {
}) return user.addStartedTask(task, {
).then(function () { started: true
});
})
);
})
.then(function () {
return expect(user.countStartedTasks({})).to.eventually.equal(2); return expect(user.countStartedTasks({})).to.eventually.equal(2);
}); });
}); });
...@@ -1825,8 +1837,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() { ...@@ -1825,8 +1837,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user'}); Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user'});
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId).to.exist; expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId).to.exist;
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId).to.exist; expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId).to.exist;
}); });
it('correctly identifies its counterpart when through is a model', function() { it('correctly identifies its counterpart when through is a model', function() {
...@@ -1839,8 +1851,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() { ...@@ -1839,8 +1851,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId).to.exist; expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId).to.exist;
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId).to.exist; expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId).to.exist;
}); });
}); });
...@@ -2146,7 +2158,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() { ...@@ -2146,7 +2158,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
Children = Person.belongsToMany(Person, { as: 'Children', through: PersonChildren}); Children = Person.belongsToMany(Person, { as: 'Children', through: PersonChildren});
expect(Children.foreignKey).to.equal('PersonId'); expect(Children.foreignKey).to.equal('ChildrenId');
expect(Children.otherKey).to.equal('ChildId'); expect(Children.otherKey).to.equal('ChildId');
expect(PersonChildren.rawAttributes[Children.foreignKey]).to.be.ok; expect(PersonChildren.rawAttributes[Children.foreignKey]).to.be.ok;
expect(PersonChildren.rawAttributes[Children.otherKey]).to.be.ok; expect(PersonChildren.rawAttributes[Children.otherKey]).to.be.ok;
...@@ -2156,7 +2168,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() { ...@@ -2156,7 +2168,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
PersonChildren = this.sequelize.define('PersonChildren', {}, {underscored: true}); PersonChildren = this.sequelize.define('PersonChildren', {}, {underscored: true});
Children = Person.belongsToMany(Person, { as: 'Children', through: PersonChildren}); Children = Person.belongsToMany(Person, { as: 'Children', through: PersonChildren});
expect(Children.foreignKey).to.equal('person_id'); expect(Children.foreignKey).to.equal('children_id');
expect(Children.otherKey).to.equal('child_id'); expect(Children.otherKey).to.equal('child_id');
expect(PersonChildren.rawAttributes[Children.foreignKey]).to.be.ok; expect(PersonChildren.rawAttributes[Children.foreignKey]).to.be.ok;
expect(PersonChildren.rawAttributes[Children.otherKey]).to.be.ok; expect(PersonChildren.rawAttributes[Children.otherKey]).to.be.ok;
......
...@@ -261,8 +261,8 @@ describe(Support.getTestDialectTeaser('associations'), function() { ...@@ -261,8 +261,8 @@ describe(Support.getTestDialectTeaser('associations'), function() {
this.PostTag = this.sequelize.define('post_tag'); this.PostTag = this.sequelize.define('post_tag');
this.Tag.belongsToMany(this.Post, {through: this.PostTag}); this.Tag.belongsToMany(this.Post, {through: this.PostTag});
this.Post.belongsToMany(this.Tag, {as: 'categories', through: this.PostTag, scope: { type: 'category' }}); this.Post.belongsToMany(this.Tag, {as: 'categories', through: this.PostTag, foreignKey: 'PostId', scope: { type: 'category' }});
this.Post.belongsToMany(this.Tag, {as: 'tags', through: this.PostTag, scope: { type: 'tag' }}); this.Post.belongsToMany(this.Tag, {as: 'tags', through: this.PostTag, foreignKey: 'PostId', scope: { type: 'tag' }});
}); });
it('should create, find and include associations with scope values', function() { it('should create, find and include associations with scope values', function() {
......
...@@ -405,8 +405,8 @@ describe(Support.getTestDialectTeaser('Include'), function() { ...@@ -405,8 +405,8 @@ describe(Support.getTestDialectTeaser('Include'), function() {
]).spread(function(user, products) { ]).spread(function(user, products) {
return Promise.all([ return Promise.all([
GroupMember.bulkCreate([ GroupMember.bulkCreate([
{UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id}, {MembershipsId: user.id, GroupId: groups[0].id, RankId: ranks[0].id},
{UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id} {MembershipsId: user.id, GroupId: groups[1].id, RankId: ranks[1].id}
]), ]),
user.setProducts([ user.setProducts([
products[(i * 2) + 0], products[(i * 2) + 0],
......
...@@ -276,8 +276,8 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), function() { ...@@ -276,8 +276,8 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), function() {
]).spread(function(user, products) { ]).spread(function(user, products) {
return Promise.all([ return Promise.all([
GroupMember.bulkCreate([ GroupMember.bulkCreate([
{AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id}, {MembershipsId: user.id, GroupId: groups[0].id, RankId: ranks[0].id},
{AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id} {MembershipsId: user.id, GroupId: groups[1].id, RankId: ranks[1].id}
]), ]),
user.setProducts([ user.setProducts([
products[(i * 2) + 0], products[(i * 2) + 0],
......
...@@ -96,7 +96,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -96,7 +96,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
this.ScopeMe.hasOne(this.Profile, { foreignKey: 'userId' }); this.ScopeMe.hasOne(this.Profile, { foreignKey: 'userId' });
this.ScopeMe.belongsTo(this.Company); this.ScopeMe.belongsTo(this.Company);
this.UserAssociation = this.Company.hasMany(this.ScopeMe, { as: 'users' }); this.UserAssociation = this.Company.hasMany(this.ScopeMe, { as: 'users', foreignKey: 'companyId'});
return this.sequelize.sync({force: true}).bind(this).then(function() { return this.sequelize.sync({force: true}).bind(this).then(function() {
return Promise.all([ return Promise.all([
......
...@@ -512,10 +512,10 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() { ...@@ -512,10 +512,10 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() {
Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user', onUpdate: 'SET NULL', onDelete: 'RESTRICT' }); Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user', onUpdate: 'SET NULL', onDelete: 'RESTRICT' });
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onUpdate).to.equal('RESTRICT'); expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId.onUpdate).to.equal('RESTRICT');
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onDelete).to.equal('SET NULL'); expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId.onDelete).to.equal('SET NULL');
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onUpdate).to.equal('SET NULL'); expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId.onUpdate).to.equal('SET NULL');
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onDelete).to.equal('RESTRICT'); expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId.onDelete).to.equal('RESTRICT');
}); });
it('work properly when through is a model', function() { it('work properly when through is a model', function() {
...@@ -527,10 +527,25 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() { ...@@ -527,10 +527,25 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() {
Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup, onUpdate: 'SET NULL', onDelete: 'RESTRICT' }); Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup, onUpdate: 'SET NULL', onDelete: 'RESTRICT' });
expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model);
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onUpdate).to.equal('RESTRICT'); expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId.onUpdate).to.equal('RESTRICT');
expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onDelete).to.equal('SET NULL'); expect(Group.associations.MyUsers.through.model.rawAttributes.MyGroupsId.onDelete).to.equal('SET NULL');
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onUpdate).to.equal('SET NULL'); expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId.onUpdate).to.equal('SET NULL');
expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onDelete).to.equal('RESTRICT'); expect(Group.associations.MyUsers.through.model.rawAttributes.MyUsersId.onDelete).to.equal('RESTRICT');
}); });
}); });
it('properly use the `as` key to generate foreign key name', function(){
var City = current.define('City', { cityname: DataTypes.STRING })
, State = current.define('State', { statename: DataTypes.STRING })
, CityStateMap = current.define('CityStateMap', { rating: DataTypes.STRING });
State.belongsToMany(City, { through: CityStateMap });
expect(CityStateMap.attributes.StateId).not.to.be.empty;
expect(CityStateMap.attributes.CityId).not.to.be.empty;
State.belongsToMany(City, { through: CityStateMap, as: 'Test'});
expect(CityStateMap.attributes.TestId).not.to.be.empty;
expect(CityStateMap.attributes.CityId).not.to.be.empty;
});
}); });
'use strict';
/* jshint -W030 */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + '/../../../lib/data-types')
, current = Support.sequelize;
describe(Support.getTestDialectTeaser('hasOne'), function() {
it('properly use the `as` key to generate foreign key name', function(){
var User = current.define('User', { username: DataTypes.STRING })
, Task = current.define('Task', { title: DataTypes.STRING });
User.belongsTo(Task);
expect(User.attributes.TaskId).not.to.be.empty;
User.belongsTo(Task, {as : 'Naam'});
expect(User.attributes.NaamId).not.to.be.empty;
});
});
...@@ -162,4 +162,15 @@ describe(Support.getTestDialectTeaser('hasMany'), function() { ...@@ -162,4 +162,15 @@ describe(Support.getTestDialectTeaser('hasMany'), function() {
}); });
}); });
}); });
it('properly use the `as` key to generate foreign key name', function(){
var City = current.define('City', { cityname: DataTypes.STRING })
, State = current.define('State', { statename: DataTypes.STRING });
State.hasMany(City);
expect(City.attributes.StateId).not.to.be.empty;
State.hasMany(City, {as : 'Capital'});
expect(City.attributes.CapitalId).not.to.be.empty;
});
}); });
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!