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

Commit a71a019f by Sushant Committed by GitHub

fix: inject foreignKey when using separate:true (#9396)

1 parent d6cd7534
...@@ -7,7 +7,6 @@ const Transaction = require('../transaction'); ...@@ -7,7 +7,6 @@ const Transaction = require('../transaction');
const Association = require('./base'); const Association = require('./base');
const Op = require('../operators'); const Op = require('../operators');
/** /**
* One-to-one association * One-to-one association
* *
...@@ -115,16 +114,17 @@ class BelongsTo extends Association { ...@@ -115,16 +114,17 @@ class BelongsTo extends Association {
/** /**
* Get the associated instance. * Get the associated instance.
* *
* @param {Object} [options] * @param {Object} [options]
* @param {String|Boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false. * @param {String|Boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false.
* @param {String} [options.schema] Apply a schema on the related model * @param {String} [options.schema] Apply a schema on the related model
*
* @see {@link Model.findOne} for a full explanation of options * @see {@link Model.findOne} for a full explanation of options
*
* @return {Promise<Model>} * @return {Promise<Model>}
*/ */
get(instances, options) { get(instances, options) {
const association = this;
const where = {}; const where = {};
let Target = association.target; let Target = this.target;
let instance; let instance;
options = Utils.cloneDeep(options); options = Utils.cloneDeep(options);
...@@ -147,14 +147,14 @@ class BelongsTo extends Association { ...@@ -147,14 +147,14 @@ class BelongsTo extends Association {
} }
if (instances) { if (instances) {
where[association.targetKey] = { where[this.targetKey] = {
[Op.in]: instances.map(instance => instance.get(association.foreignKey)) [Op.in]: instances.map(instance => instance.get(this.foreignKey))
}; };
} else { } else {
if (association.targetKeyIsPrimary && !options.where) { if (this.targetKeyIsPrimary && !options.where) {
return Target.findById(instance.get(association.foreignKey), options); return Target.findById(instance.get(this.foreignKey), options);
} else { } else {
where[association.targetKey] = instance.get(association.foreignKey); where[this.targetKey] = instance.get(this.foreignKey);
options.limit = null; options.limit = null;
} }
} }
...@@ -167,11 +167,11 @@ class BelongsTo extends Association { ...@@ -167,11 +167,11 @@ class BelongsTo extends Association {
return Target.findAll(options).then(results => { return Target.findAll(options).then(results => {
const result = {}; const result = {};
for (const instance of instances) { for (const instance of instances) {
result[instance.get(association.foreignKey, {raw: true})] = null; result[instance.get(this.foreignKey, {raw: true})] = null;
} }
for (const instance of results) { for (const instance of results) {
result[instance.get(association.targetKey, {raw: true})] = instance; result[instance.get(this.targetKey, {raw: true})] = instance;
} }
return result; return result;
...@@ -184,28 +184,26 @@ class BelongsTo extends Association { ...@@ -184,28 +184,26 @@ class BelongsTo extends Association {
/** /**
* Set the associated model. * Set the associated model.
* *
* @param {Model|String|Number} [newAssociation] An persisted instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association. * @param {Model|String|Number} [newAssociation] An persisted instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association.
* @param {Object} [options] Options passed to `this.save` * @param {Object} [options] Options passed to `this.save`
* @param {Boolean} [options.save=true] Skip saving this after setting the foreign key if false. * @param {Boolean} [options.save=true] Skip saving this after setting the foreign key if false.
* @return {Promise} *
* @return {Promise}
*/ */
set(sourceInstance, associatedInstance, options) { set(sourceInstance, associatedInstance, options = {}) {
const association = this;
options = options || {};
let value = associatedInstance; let value = associatedInstance;
if (associatedInstance instanceof association.target) {
value = associatedInstance[association.targetKey]; if (associatedInstance instanceof this.target) {
value = associatedInstance[this.targetKey];
} }
sourceInstance.set(association.foreignKey, value); sourceInstance.set(this.foreignKey, value);
if (options.save === false) return; if (options.save === false) return;
options = _.extend({ options = _.extend({
fields: [association.foreignKey], fields: [this.foreignKey],
allowNull: [association.foreignKey], allowNull: [this.foreignKey],
association: true association: true
}, options); }, options);
...@@ -218,22 +216,24 @@ class BelongsTo extends Association { ...@@ -218,22 +216,24 @@ class BelongsTo extends Association {
* *
* @param {Object} [values] * @param {Object} [values]
* @param {Object} [options] Options passed to `target.create` and setAssociation. * @param {Object} [options] Options passed to `target.create` and setAssociation.
*
* @see {@link Model#create} for a full explanation of options * @see {@link Model#create} for a full explanation of options
*
* @return {Promise} * @return {Promise}
*/ */
create(sourceInstance, values, fieldsOrOptions) { create(sourceInstance, values, fieldsOrOptions) {
const association = this;
const options = {}; const options = {};
options.logging = (fieldsOrOptions || {}).logging;
if ((fieldsOrOptions || {}).transaction instanceof Transaction) { if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction; options.transaction = fieldsOrOptions.transaction;
} }
options.logging = (fieldsOrOptions || {}).logging;
return association.target.create(values, fieldsOrOptions).then(newAssociatedObject => return this.target.create(values, fieldsOrOptions)
sourceInstance[association.accessors.set](newAssociatedObject, options) .then(newAssociatedObject =>
); sourceInstance[this.accessors.set](newAssociatedObject, options)
);
} }
} }
......
...@@ -21,7 +21,6 @@ class HasMany extends Association { ...@@ -21,7 +21,6 @@ class HasMany extends Association {
this.associationType = 'HasMany'; this.associationType = 'HasMany';
this.targetAssociation = null; this.targetAssociation = null;
this.sequelize = source.sequelize; this.sequelize = source.sequelize;
this.through = options.through;
this.isMultiAssociation = true; this.isMultiAssociation = true;
this.foreignKeyAttribute = {}; this.foreignKeyAttribute = {};
...@@ -91,7 +90,8 @@ class HasMany extends Association { ...@@ -91,7 +90,8 @@ class HasMany extends Association {
this.sourceKeyField = this.source.primaryKeyField; this.sourceKeyField = this.source.primaryKeyField;
} }
// Get singular and plural names, trying to uppercase the first letter, unless the model forbids it // Get singular and plural names
// try to uppercase the first letter, unless the model forbids it
const plural = Utils.uppercaseFirst(this.options.name.plural); const plural = Utils.uppercaseFirst(this.options.name.plural);
const singular = Utils.uppercaseFirst(this.options.name.singular); const singular = Utils.uppercaseFirst(this.options.name.singular);
...@@ -165,10 +165,10 @@ class HasMany extends Association { ...@@ -165,10 +165,10 @@ class HasMany extends Association {
* @see {@link Model.findAll} for a full explanation of options * @see {@link Model.findAll} for a full explanation of options
* @return {Promise<Array<Model>>} * @return {Promise<Array<Model>>}
*/ */
get(instances, options) { get(instances, options = {}) {
const association = this;
const where = {}; const where = {};
let Model = association.target;
let Model = this.target;
let instance; let instance;
let values; let values;
...@@ -177,36 +177,35 @@ class HasMany extends Association { ...@@ -177,36 +177,35 @@ class HasMany extends Association {
instances = undefined; instances = undefined;
} }
options = Utils.cloneDeep(options) || {}; options = Utils.cloneDeep(options);
if (association.scope) { if (this.scope) {
_.assign(where, association.scope); _.assign(where, this.scope);
} }
if (instances) { if (instances) {
values = instances.map(instance => instance.get(association.sourceKey, {raw: true})); values = instances.map(instance => instance.get(this.sourceKey, { raw: true }));
if (options.limit && instances.length > 1) { if (options.limit && instances.length > 1) {
options.groupedLimit = { options.groupedLimit = {
limit: options.limit, limit: options.limit,
on: association, on: this, // association
values values
}; };
delete options.limit; delete options.limit;
} else { } else {
where[association.foreignKey] = { where[this.foreignKey] = {
[Op.in]: values [Op.in]: values
}; };
delete options.groupedLimit; delete options.groupedLimit;
} }
} else { } else {
where[association.foreignKey] = instance.get(association.sourceKey, {raw: true}); where[this.foreignKey] = instance.get(this.sourceKey, { raw: true });
} }
options.where = options.where ? options.where = options.where ?
{[Op.and]: [where, options.where]} : { [Op.and]: [where, options.where] } :
where; where;
if (options.hasOwnProperty('scope')) { if (options.hasOwnProperty('scope')) {
...@@ -221,17 +220,16 @@ class HasMany extends Association { ...@@ -221,17 +220,16 @@ class HasMany extends Association {
Model = Model.schema(options.schema, options.schemaDelimiter); Model = Model.schema(options.schema, options.schemaDelimiter);
} }
return Model.findAll(options).then(results => { return Model.findAll(options).then(results => {
if (instance) return results; if (instance) return results;
const result = {}; const result = {};
for (const instance of instances) { for (const instance of instances) {
result[instance.get(association.sourceKey, {raw: true})] = []; result[instance.get(this.sourceKey, { raw: true })] = [];
} }
for (const instance of results) { for (const instance of results) {
result[instance.get(association.foreignKey, {raw: true})].push(instance); result[instance.get(this.foreignKey, { raw: true })].push(instance);
} }
return result; return result;
...@@ -241,24 +239,28 @@ class HasMany extends Association { ...@@ -241,24 +239,28 @@ class HasMany extends Association {
/** /**
* Count everything currently associated with this, using an optional where clause. * Count everything currently associated with this, using an optional where clause.
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Object} [options.where] An optional where clause to limit the associated models * @param {Object} [options.where] An optional where clause to limit the associated models
* @param {String|Boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {String|Boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false
*
* @return {Promise<Integer>} * @return {Promise<Integer>}
*/ */
count(instance, options) { count(instance, options) {
const association = this;
const model = association.target;
const sequelize = model.sequelize;
options = Utils.cloneDeep(options); options = Utils.cloneDeep(options);
options.attributes = [ options.attributes = [
[sequelize.fn('COUNT', sequelize.col(model.name.concat('.', model.primaryKeyField))), 'count'] [
this.sequelize.fn(
'COUNT',
this.sequelize.col(this.target.name.concat('.', this.target.primaryKeyField))
),
'count'
]
]; ];
options.raw = true; options.raw = true;
options.plain = true; options.plain = true;
return association.get(instance, options).then(result => parseInt(result.count, 10)); return this.get(instance, options).then(result => parseInt(result.count, 10));
} }
/** /**
...@@ -266,10 +268,10 @@ class HasMany extends Association { ...@@ -266,10 +268,10 @@ class HasMany extends Association {
* *
* @param {Model[]|Model|string[]|String|number[]|Number} [instance(s)] * @param {Model[]|Model|string[]|String|number[]|Number} [instance(s)]
* @param {Object} [options] Options passed to getAssociations * @param {Object} [options] Options passed to getAssociations
*
* @return {Promise} * @return {Promise}
*/ */
has(sourceInstance, targetInstances, options) { has(sourceInstance, targetInstances, options) {
const association = this;
const where = {}; const where = {};
if (!Array.isArray(targetInstances)) { if (!Array.isArray(targetInstances)) {
...@@ -282,11 +284,11 @@ class HasMany extends Association { ...@@ -282,11 +284,11 @@ class HasMany extends Association {
}); });
where[Op.or] = targetInstances.map(instance => { where[Op.or] = targetInstances.map(instance => {
if (instance instanceof association.target) { if (instance instanceof this.target) {
return instance.where(); return instance.where();
} else { } else {
const _where = {}; const _where = {};
_where[association.target.primaryKeyAttribute] = instance; _where[this.target.primaryKeyAttribute] = instance;
return _where; return _where;
} }
}); });
...@@ -298,7 +300,7 @@ class HasMany extends Association { ...@@ -298,7 +300,7 @@ class HasMany extends Association {
] ]
}; };
return association.get(sourceInstance, options).then(associatedObjects => associatedObjects.length === targetInstances.length); return this.get(sourceInstance, options).then(associatedObjects => associatedObjects.length === targetInstances.length);
} }
/** /**
...@@ -310,24 +312,22 @@ class HasMany extends Association { ...@@ -310,24 +312,22 @@ class HasMany extends Association {
* @return {Promise} * @return {Promise}
*/ */
set(sourceInstance, targetInstances, options) { set(sourceInstance, targetInstances, options) {
const association = this;
if (targetInstances === null) { if (targetInstances === null) {
targetInstances = []; targetInstances = [];
} else { } else {
targetInstances = association.toInstanceArray(targetInstances); targetInstances = this.toInstanceArray(targetInstances);
} }
return association.get(sourceInstance, _.defaults({scope: false, raw: true}, options)).then(oldAssociations => { return this.get(sourceInstance, _.defaults({scope: false, raw: true}, options)).then(oldAssociations => {
const promises = []; const promises = [];
const obsoleteAssociations = oldAssociations.filter(old => const obsoleteAssociations = oldAssociations.filter(old =>
!_.find(targetInstances, obj => !_.find(targetInstances, obj =>
obj[association.target.primaryKeyAttribute] === old[association.target.primaryKeyAttribute] obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute]
) )
); );
const unassociatedObjects = targetInstances.filter(obj => const unassociatedObjects = targetInstances.filter(obj =>
!_.find(oldAssociations, old => !_.find(oldAssociations, old =>
obj[association.target.primaryKeyAttribute] === old[association.target.primaryKeyAttribute] obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute]
) )
); );
let updateWhere; let updateWhere;
...@@ -335,15 +335,15 @@ class HasMany extends Association { ...@@ -335,15 +335,15 @@ class HasMany extends Association {
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
update = {}; update = {};
update[association.foreignKey] = null; update[this.foreignKey] = null;
updateWhere = {}; updateWhere = {};
updateWhere[association.target.primaryKeyAttribute] = obsoleteAssociations.map(associatedObject => updateWhere[this.target.primaryKeyAttribute] = obsoleteAssociations.map(associatedObject =>
associatedObject[association.target.primaryKeyAttribute] associatedObject[this.target.primaryKeyAttribute]
); );
promises.push(association.target.unscoped().update( promises.push(this.target.unscoped().update(
update, update,
_.defaults({ _.defaults({
where: updateWhere where: updateWhere
...@@ -355,14 +355,14 @@ class HasMany extends Association { ...@@ -355,14 +355,14 @@ class HasMany extends Association {
updateWhere = {}; updateWhere = {};
update = {}; update = {};
update[association.foreignKey] = sourceInstance.get(association.sourceKey); update[this.foreignKey] = sourceInstance.get(this.sourceKey);
_.assign(update, association.scope); _.assign(update, this.scope);
updateWhere[association.target.primaryKeyAttribute] = unassociatedObjects.map(unassociatedObject => updateWhere[this.target.primaryKeyAttribute] = unassociatedObjects.map(unassociatedObject =>
unassociatedObject[association.target.primaryKeyAttribute] unassociatedObject[this.target.primaryKeyAttribute]
); );
promises.push(association.target.unscoped().update( promises.push(this.target.unscoped().update(
update, update,
_.defaults({ _.defaults({
where: updateWhere where: updateWhere
...@@ -382,25 +382,22 @@ class HasMany extends Association { ...@@ -382,25 +382,22 @@ class HasMany extends Association {
* @param {Object} [options] Options passed to `target.update`. * @param {Object} [options] Options passed to `target.update`.
* @return {Promise} * @return {Promise}
*/ */
add(sourceInstance, targetInstances, options) { add(sourceInstance, targetInstances, options = {}) {
if (!targetInstances) return Utils.Promise.resolve(); if (!targetInstances) return Utils.Promise.resolve();
const association = this;
const update = {}; const update = {};
const where = {}; const where = {};
options = options || {}; targetInstances = this.toInstanceArray(targetInstances);
targetInstances = association.toInstanceArray(targetInstances); update[this.foreignKey] = sourceInstance.get(this.sourceKey);
_.assign(update, this.scope);
update[association.foreignKey] = sourceInstance.get(association.sourceKey); where[this.target.primaryKeyAttribute] = targetInstances.map(unassociatedObject =>
_.assign(update, association.scope); unassociatedObject.get(this.target.primaryKeyAttribute)
where[association.target.primaryKeyAttribute] = targetInstances.map(unassociatedObject =>
unassociatedObject.get(association.target.primaryKeyAttribute)
); );
return association.target.unscoped().update(update, _.defaults({where}, options)).return(sourceInstance); return this.target.unscoped().update(update, _.defaults({where}, options)).return(sourceInstance);
} }
/** /**
...@@ -410,22 +407,20 @@ class HasMany extends Association { ...@@ -410,22 +407,20 @@ class HasMany extends Association {
* @param {Object} [options] Options passed to `target.update` * @param {Object} [options] Options passed to `target.update`
* @return {Promise} * @return {Promise}
*/ */
remove(sourceInstance, targetInstances, options) { remove(sourceInstance, targetInstances, options = {}) {
const association = this;
const update = {}; const update = {};
const where = {}; const where = {};
options = options || {}; targetInstances = this.toInstanceArray(targetInstances);
targetInstances = association.toInstanceArray(targetInstances);
update[association.foreignKey] = null; update[this.foreignKey] = null;
where[association.foreignKey] = sourceInstance.get(association.sourceKey); where[this.foreignKey] = sourceInstance.get(this.sourceKey);
where[association.target.primaryKeyAttribute] = targetInstances.map(targetInstance => where[this.target.primaryKeyAttribute] = targetInstances.map(targetInstance =>
targetInstance.get(association.target.primaryKeyAttribute) targetInstance.get(this.target.primaryKeyAttribute)
); );
return association.target.unscoped().update(update, _.defaults({where}, options)).return(this); return this.target.unscoped().update(update, _.defaults({where}, options)).return(this);
} }
/** /**
...@@ -435,11 +430,7 @@ class HasMany extends Association { ...@@ -435,11 +430,7 @@ class HasMany extends Association {
* @param {Object} [options] Options passed to `target.create`. * @param {Object} [options] Options passed to `target.create`.
* @return {Promise} * @return {Promise}
*/ */
create(sourceInstance, values, options) { create(sourceInstance, values, options = {}) {
const association = this;
options = options || {};
if (Array.isArray(options)) { if (Array.isArray(options)) {
options = { options = {
fields: options fields: options
...@@ -450,16 +441,16 @@ class HasMany extends Association { ...@@ -450,16 +441,16 @@ class HasMany extends Association {
values = {}; values = {};
} }
if (association.scope) { if (this.scope) {
for (const attribute of Object.keys(association.scope)) { for (const attribute of Object.keys(this.scope)) {
values[attribute] = association.scope[attribute]; values[attribute] = this.scope[attribute];
if (options.fields) options.fields.push(attribute); if (options.fields) options.fields.push(attribute);
} }
} }
values[association.foreignKey] = sourceInstance.get(association.sourceKey); values[this.foreignKey] = sourceInstance.get(this.sourceKey);
if (options.fields) options.fields.push(association.foreignKey); if (options.fields) options.fields.push(this.foreignKey);
return association.target.create(values, options); return this.target.create(values, options);
} }
} }
......
...@@ -120,9 +120,9 @@ class HasOne extends Association { ...@@ -120,9 +120,9 @@ class HasOne extends Association {
* @return {Promise<Model>} * @return {Promise<Model>}
*/ */
get(instances, options) { get(instances, options) {
const association = this;
const where = {}; const where = {};
let Target = association.target;
let Target = this.target;
let instance; let instance;
options = Utils.cloneDeep(options); options = Utils.cloneDeep(options);
...@@ -145,15 +145,15 @@ class HasOne extends Association { ...@@ -145,15 +145,15 @@ class HasOne extends Association {
} }
if (instances) { if (instances) {
where[association.foreignKey] = { where[this.foreignKey] = {
[Op.in]: instances.map(instance => instance.get(association.sourceKey)) [Op.in]: instances.map(instance => instance.get(this.sourceKey))
}; };
} else { } else {
where[association.foreignKey] = instance.get(association.sourceKey); where[this.foreignKey] = instance.get(this.sourceKey);
} }
if (association.scope) { if (this.scope) {
_.assign(where, association.scope); _.assign(where, this.scope);
} }
options.where = options.where ? options.where = options.where ?
...@@ -164,61 +164,61 @@ class HasOne extends Association { ...@@ -164,61 +164,61 @@ class HasOne extends Association {
return Target.findAll(options).then(results => { return Target.findAll(options).then(results => {
const result = {}; const result = {};
for (const instance of instances) { for (const instance of instances) {
result[instance.get(association.sourceKey, {raw: true})] = null; result[instance.get(this.sourceKey, {raw: true})] = null;
} }
for (const instance of results) { for (const instance of results) {
result[instance.get(association.foreignKey, {raw: true})] = instance; result[instance.get(this.foreignKey, {raw: true})] = instance;
} }
return result; return result;
}); });
} }
return Target.findOne(options); return Target.findOne(options);
} }
/** /**
* Set the associated model. * Set the associated model.
* *
* @param {Model|String|Number} [newAssociation] An persisted instance or the primary key of a persisted instance to associate with this. Pass `null` or `undefined` to remove the association. * @param {Model|String|Number} [associatedInstance] An persisted instance or the primary key of a persisted instance to associate with this. Pass `null` or `undefined` to remove the association.
* @param {Object} [options] Options passed to getAssociation and `target.save` * @param {Object} [options] Options passed to getAssociation and `target.save`
*
* @return {Promise} * @return {Promise}
*/ */
set(sourceInstance, associatedInstance, options) { set(sourceInstance, associatedInstance, options) {
const association = this;
let alreadyAssociated; let alreadyAssociated;
options = _.assign({}, options, { options = _.assign({}, options, {
scope: false scope: false
}); });
return sourceInstance[association.accessors.get](options).then(oldInstance => { return sourceInstance[this.accessors.get](options).then(oldInstance => {
// TODO Use equals method once #5605 is resolved // TODO Use equals method once #5605 is resolved
alreadyAssociated = oldInstance && associatedInstance && _.every(association.target.primaryKeyAttributes, attribute => alreadyAssociated = oldInstance && associatedInstance && _.every(this.target.primaryKeyAttributes, attribute =>
oldInstance.get(attribute, {raw: true}) === (associatedInstance.get ? associatedInstance.get(attribute, {raw: true}) : associatedInstance) oldInstance.get(attribute, {raw: true}) === (associatedInstance.get ? associatedInstance.get(attribute, {raw: true}) : associatedInstance)
); );
if (oldInstance && !alreadyAssociated) { if (oldInstance && !alreadyAssociated) {
oldInstance[association.foreignKey] = null; oldInstance[this.foreignKey] = null;
return oldInstance.save(_.extend({}, options, { return oldInstance.save(_.extend({}, options, {
fields: [association.foreignKey], fields: [this.foreignKey],
allowNull: [association.foreignKey], allowNull: [this.foreignKey],
association: true association: true
})); }));
} }
}).then(() => { }).then(() => {
if (associatedInstance && !alreadyAssociated) { if (associatedInstance && !alreadyAssociated) {
if (!(associatedInstance instanceof association.target)) { if (!(associatedInstance instanceof this.target)) {
const tmpInstance = {}; const tmpInstance = {};
tmpInstance[association.target.primaryKeyAttribute] = associatedInstance; tmpInstance[this.target.primaryKeyAttribute] = associatedInstance;
associatedInstance = association.target.build(tmpInstance, { associatedInstance = this.target.build(tmpInstance, {
isNewRecord: false isNewRecord: false
}); });
} }
_.assign(associatedInstance, association.scope); _.assign(associatedInstance, this.scope);
associatedInstance.set(association.foreignKey, sourceInstance.get(association.sourceIdentifier)); associatedInstance.set(this.foreignKey, sourceInstance.get(this.sourceIdentifier));
return associatedInstance.save(options); return associatedInstance.save(options);
} }
...@@ -232,30 +232,30 @@ class HasOne extends Association { ...@@ -232,30 +232,30 @@ class HasOne extends Association {
* *
* @param {Object} [values] * @param {Object} [values]
* @param {Object} [options] Options passed to `target.create` and setAssociation. * @param {Object} [options] Options passed to `target.create` and setAssociation.
*
* @see {@link Model#create} for a full explanation of options * @see {@link Model#create} for a full explanation of options
*
* @return {Promise} * @return {Promise}
*/ */
create(sourceInstance, values, options) { create(sourceInstance, values, options) {
const association = this;
values = values || {}; values = values || {};
options = options || {}; options = options || {};
if (association.scope) { if (this.scope) {
for (const attribute of Object.keys(association.scope)) { for (const attribute of Object.keys(this.scope)) {
values[attribute] = association.scope[attribute]; values[attribute] = this.scope[attribute];
if (options.fields) { if (options.fields) {
options.fields.push(attribute); options.fields.push(attribute);
} }
} }
} }
values[association.foreignKey] = sourceInstance.get(association.sourceIdentifier); values[this.foreignKey] = sourceInstance.get(this.sourceIdentifier);
if (options.fields) { if (options.fields) {
options.fields.push(association.foreignKey); options.fields.push(this.foreignKey);
} }
return association.target.create(values, options); return this.target.create(values, options);
} }
} }
......
...@@ -655,16 +655,28 @@ class Model { ...@@ -655,16 +655,28 @@ class Model {
include.separate = true; include.separate = true;
} }
if (include.separate === true && !(include.association instanceof HasMany)) {
throw new Error('Only HasMany associations support include.separate');
}
if (include.separate === true) { if (include.separate === true) {
if (!(include.association instanceof HasMany)) {
throw new Error('Only HasMany associations support include.separate');
}
include.duplicating = false; include.duplicating = false;
}
if (include.separate === true && options.attributes && options.attributes.length && !_.includes(options.attributes, association.source.primaryKeyAttribute)) { if (
options.attributes.push(association.source.primaryKeyAttribute); options.attributes
&& options.attributes.length
&& !_.includes(_.flattenDepth(options.attributes, 2), association.sourceKey)
) {
options.attributes.push(association.sourceKey);
}
if (
include.attributes
&& include.attributes.length
&& !_.includes(_.flattenDepth(include.attributes, 2), association.foreignKey)
) {
include.attributes.push(association.foreignKey);
}
} }
// Validate child includes // Validate child includes
...@@ -1769,16 +1781,14 @@ class Model { ...@@ -1769,16 +1781,14 @@ class Model {
return include.association.get(results, _.assign( return include.association.get(results, _.assign(
{}, {},
_.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'offset', 'plain'), _.omit(options, ['include', 'attributes', 'originalAttributes', 'order', 'where', 'limit', 'offset', 'plain']),
_.omit(include, 'parent', 'association', 'as') _.omit(include, ['parent', 'association', 'as', 'originalAttributes'])
)).then(map => { )).then(map => {
for (const result of results) { for (const result of results) {
result.set( result.set(
include.association.as, include.association.as,
map[result.get(include.association.source.primaryKeyAttribute)], map[result.get(include.association.sourceKey)],
{ { raw: true }
raw: true
}
); );
} }
}); });
......
...@@ -103,6 +103,65 @@ if (current.dialect.supports.groupedLimit) { ...@@ -103,6 +103,65 @@ if (current.dialect.supports.groupedLimit) {
}); });
}); });
it('should work even if include does not specify foreign key attribute with custom sourceKey', function() {
const User = this.sequelize.define('User', {
name: DataTypes.STRING,
userExtraId: {
type: DataTypes.INTEGER,
unique: true
}
});
const Task = this.sequelize.define('Task', {
title: DataTypes.STRING
});
const sqlSpy = sinon.spy();
User.Tasks = User.hasMany(Task, {
as: 'tasks',
foreignKey: 'userId',
sourceKey: 'userExtraId'
});
return this.sequelize
.sync({force: true})
.then(() => {
return User.create({
id: 1,
userExtraId: 222,
tasks: [
{},
{},
{}
]
}, {
include: [User.Tasks]
});
})
.then(() => {
return User.findAll({
attributes: ['name'],
include: [
{
attributes: [
'title'
],
association: User.Tasks,
separate: true
}
],
order: [
['id', 'ASC']
],
logging: sqlSpy
});
})
.then(users => {
expect(users[0].get('tasks')).to.be.ok;
expect(users[0].get('tasks').length).to.equal(3);
expect(sqlSpy).to.have.been.calledTwice;
});
});
it('should not break a nested include with null values', function() { it('should not break a nested include with null values', function() {
const User = this.sequelize.define('User', {}), const User = this.sequelize.define('User', {}),
Team = this.sequelize.define('Team', {}), Team = this.sequelize.define('Team', {}),
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!