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

Commit eb383aa6 by Mick Hansen

Merge pull request #3876 from JHouk/master

Add new option for belongsTo association when target key field for the target table is not the primary key.
2 parents ad4b2c30 e7f25687
# Next # Next
- [FEATURE] Add support for new option `targetKey` in a belongs-to relationship for situations where the target key is not the id field.
- [FEATURE] Add support for keyword `after` in options of a field (useful for migrations), only for MySQL. [#3166](https://github.com/sequelize/sequelize/pull/3166) - [FEATURE] Add support for keyword `after` in options of a field (useful for migrations), only for MySQL. [#3166](https://github.com/sequelize/sequelize/pull/3166)
- [FEATURE] There's a new sequelize.truncate function to truncate all tables defined through the sequelize models [#2671](https://github.com/sequelize/sequelize/pull/2671) - [FEATURE] There's a new sequelize.truncate function to truncate all tables defined through the sequelize models [#2671](https://github.com/sequelize/sequelize/pull/2671)
- [FEATURE] Add support for MySQLs TINYTEXT, MEDIUMTEXT and LONGTEXT. [#3836](https://github.com/sequelize/sequelize/pull/3836) - [FEATURE] Add support for MySQLs TINYTEXT, MEDIUMTEXT and LONGTEXT. [#3836](https://github.com/sequelize/sequelize/pull/3836)
......
...@@ -56,6 +56,17 @@ var User = this.sequelize.define('User', {/* attributes */}) ...@@ -56,6 +56,17 @@ var User = this.sequelize.define('User', {/* attributes */})
User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User
``` ```
#### Target keys
By default the target key for a belongsTo relation will be the target primary key. To override this behavior, use the `targetKey` option.
```js
var User = this.sequelize.define('User', {/* attributes */})
, Company = this.sequelize.define('Company', {/* attributes */});
User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // Adds fk_companyname to User
```
### HasOne ### HasOne
......
...@@ -54,7 +54,7 @@ var BelongsTo = function(source, target, options) { ...@@ -54,7 +54,7 @@ var BelongsTo = function(source, target, options) {
!this.target.options.underscored !this.target.options.underscored
); );
this.targetIdentifier = this.target.primaryKeyAttribute; this.targetIdentifier = this.options.targetKey || this.target.primaryKeyAttribute;
this.associationAccessor = this.as; this.associationAccessor = this.as;
this.options.useHooks = options.useHooks; this.options.useHooks = options.useHooks;
......
...@@ -161,8 +161,9 @@ Mixin.hasOne = singleLinked(HasOne); ...@@ -161,8 +161,9 @@ Mixin.hasOne = singleLinked(HasOne);
* @param {Model} target * @param {Model} target
* @param {object} [options] * @param {object} [options]
* @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
* @param {string} [options.as] The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized name of target * @param {string} [options.as] The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized name of target
* @param {string|object} [options.foreignKey] The name of the foreign key in the source table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the colum. Defaults to the name of target + primary key of target * @param {string|object} [options.foreignKey] The name of the foreign key in the source table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target
* @param {string} [options.targetKey] The name of the field to use as the key for the association in the target table. Defaults to the primary key of the target table
* @param {string} [options.onDelete='SET NULL'] * @param {string} [options.onDelete='SET NULL']
* @param {string} [options.onUpdate='CASCADE'] * @param {string} [options.onUpdate='CASCADE']
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key. * @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
......
...@@ -1194,7 +1194,7 @@ var QueryGenerator = { ...@@ -1194,7 +1194,7 @@ var QueryGenerator = {
, tableRight = as , tableRight = as
, attrRight = association.associationType !== 'BelongsTo' ? , attrRight = association.associationType !== 'BelongsTo' ?
association.identifierField || association.identifier : association.identifierField || association.identifier :
right.rawAttributes[primaryKeysRight[0]].field || primaryKeysRight[0] right.rawAttributes[association.targetIdentifier || primaryKeysRight[0]].field
, joinOn , joinOn
, subQueryJoinOn; , subQueryJoinOn;
......
...@@ -600,6 +600,54 @@ describe(Support.getTestDialectTeaser('BelongsTo'), function() { ...@@ -600,6 +600,54 @@ describe(Support.getTestDialectTeaser('BelongsTo'), function() {
expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING); expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING);
}); });
}); });
it('should support a non-primary key as the association column', function() {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
, Task = this.sequelize.define('Task', { title: DataTypes.STRING });
User.removeAttribute('id');
Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username'});
return this.sequelize.sync({ force: true }).then(function() {
return User.create({ username: 'bob' }).then(function(newUser) {
return Task.create({ title: 'some task' }).then(function(newTask) {
return newTask.setUser(newUser).then(function() {
return Task.findOne({title: 'some task'}).then(function (foundTask) {
return foundTask.getUser().then(function (foundUser) {
expect(foundUser.username).to.equal('bob');
});
});
});
});
});
});
});
it('should support a non-primary key as the association column with a field option', function() {
var User = this.sequelize.define('User', {
username: {
type: DataTypes.STRING,
field: 'the_user_name_field'
}
})
, Task = this.sequelize.define('Task', { title: DataTypes.STRING });
User.removeAttribute('id');
Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username'});
return this.sequelize.sync({ force: true }).then(function() {
return User.create({ username: 'bob' }).then(function(newUser) {
return Task.create({ title: 'some task' }).then(function(newTask) {
return newTask.setUser(newUser).then(function() {
return Task.findOne({title: 'some task'}).then(function (foundTask) {
return foundTask.getUser().then(function (foundUser) {
expect(foundUser.username).to.equal('bob');
});
});
});
});
});
});
});
}); });
describe('Association options', function() { describe('Association options', function() {
......
...@@ -170,6 +170,30 @@ describe(Support.getTestDialectTeaser('Include'), function() { ...@@ -170,6 +170,30 @@ describe(Support.getTestDialectTeaser('Include'), function() {
}); });
}); });
it('should support a belongsTo with the targetKey option', function() {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
, Task = this.sequelize.define('Task', { title: DataTypes.STRING });
User.removeAttribute('id');
Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username'});
return this.sequelize.sync({ force: true }).then(function() {
return User.create({ username: 'bob' }).then(function(newUser) {
return Task.create({ title: 'some task' }).then(function(newTask) {
return newTask.setUser(newUser).then(function() {
return Task.find({
title: 'some task',
include: [ { model: User } ]
})
.then(function (foundTask) {
expect(foundTask).to.be.ok;
expect(foundTask.User.username).to.equal('bob');
});
});
});
});
});
});
it('should support many levels of belongsTo (with a lower level having a where)', function() { it('should support many levels of belongsTo (with a lower level having a where)', function() {
var A = this.sequelize.define('a', {}) var A = this.sequelize.define('a', {})
, B = this.sequelize.define('b', {}) , B = this.sequelize.define('b', {})
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!