feat(associations): source and target key support for belongs-to-many… · sequelize/sequelize@83e263b · GitHub
Skip to content

Commit 83e263b

Browse files
swarthysushantdhiman
authored andcommitted
feat(associations): source and target key support for belongs-to-many (#11311)
1 parent 4f09899 commit 83e263b

7 files changed

Lines changed: 850 additions & 70 deletions

File tree

docs/associations.md

Lines changed: 64 additions & 0 deletions

lib/associations/belongs-to-many.js

Lines changed: 92 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -110,43 +110,6 @@ class BelongsToMany extends Association {
110110
this.targetAssociation = this;
111111
}
112112

113-
/*
114-
* Default/generated foreign/other keys
115-
*/
116-
if (_.isObject(this.options.foreignKey)) {
117-
this.foreignKeyAttribute = this.options.foreignKey;
118-
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
119-
} else {
120-
if (!this.options.foreignKey) {
121-
this.foreignKeyDefault = true;
122-
}
123-
124-
this.foreignKeyAttribute = {};
125-
this.foreignKey = this.options.foreignKey || Utils.camelize(
126-
[
127-
this.source.options.name.singular,
128-
this.source.primaryKeyAttribute
129-
].join('_')
130-
);
131-
}
132-
133-
if (_.isObject(this.options.otherKey)) {
134-
this.otherKeyAttribute = this.options.otherKey;
135-
this.otherKey = this.otherKeyAttribute.name || this.otherKeyAttribute.fieldName;
136-
} else {
137-
if (!this.options.otherKey) {
138-
this.otherKeyDefault = true;
139-
}
140-
141-
this.otherKeyAttribute = {};
142-
this.otherKey = this.options.otherKey || Utils.camelize(
143-
[
144-
this.isSelfAssociation ? Utils.singularize(this.as) : this.target.options.name.singular,
145-
this.target.primaryKeyAttribute
146-
].join('_')
147-
);
148-
}
149-
150113
/*
151114
* Find paired association (if exists)
152115
*/
@@ -160,6 +123,23 @@ class BelongsToMany extends Association {
160123
}
161124
});
162125

126+
/*
127+
* Default/generated source/target keys
128+
*/
129+
this.sourceKey = this.options.sourceKey || this.source.primaryKeyAttribute;
130+
this.sourceKeyField = this.source.rawAttributes[this.sourceKey].field || this.sourceKey;
131+
132+
if (this.options.targetKey) {
133+
this.targetKey = this.options.targetKey;
134+
this.targetKeyField = this.target.rawAttributes[this.targetKey].field || this.targetKey;
135+
} else {
136+
this.targetKeyDefault = true;
137+
this.targetKey = this.target.primaryKeyAttribute;
138+
this.targetKeyField = this.target.rawAttributes[this.targetKey].field || this.targetKey;
139+
}
140+
141+
this._createForeignAndOtherKeys();
142+
163143
if (typeof this.through.model === 'string') {
164144
if (!this.sequelize.isDefined(this.through.model)) {
165145
this.through.model = this.sequelize.define(this.through.model, {}, Object.assign(this.options, {
@@ -178,6 +158,25 @@ class BelongsToMany extends Association {
178158
]));
179159

180160
if (this.paired) {
161+
let needInjectPaired = false;
162+
163+
if (this.targetKeyDefault) {
164+
this.targetKey = this.paired.sourceKey;
165+
this.targetKeyField = this.paired.sourceKeyField;
166+
this._createForeignAndOtherKeys();
167+
}
168+
if (this.paired.targetKeyDefault) {
169+
// in this case paired.otherKey depends on paired.targetKey,
170+
// so cleanup previously wrong generated otherKey
171+
if (this.paired.targetKey !== this.sourceKey) {
172+
delete this.through.model.rawAttributes[this.paired.otherKey];
173+
this.paired.targetKey = this.sourceKey;
174+
this.paired.targetKeyField = this.sourceKeyField;
175+
this.paired._createForeignAndOtherKeys();
176+
needInjectPaired = true;
177+
}
178+
}
179+
181180
if (this.otherKeyDefault) {
182181
this.otherKey = this.paired.foreignKey;
183182
}
@@ -187,9 +186,13 @@ class BelongsToMany extends Association {
187186
if (this.paired.otherKey !== this.foreignKey) {
188187
delete this.through.model.rawAttributes[this.paired.otherKey];
189188
this.paired.otherKey = this.foreignKey;
190-
this.paired._injectAttributes();
189+
needInjectPaired = true;
191190
}
192191
}
192+
193+
if (needInjectPaired) {
194+
this.paired._injectAttributes();
195+
}
193196
}
194197

195198
if (this.through) {
@@ -218,6 +221,41 @@ class BelongsToMany extends Association {
218221
};
219222
}
220223

224+
_createForeignAndOtherKeys() {
225+
/*
226+
* Default/generated foreign/other keys
227+
*/
228+
if (_.isObject(this.options.foreignKey)) {
229+
this.foreignKeyAttribute = this.options.foreignKey;
230+
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
231+
} else {
232+
this.foreignKeyAttribute = {};
233+
this.foreignKey = this.options.foreignKey || Utils.camelize(
234+
[
235+
this.source.options.name.singular,
236+
this.sourceKey
237+
].join('_')
238+
);
239+
}
240+
241+
if (_.isObject(this.options.otherKey)) {
242+
this.otherKeyAttribute = this.options.otherKey;
243+
this.otherKey = this.otherKeyAttribute.name || this.otherKeyAttribute.fieldName;
244+
} else {
245+
if (!this.options.otherKey) {
246+
this.otherKeyDefault = true;
247+
}
248+
249+
this.otherKeyAttribute = {};
250+
this.otherKey = this.options.otherKey || Utils.camelize(
251+
[
252+
this.isSelfAssociation ? Utils.singularize(this.as) : this.target.options.name.singular,
253+
this.targetKey
254+
].join('_')
255+
);
256+
}
257+
}
258+
221259
// the id is in the target table
222260
// or in an extra table which connects two tables
223261
_injectAttributes() {
@@ -240,12 +278,12 @@ class BelongsToMany extends Association {
240278
}
241279
});
242280

243-
const sourceKey = this.source.rawAttributes[this.source.primaryKeyAttribute];
281+
const sourceKey = this.source.rawAttributes[this.sourceKey];
244282
const sourceKeyType = sourceKey.type;
245-
const sourceKeyField = sourceKey.field || this.source.primaryKeyAttribute;
246-
const targetKey = this.target.rawAttributes[this.target.primaryKeyAttribute];
283+
const sourceKeyField = this.sourceKeyField;
284+
const targetKey = this.target.rawAttributes[this.targetKey];
247285
const targetKeyType = targetKey.type;
248-
const targetKeyField = targetKey.field || this.target.primaryKeyAttribute;
286+
const targetKeyField = this.targetKeyField;
249287
const sourceAttribute = _.defaults({}, this.foreignKeyAttribute, { type: sourceKeyType });
250288
const targetAttribute = _.defaults({}, this.otherKeyAttribute, { type: targetKeyType });
251289

@@ -393,7 +431,7 @@ class BelongsToMany extends Association {
393431

394432
if (Object(through.model) === through.model) {
395433
throughWhere = {};
396-
throughWhere[this.foreignKey] = instance.get(this.source.primaryKeyAttribute);
434+
throughWhere[this.foreignKey] = instance.get(this.sourceKey);
397435

398436
if (through.scope) {
399437
Object.assign(throughWhere, through.scope);
@@ -442,12 +480,11 @@ class BelongsToMany extends Association {
442480
* @returns {Promise<number>}
443481
*/
444482
count(instance, options) {
445-
const model = this.target;
446-
const sequelize = model.sequelize;
483+
const sequelize = this.target.sequelize;
447484

448485
options = Utils.cloneDeep(options);
449486
options.attributes = [
450-
[sequelize.fn('COUNT', sequelize.col([this.target.name, model.primaryKeyField].join('.'))), 'count']
487+
[sequelize.fn('COUNT', sequelize.col([this.target.name, this.targetKeyField].join('.'))), 'count']
451488
];
452489
options.joinTableAttributes = [];
453490
options.raw = true;
@@ -474,7 +511,7 @@ class BelongsToMany extends Association {
474511
raw: true
475512
}, options, {
476513
scope: false,
477-
attributes: [this.target.primaryKeyAttribute],
514+
attributes: [this.targetKey],
478515
joinTableAttributes: []
479516
});
480517

@@ -483,7 +520,7 @@ class BelongsToMany extends Association {
483520
return instance.where();
484521
}
485522
return {
486-
[this.target.primaryKeyAttribute]: instance
523+
[this.targetKey]: instance
487524
};
488525
});
489526

@@ -495,7 +532,7 @@ class BelongsToMany extends Association {
495532
};
496533

497534
return this.get(sourceInstance, options).then(associatedObjects =>
498-
_.differenceBy(instancePrimaryKeys, associatedObjects, this.target.primaryKeyAttribute).length === 0
535+
_.differenceBy(instancePrimaryKeys, associatedObjects, this.targetKey).length === 0
499536
);
500537
}
501538

@@ -514,8 +551,8 @@ class BelongsToMany extends Association {
514551
set(sourceInstance, newAssociatedObjects, options) {
515552
options = options || {};
516553

517-
const sourceKey = this.source.primaryKeyAttribute;
518-
const targetKey = this.target.primaryKeyAttribute;
554+
const sourceKey = this.sourceKey;
555+
const targetKey = this.targetKey;
519556
const identifier = this.identifier;
520557
const foreignIdentifier = this.foreignIdentifier;
521558
let where = {};
@@ -626,8 +663,8 @@ class BelongsToMany extends Association {
626663
options = _.clone(options) || {};
627664

628665
const association = this;
629-
const sourceKey = association.source.primaryKeyAttribute;
630-
const targetKey = association.target.primaryKeyAttribute;
666+
const sourceKey = association.sourceKey;
667+
const targetKey = association.targetKey;
631668
const identifier = association.identifier;
632669
const foreignIdentifier = association.foreignIdentifier;
633670
const defaultAttributes = options.through || {};
@@ -721,8 +758,8 @@ class BelongsToMany extends Association {
721758
oldAssociatedObjects = association.toInstanceArray(oldAssociatedObjects);
722759

723760
const where = {
724-
[association.identifier]: sourceInstance.get(association.source.primaryKeyAttribute),
725-
[association.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(association.target.primaryKeyAttribute))
761+
[association.identifier]: sourceInstance.get(association.sourceKey),
762+
[association.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(association.targetKey))
726763
};
727764

728765
return association.through.model.destroy(_.defaults({ where }, options));

lib/dialects/abstract/query-generator.js

Lines changed: 4 additions & 6 deletions

0 commit comments

Comments
 (0)