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

Commit 3ff27d10 by Felix Becker Committed by Jan Aagaard Meier

ES6 refactor: associations (#6050)

* Make BelongsTo association an ES6 class

* ES6 refactor of belongs-to.js

* Make BelongsToMany an ES6 class

* ES6 refactor of BelongsToMany

* Make HasMany an ES6 class

* ES6 refactor of HasMany

* Make HasOne an ES6 class

* ES6 refactor of HasOne

* ES6 refactor of association helpers

* ES6 refactor of associations/index.js

* ES6 refactor of association mixin
1 parent 5d7b26c2
'use strict';
var Utils = require('./../utils')
, Helpers = require('./helpers')
, _ = require('lodash')
, Association = require('./base')
, BelongsTo = require('./belongs-to')
, HasMany = require('./has-many')
, HasOne = require('./has-one')
, util = require('util');
const Utils = require('./../utils');
const Helpers = require('./helpers');
const _ = require('lodash');
const Association = require('./base');
const BelongsTo = require('./belongs-to');
const HasMany = require('./has-many');
const HasOne = require('./has-one');
/**
* Many-to-many association with a join table.
......@@ -37,8 +36,9 @@ var Utils = require('./../utils')
*
* @mixin BelongsToMany
*/
var BelongsToMany = function(source, target, options) {
Association.call(this);
class BelongsToMany extends Association {
constructor(source, target, options) {
super();
options = options || {};
......@@ -145,7 +145,7 @@ var BelongsToMany = function(source, target, options) {
/*
* Find paired association (if exists)
*/
_.each(this.target.associations, function(association) {
_.each(this.target.associations, association => {
if (association.associationType !== 'BelongsToMany') return;
if (association.target !== this.source) return;
......@@ -153,7 +153,7 @@ var BelongsToMany = function(source, target, options) {
this.paired = association;
association.paired = this;
}
}.bind(this));
});
if (typeof this.through.model === 'string') {
if (!this.sequelize.isDefined(this.through.model)) {
......@@ -192,8 +192,8 @@ var BelongsToMany = function(source, target, options) {
this.associationAccessor = this.as;
// Get singular and plural names, trying to uppercase the first letter, unless the model forbids it
var plural = Utils.uppercaseFirst(this.options.name.plural)
, singular = Utils.uppercaseFirst(this.options.name.singular);
const plural = Utils.uppercaseFirst(this.options.name.plural);
const singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
/**
......@@ -294,47 +294,44 @@ var BelongsToMany = function(source, target, options) {
*/
count: 'count' + plural
};
};
util.inherits(BelongsToMany, Association);
}
// the id is in the target table
// or in an extra table which connects two tables
BelongsToMany.prototype.injectAttributes = function() {
var self = this;
// the id is in the target table
// or in an extra table which connects two tables
injectAttributes() {
this.identifier = this.foreignKey;
this.foreignIdentifier = this.otherKey;
// remove any PKs previously defined by sequelize
// but ignore any keys that are part of this association (#5865)
_.each(this.through.model.rawAttributes, function(attribute, attributeName) {
_.each(this.through.model.rawAttributes, (attribute, attributeName) => {
if (attribute.primaryKey === true && attribute._autoGenerated === true) {
if (attributeName === self.foreignKey || attributeName === self.otherKey) {
if (attributeName === this.foreignKey || attributeName === this.otherKey) {
// this key is still needed as it's part of the association
// so just set primaryKey to false
attribute.primaryKey = false;
}
else {
delete self.through.model.rawAttributes[attributeName];
delete this.through.model.rawAttributes[attributeName];
}
self.primaryKeyDeleted = true;
this.primaryKeyDeleted = true;
}
});
var sourceKey = this.source.rawAttributes[this.source.primaryKeyAttribute]
, sourceKeyType = sourceKey.type
, sourceKeyField = sourceKey.field || this.source.primaryKeyAttribute
, targetKey = this.target.rawAttributes[this.target.primaryKeyAttribute]
, targetKeyType = targetKey.type
, targetKeyField = targetKey.field || this.target.primaryKeyAttribute
, sourceAttribute = _.defaults({}, this.foreignKeyAttribute, { type: sourceKeyType })
, targetAttribute = _.defaults({}, this.otherKeyAttribute, { type: targetKeyType });
const sourceKey = this.source.rawAttributes[this.source.primaryKeyAttribute];
const sourceKeyType = sourceKey.type;
const sourceKeyField = sourceKey.field || this.source.primaryKeyAttribute;
const targetKey = this.target.rawAttributes[this.target.primaryKeyAttribute];
const targetKeyType = targetKey.type;
const targetKeyField = targetKey.field || this.target.primaryKeyAttribute;
const sourceAttribute = _.defaults({}, this.foreignKeyAttribute, { type: sourceKeyType });
const targetAttribute = _.defaults({}, this.otherKeyAttribute, { type: targetKeyType });
if (this.primaryKeyDeleted === true) {
targetAttribute.primaryKey = sourceAttribute.primaryKey = true;
} else if (this.through.unique !== false) {
var uniqueKey = [this.through.model.tableName, this.foreignKey, this.otherKey, 'unique'].join('_');
const uniqueKey = [this.through.model.tableName, this.foreignKey, this.otherKey, 'unique'].join('_');
targetAttribute.unique = sourceAttribute.unique = uniqueKey;
}
......@@ -422,18 +419,17 @@ BelongsToMany.prototype.injectAttributes = function() {
Helpers.checkNamingCollision(this);
return this;
};
}
BelongsToMany.prototype.injectGetter = function(obj) {
var association = this;
injectGetter(obj) {
const association = this;
obj[this.accessors.get] = function(options) {
options = Utils.cloneDeep(options) || {};
var instance = this
, through = association.through
, scopeWhere
, throughWhere;
const through = association.through;
let scopeWhere;
let throughWhere;
if (association.scope) {
scopeWhere = _.clone(association.scope);
......@@ -448,7 +444,7 @@ BelongsToMany.prototype.injectGetter = function(obj) {
if (Object(through.model) === through.model) {
throughWhere = {};
throughWhere[association.foreignKey] = instance.get(association.source.primaryKeyAttribute);
throughWhere[association.foreignKey] = this.get(association.source.primaryKeyAttribute);
if (through.scope) {
_.assign(throughWhere, through.scope);
......@@ -470,7 +466,7 @@ BelongsToMany.prototype.injectGetter = function(obj) {
});
}
var model = association.target;
let model = association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
......@@ -487,8 +483,8 @@ BelongsToMany.prototype.injectGetter = function(obj) {
};
obj[this.accessors.count] = function(options) {
var model = association.target
, sequelize = model.sequelize;
const model = association.target;
const sequelize = model.sequelize;
options = Utils.cloneDeep(options);
options.attributes = [
......@@ -498,13 +494,11 @@ BelongsToMany.prototype.injectGetter = function(obj) {
options.raw = true;
options.plain = true;
return obj[association.accessors.get].call(this, options).then(function (result) {
return parseInt(result.count, 10);
});
return obj[association.accessors.get].call(this, options).then(result => parseInt(result.count, 10));
};
obj[this.accessors.hasSingle] = obj[this.accessors.hasAll] = function(instances, options) {
var where = {};
const where = {};
if (!Array.isArray(instances)) {
instances = [instances];
......@@ -516,11 +510,11 @@ BelongsToMany.prototype.injectGetter = function(obj) {
scope: false
});
where.$or = instances.map(function (instance) {
where.$or = instances.map(instance => {
if (instance instanceof association.target) {
return instance.where();
} else {
var $where = {};
const $where = {};
$where[association.target.primaryKeyAttribute] = instance;
return $where;
}
......@@ -533,25 +527,22 @@ BelongsToMany.prototype.injectGetter = function(obj) {
]
};
return this[association.accessors.get](options).then(function(associatedObjects) {
return associatedObjects.length === instances.length;
});
return this[association.accessors.get](options).then(associatedObjects => associatedObjects.length === instances.length);
};
return this;
};
}
BelongsToMany.prototype.injectSetter = function(obj) {
var association = this;
injectSetter(obj) {
const association = this;
obj[this.accessors.set] = function(newAssociatedObjects, options) {
options = options || {};
var instance = this
, sourceKey = association.source.primaryKeyAttribute
, targetKey = association.target.primaryKeyAttribute
, identifier = association.identifier
, foreignIdentifier = association.foreignIdentifier
, where = {};
const sourceKey = association.source.primaryKeyAttribute;
const targetKey = association.target.primaryKeyAttribute;
const identifier = association.identifier;
const foreignIdentifier = association.foreignIdentifier;
const where = {};
if (newAssociatedObjects === null) {
newAssociatedObjects = [];
......@@ -560,69 +551,55 @@ BelongsToMany.prototype.injectSetter = function(obj) {
}
where[identifier] = this.get(sourceKey);
return association.through.model.findAll(_.defaults({
where: where,
raw: true,
}, options)).then(function (currentRows) {
var obsoleteAssociations = []
, defaultAttributes = options
, promises = []
, unassociatedObjects;
return association.through.model.findAll(_.defaults({where, raw: true}, options)).then(currentRows => {
const obsoleteAssociations = [];
const promises = [];
let defaultAttributes = options;
// Don't try to insert the transaction as an attribute in the through table
defaultAttributes = _.omit(defaultAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
unassociatedObjects = newAssociatedObjects.filter(function(obj) {
return !_.find(currentRows, function(currentRow) {
return currentRow[foreignIdentifier] === obj.get(targetKey);
});
});
const unassociatedObjects = newAssociatedObjects.filter(obj =>
!_.find(currentRows, currentRow => currentRow[foreignIdentifier] === obj.get(targetKey))
);
currentRows.forEach(function(currentRow) {
var newObj = _.find(newAssociatedObjects, function(obj) {
return currentRow[foreignIdentifier] === obj.get(targetKey);
});
for (const currentRow of currentRows) {
const newObj = _.find(newAssociatedObjects, obj => currentRow[foreignIdentifier] === obj.get(targetKey));
if (!newObj) {
obsoleteAssociations.push(currentRow);
} else {
var throughAttributes = newObj[association.through.model.name];
let throughAttributes = newObj[association.through.model.name];
// Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object)
if (throughAttributes instanceof association.through.model) {
throughAttributes = {};
}
var where = {}
, attributes = _.defaults({}, throughAttributes, defaultAttributes);
const where = {};
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
where[identifier] = instance.get(sourceKey);
where[identifier] = this.get(sourceKey);
where[foreignIdentifier] = newObj.get(targetKey);
if (Object.keys(attributes).length) {
promises.push(association.through.model.update(attributes, _.extend(options, {
where: where
})));
promises.push(association.through.model.update(attributes, _.extend(options, {where})));
}
}
}
});
if (obsoleteAssociations.length > 0) {
var where = {};
where[identifier] = instance.get(sourceKey);
where[foreignIdentifier] = obsoleteAssociations.map(function(obsoleteAssociation) {
return obsoleteAssociation[foreignIdentifier];
});
const where = {};
where[identifier] = this.get(sourceKey);
where[foreignIdentifier] = obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]);
promises.push(association.through.model.destroy(_.defaults({
where: where
}, options)));
promises.push(association.through.model.destroy(_.defaults({where}, options)));
}
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {};
const bulk = unassociatedObjects.map(unassociatedObject => {
let attributes = {};
attributes[identifier] = instance.get(sourceKey);
attributes[identifier] = this.get(sourceKey);
attributes[foreignIdentifier] = unassociatedObject.get(targetKey);
attributes = _.defaults(attributes, unassociatedObject[association.through.model.name], defaultAttributes);
......@@ -630,7 +607,7 @@ BelongsToMany.prototype.injectSetter = function(obj) {
_.assign(attributes, association.through.scope);
return attributes;
}.bind(this));
});
promises.push(association.through.model.bulkCreate(bulk, _.assign({ validate: true }, options)));
}
......@@ -645,80 +622,70 @@ BelongsToMany.prototype.injectSetter = function(obj) {
additionalAttributes = _.clone(additionalAttributes) || {};
var instance = this
, defaultAttributes = _.omit(additionalAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging'])
, sourceKey = association.source.primaryKeyAttribute
, targetKey = association.target.primaryKeyAttribute
, identifier = association.identifier
, foreignIdentifier = association.foreignIdentifier
, options = additionalAttributes;
const defaultAttributes = _.omit(additionalAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
const sourceKey = association.source.primaryKeyAttribute;
const targetKey = association.target.primaryKeyAttribute;
const identifier = association.identifier;
const foreignIdentifier = association.foreignIdentifier;
const options = additionalAttributes;
newInstances = association.toInstanceArray(newInstances);
var where = {};
where[identifier] = instance.get(sourceKey);
where[foreignIdentifier] = newInstances.map(function (newInstance) { return newInstance.get(targetKey); });
const where = {};
where[identifier] = this.get(sourceKey);
where[foreignIdentifier] = newInstances.map(newInstance => newInstance.get(targetKey));
_.assign(where, association.through.scope);
return association.through.model.findAll(_.defaults({
where: where,
raw: true,
}, options)).then(function (currentRows) {
var promises = [];
var unassociatedObjects = [], changedAssociations = [];
newInstances.forEach(function(obj) {
var existingAssociation = _.find(currentRows, function(current) {
return current[foreignIdentifier] === obj.get(targetKey);
});
return association.through.model.findAll(_.defaults({where, raw: true}, options)).then(currentRows => {
const promises = [];
const unassociatedObjects = [];
const changedAssociations = [];
for (const obj of newInstances) {
const existingAssociation = _.find(currentRows, current => current[foreignIdentifier] === obj.get(targetKey));
if (!existingAssociation) {
unassociatedObjects.push(obj);
} else {
var throughAttributes = obj[association.through.model.name]
, attributes = _.defaults({}, throughAttributes, defaultAttributes);
const throughAttributes = obj[association.through.model.name];
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
if (_.some(Object.keys(attributes), function (attribute) {
return attributes[attribute] !== existingAssociation[attribute];
})) {
if (_.some(Object.keys(attributes), attribute => attributes[attribute] !== existingAssociation[attribute])) {
changedAssociations.push(obj);
}
}
});
}
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var throughAttributes = unassociatedObject[association.through.model.name]
, attributes = _.defaults({}, throughAttributes, defaultAttributes);
const bulk = unassociatedObjects.map(unassociatedObject => {
const throughAttributes = unassociatedObject[association.through.model.name];
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
attributes[identifier] = instance.get(sourceKey);
attributes[identifier] = this.get(sourceKey);
attributes[foreignIdentifier] = unassociatedObject.get(targetKey);
_.assign(attributes, association.through.scope);
return attributes;
}.bind(this));
});
promises.push(association.through.model.bulkCreate(bulk, _.assign({ validate: true }, options)));
}
changedAssociations.forEach(function(assoc) {
var throughAttributes = assoc[association.through.model.name]
, attributes = _.defaults({}, throughAttributes, defaultAttributes)
, where = {};
for (const assoc of changedAssociations) {
let throughAttributes = assoc[association.through.model.name];
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
const where = {};
// Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object)
if (throughAttributes instanceof association.through.model) {
throughAttributes = {};
}
where[identifier] = instance.get(sourceKey);
where[identifier] = this.get(sourceKey);
where[foreignIdentifier] = assoc.get(targetKey);
promises.push(association.through.model.update(attributes, _.extend(options, {
where: where
})));
});
promises.push(association.through.model.update(attributes, _.extend(options, {where})));
}
return Utils.Promise.all(promises);
});
......@@ -729,23 +696,20 @@ BelongsToMany.prototype.injectSetter = function(obj) {
oldAssociatedObjects = association.toInstanceArray(oldAssociatedObjects);
var where = {};
const where = {};
where[association.identifier] = this.get(association.source.primaryKeyAttribute);
where[association.foreignIdentifier] = oldAssociatedObjects.map(function (newInstance) { return newInstance.get(association.target.primaryKeyAttribute); });
where[association.foreignIdentifier] = oldAssociatedObjects.map(newInstance => newInstance.get(association.target.primaryKeyAttribute));
return association.through.model.destroy(_.defaults({
where: where
}, options));
return association.through.model.destroy(_.defaults({where}, options));
};
return this;
};
}
BelongsToMany.prototype.injectCreator = function(obj) {
var association = this;
injectCreator(obj) {
const association = this;
obj[this.accessors.create] = function(values, options) {
var instance = this;
options = options || {};
values = values || {};
......@@ -763,12 +727,15 @@ BelongsToMany.prototype.injectCreator = function(obj) {
}
// Create the related model instance
return association.target.create(values, options).then(function(newAssociatedObject) {
return instance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject);
});
return association.target.create(values, options).then(newAssociatedObject =>
this[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject)
);
};
return this;
};
}
}
module.exports = BelongsToMany;
module.exports.BelongsToMany = BelongsToMany;
module.exports.default = BelongsToMany;
'use strict';
var Utils = require('./../utils')
, Helpers = require('./helpers')
, _ = require('lodash')
, Transaction = require('../transaction')
, Association = require('./base')
, util = require('util');
const Utils = require('./../utils');
const Helpers = require('./helpers');
const _ = require('lodash');
const Transaction = require('../transaction');
const Association = require('./base');
/**
* One-to-one association
......@@ -14,8 +13,9 @@ var Utils = require('./../utils')
*
* @mixin BelongsTo
*/
var BelongsTo = function(source, target, options) {
Association.call(this);
class BelongsTo extends Association {
constructor(source, target, options) {
super();
this.associationType = 'BelongsTo';
this.source = source;
......@@ -69,7 +69,7 @@ var BelongsTo = function(source, target, options) {
this.options.useHooks = options.useHooks;
// Get singular name, trying to uppercase the first letter, unless the model forbids it
var singular = Utils.uppercaseFirst(this.options.name.singular);
const singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
/**
......@@ -104,13 +104,11 @@ var BelongsTo = function(source, target, options) {
*/
create: 'create' + singular
};
};
util.inherits(BelongsTo, Association);
}
// the id is in the source table
BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {};
// the id is in the source table
injectAttributes() {
const newAttributes = {};
newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, {
type: this.options.keyType || this.target.rawAttributes[this.targetKey].type,
......@@ -118,7 +116,7 @@ BelongsTo.prototype.injectAttributes = function() {
});
if (this.options.constraints !== false) {
var source = this.source.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey];
const source = this.source.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey];
this.options.onDelete = this.options.onDelete || (source.allowNull ? 'SET NULL' : 'NO ACTION');
this.options.onUpdate = this.options.onUpdate || 'CASCADE';
}
......@@ -133,10 +131,10 @@ BelongsTo.prototype.injectAttributes = function() {
Helpers.checkNamingCollision(this);
return this;
};
}
BelongsTo.prototype.mixin = function(obj) {
var association = this;
mixin(obj) {
const association = this;
obj[this.accessors.get] = function(options) {
return association.get(this, options);
......@@ -144,13 +142,13 @@ BelongsTo.prototype.mixin = function(obj) {
association.injectSetter(obj);
association.injectCreator(obj);
};
}
BelongsTo.prototype.get = function(instances, options) {
var association = this
, Target = association.target
, instance
, where = {};
get(instances, options) {
const association = this;
const where = {};
let Target = association.target;
let instance;
options = Utils.cloneDeep(options);
......@@ -173,9 +171,7 @@ BelongsTo.prototype.get = function(instances, options) {
if (instances) {
where[association.targetKey] = {
$in: instances.map(function (instance) {
return instance.get(association.foreignKey);
})
$in: instances.map(instance => instance.get(association.foreignKey))
};
} else {
if (association.targetKeyIsPrimary && !options.where) {
......@@ -191,30 +187,30 @@ BelongsTo.prototype.get = function(instances, options) {
where;
if (instances) {
return Target.findAll(options).then(function (results) {
var result = {};
instances.forEach(function (instance) {
return Target.findAll(options).then(results => {
const result = {};
for (const instance of instances) {
result[instance.get(association.foreignKey, {raw: true})] = null;
});
}
results.forEach(function (instance) {
for (const instance of results) {
result[instance.get(association.targetKey, {raw: true})] = instance;
});
}
return result;
});
}
return Target.findOne(options);
};
}
// Add setAssociaton method to the prototype of the model instance
BelongsTo.prototype.injectSetter = function(instancePrototype) {
var association = this;
// Add setAssociaton method to the prototype of the model instance
injectSetter(instancePrototype) {
const association = this;
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
options = options || {};
var value = associatedInstance;
let value = associatedInstance;
if (associatedInstance instanceof association.target) {
value = associatedInstance[association.targetKey];
}
......@@ -235,27 +231,29 @@ BelongsTo.prototype.injectSetter = function(instancePrototype) {
};
return this;
};
}
// Add createAssociation method to the prototype of the model instance
BelongsTo.prototype.injectCreator = function(instancePrototype) {
var association = this;
// Add createAssociation method to the prototype of the model instance
injectCreator(instancePrototype) {
const association = this;
instancePrototype[this.accessors.create] = function(values, fieldsOrOptions) {
var instance = this
, options = {};
const options = {};
if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction;
}
options.logging = (fieldsOrOptions || {}).logging;
return association.target.create(values, fieldsOrOptions).then(function(newAssociatedObject) {
return instance[association.accessors.set](newAssociatedObject, options);
});
return association.target.create(values, fieldsOrOptions).then(newAssociatedObject =>
this[association.accessors.set](newAssociatedObject, options)
);
};
return this;
};
}
}
module.exports = BelongsTo;
module.exports.BelongsTo = BelongsTo;
module.exports.default = BelongsTo;
'use strict';
var Utils = require('./../utils')
, Helpers = require('./helpers')
, _ = require('lodash')
, Association = require('./base')
, util = require('util');
const Utils = require('./../utils');
const Helpers = require('./helpers');
const _ = require('lodash');
const Association = require('./base');
/**
* One-to-many association
......@@ -13,8 +12,9 @@ var Utils = require('./../utils')
*
* @mixin HasMany
*/
var HasMany = function(source, target, options) {
Association.call(this);
class HasMany extends Association {
constructor(source, target, options) {
super();
this.associationType = 'HasMany';
this.source = source;
......@@ -86,8 +86,8 @@ var HasMany = function(source, target, options) {
this.associationAccessor = this.as;
// Get singular and plural names, trying to uppercase the first letter, unless the model forbids it
var plural = Utils.uppercaseFirst(this.options.name.plural)
, singular = Utils.uppercaseFirst(this.options.name.singular);
const plural = Utils.uppercaseFirst(this.options.name.plural);
const singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
/**
......@@ -188,22 +188,20 @@ var HasMany = function(source, target, options) {
*/
count: 'count' + plural
};
};
util.inherits(HasMany, Association);
}
// the id is in the target table
// or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() {
var newAttributes = {};
var constraintOptions = _.clone(this.options); // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m
// the id is in the target table
// or in an extra table which connects two tables
injectAttributes() {
const newAttributes = {};
const constraintOptions = _.clone(this.options); // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m
newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, {
type: this.options.keyType || this.source.rawAttributes[this.source.primaryKeyAttribute].type,
allowNull : true
});
if (this.options.constraints !== false) {
var target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey];
const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey];
constraintOptions.onDelete = constraintOptions.onDelete || (target.allowNull ? 'SET NULL' : 'CASCADE');
constraintOptions.onUpdate = constraintOptions.onUpdate || 'CASCADE';
}
......@@ -219,10 +217,10 @@ HasMany.prototype.injectAttributes = function() {
Helpers.checkNamingCollision(this);
return this;
};
}
HasMany.prototype.mixin = function(obj) {
var association = this;
mixin(obj) {
const association = this;
obj[this.accessors.get] = function(options) {
return association.get(this, options);
......@@ -253,14 +251,14 @@ HasMany.prototype.mixin = function(obj) {
obj[this.accessors.create] = function(values, options) {
return association.create(this, values, options);
};
};
}
HasMany.prototype.get = function(instances, options) {
var association = this
, where = {}
, Model = association.target
, instance
, values;
get(instances, options) {
const association = this;
const where = {};
let Model = association.target;
let instance;
let values;
if (!Array.isArray(instances)) {
instance = instances;
......@@ -274,15 +272,13 @@ HasMany.prototype.get = function(instances, options) {
}
if (instances) {
values = instances.map(function (instance) {
return instance.get(association.source.primaryKeyAttribute, {raw: true});
});
values = instances.map(instance => instance.get(association.source.primaryKeyAttribute, {raw: true}));
if (options.limit && instances.length > 1) {
options.groupedLimit = {
limit: options.limit,
on: association.foreignKeyField,
values: values
values
};
delete options.limit;
......@@ -314,26 +310,26 @@ HasMany.prototype.get = function(instances, options) {
}
return Model.findAll(options).then(function (results) {
return Model.findAll(options).then(results => {
if (instance) return results;
var result = {};
instances.forEach(function (instance) {
const result = {};
for (const instance of instances) {
result[instance.get(association.source.primaryKeyAttribute, {raw: true})] = [];
});
}
results.forEach(function (instance) {
for (const instance of results) {
result[instance.get(association.foreignKey, {raw: true})].push(instance);
});
}
return result;
});
};
}
HasMany.prototype.count = function(instance, options) {
var association = this
, model = association.target
, sequelize = model.sequelize;
count(instance, options) {
const association = this;
const model = association.target;
const sequelize = model.sequelize;
options = Utils.cloneDeep(options);
options.attributes = [
......@@ -342,14 +338,12 @@ HasMany.prototype.count = function(instance, options) {
options.raw = true;
options.plain = true;
return this.get(instance, options).then(function (result) {
return parseInt(result.count, 10);
});
};
return this.get(instance, options).then(result => parseInt(result.count, 10));
}
HasMany.prototype.has = function(sourceInstance, targetInstances, options) {
var association = this
, where = {};
has(sourceInstance, targetInstances, options) {
const association = this;
const where = {};
if (!Array.isArray(targetInstances)) {
targetInstances = [targetInstances];
......@@ -360,11 +354,11 @@ HasMany.prototype.has = function(sourceInstance, targetInstances, options) {
raw: true
});
where.$or = targetInstances.map(function (instance) {
where.$or = targetInstances.map(instance => {
if (instance instanceof association.target) {
return instance.where();
} else {
var _where = {};
const _where = {};
_where[association.target.primaryKeyAttribute] = instance;
return _where;
}
......@@ -377,16 +371,11 @@ HasMany.prototype.has = function(sourceInstance, targetInstances, options) {
]
};
return this.get(
sourceInstance,
options
).then(function(associatedObjects) {
return associatedObjects.length === targetInstances.length;
});
};
return this.get(sourceInstance, options).then(associatedObjects => associatedObjects.length === targetInstances.length);
}
HasMany.prototype.set = function(sourceInstance, targetInstances, options) {
var association = this;
set(sourceInstance, targetInstances, options) {
const association = this;
if (targetInstances === null) {
targetInstances = [];
......@@ -394,23 +383,20 @@ HasMany.prototype.set = function(sourceInstance, targetInstances, options) {
targetInstances = association.toInstanceArray(targetInstances);
}
return association.get(sourceInstance, _.defaults({
scope: false,
raw: true
}, options)).then(function(oldAssociations) {
var promises = []
, obsoleteAssociations = oldAssociations.filter(function(old) {
return !_.find(targetInstances, function(obj) {
return obj[association.target.primaryKeyAttribute] === old[association.target.primaryKeyAttribute];
});
})
, unassociatedObjects = targetInstances.filter(function(obj) {
return !_.find(oldAssociations, function(old) {
return obj[association.target.primaryKeyAttribute] === old[association.target.primaryKeyAttribute];
});
})
, updateWhere
, update;
return association.get(sourceInstance, _.defaults({scope: false, raw: true}, options)).then(oldAssociations => {
const promises = [];
const obsoleteAssociations = oldAssociations.filter(old =>
!_.find(targetInstances, obj =>
obj[association.target.primaryKeyAttribute] === old[association.target.primaryKeyAttribute]
)
);
const unassociatedObjects = targetInstances.filter(obj =>
!_.find(oldAssociations, old =>
obj[association.target.primaryKeyAttribute] === old[association.target.primaryKeyAttribute]
)
);
let updateWhere;
let update;
if (obsoleteAssociations.length > 0) {
update = {};
......@@ -418,9 +404,9 @@ HasMany.prototype.set = function(sourceInstance, targetInstances, options) {
updateWhere = {};
updateWhere[association.target.primaryKeyAttribute] = obsoleteAssociations.map(function(associatedObject) {
return associatedObject[association.target.primaryKeyAttribute];
});
updateWhere[association.target.primaryKeyAttribute] = obsoleteAssociations.map(associatedObject =>
associatedObject[association.target.primaryKeyAttribute]
);
promises.push(association.target.unscoped().update(
update,
......@@ -437,9 +423,9 @@ HasMany.prototype.set = function(sourceInstance, targetInstances, options) {
update[association.foreignKey] = sourceInstance.get(association.source.primaryKeyAttribute);
_.assign(update, association.scope);
updateWhere[association.target.primaryKeyAttribute] = unassociatedObjects.map(function(unassociatedObject) {
return unassociatedObject[association.target.primaryKeyAttribute];
});
updateWhere[association.target.primaryKeyAttribute] = unassociatedObjects.map(unassociatedObject =>
unassociatedObject[association.target.primaryKeyAttribute]
);
promises.push(association.target.unscoped().update(
update,
......@@ -451,14 +437,14 @@ HasMany.prototype.set = function(sourceInstance, targetInstances, options) {
return Utils.Promise.all(promises).return(sourceInstance);
});
};
}
HasMany.prototype.add = function(sourceInstance, targetInstances, options) {
add(sourceInstance, targetInstances, options) {
if (!targetInstances) return Utils.Promise.resolve();
var association = this
, update = {}
, where = {};
const association = this;
const update = {};
const where = {};
options = options || {};
......@@ -467,22 +453,17 @@ HasMany.prototype.add = function(sourceInstance, targetInstances, options) {
update[association.foreignKey] = sourceInstance.get(association.source.primaryKeyAttribute);
_.assign(update, association.scope);
where[association.target.primaryKeyAttribute] = targetInstances.map(function (unassociatedObject) {
return unassociatedObject.get(association.target.primaryKeyAttribute);
});
where[association.target.primaryKeyAttribute] = targetInstances.map(unassociatedObject =>
unassociatedObject.get(association.target.primaryKeyAttribute)
);
return association.target.unscoped().update(
update,
_.defaults({
where: where
}, options)
).return(sourceInstance);
};
return association.target.unscoped().update(update, _.defaults({where}, options)).return(sourceInstance);
}
HasMany.prototype.remove = function(sourceInstance, targetInstances, options) {
var association = this
, update = {}
, where = {};
remove(sourceInstance, targetInstances, options) {
const association = this;
const update = {};
const where = {};
options = options || {};
targetInstances = association.toInstanceArray(targetInstances);
......@@ -490,20 +471,15 @@ HasMany.prototype.remove = function(sourceInstance, targetInstances, options) {
update[association.foreignKey] = null;
where[association.foreignKey] = sourceInstance.get(association.source.primaryKeyAttribute);
where[association.target.primaryKeyAttribute] = targetInstances.map(function (targetInstance) {
return targetInstance.get(association.target.primaryKeyAttribute);
});
where[association.target.primaryKeyAttribute] = targetInstances.map(targetInstance =>
targetInstance.get(association.target.primaryKeyAttribute)
);
return association.target.unscoped().update(
update,
_.defaults({
where: where
}, options)
).return(this);
};
return association.target.unscoped().update(update, _.defaults({where}, options)).return(this);
}
HasMany.prototype.create = function(sourceInstance, values, options) {
var association = this;
create(sourceInstance, values, options) {
const association = this;
options = options || {};
......@@ -518,15 +494,18 @@ HasMany.prototype.create = function(sourceInstance, values, options) {
}
if (association.scope) {
Object.keys(association.scope).forEach(function (attribute) {
for (const attribute of Object.keys(association.scope)) {
values[attribute] = association.scope[attribute];
if (options.fields) options.fields.push(attribute);
});
}
}
values[association.foreignKey] = sourceInstance.get(association.source.primaryKeyAttribute);
if (options.fields) options.fields.push(association.foreignKey);
return association.target.create(values, options);
};
}
}
module.exports = HasMany;
module.exports.HasMany = HasMany;
module.exports.default = HasMany;
'use strict';
var Utils = require('./../utils')
, Helpers = require('./helpers')
, _ = require('lodash')
, Association = require('./base')
, util = require('util');
const Utils = require('./../utils');
const Helpers = require('./helpers');
const _ = require('lodash');
const Association = require('./base');
/**
* One-to-one association
......@@ -14,8 +13,9 @@ var Utils = require('./../utils')
*
* @mixin HasOne
*/
var HasOne = function(srcModel, targetModel, options) {
Association.call(this);
class HasOne extends Association {
constructor(srcModel, targetModel, options) {
super();
this.associationType = 'HasOne';
this.source = srcModel;
......@@ -66,7 +66,7 @@ var HasOne = function(srcModel, targetModel, options) {
}
// Get singular name, trying to uppercase the first letter, unless the model forbids it
var singular = Utils.uppercaseFirst(this.options.name.singular);
const singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
/**
......@@ -100,14 +100,12 @@ var HasOne = function(srcModel, targetModel, options) {
*/
create: 'create' + singular
};
};
util.inherits(HasOne, Association);
}
// the id is in the target table
HasOne.prototype.injectAttributes = function() {
var newAttributes = {}
, keyType = this.source.rawAttributes[this.source.primaryKeyAttribute].type;
// the id is in the target table
injectAttributes() {
const newAttributes = {};
const keyType = this.source.rawAttributes[this.source.primaryKeyAttribute].type;
newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, {
type: this.options.keyType || keyType,
......@@ -118,7 +116,7 @@ HasOne.prototype.injectAttributes = function() {
this.identifierField = this.target.rawAttributes[this.foreignKey].field || this.foreignKey;
if (this.options.constraints !== false) {
var target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey];
const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey];
this.options.onDelete = this.options.onDelete || (target.allowNull ? 'SET NULL' : 'CASCADE');
this.options.onUpdate = this.options.onUpdate || 'CASCADE';
}
......@@ -131,10 +129,10 @@ HasOne.prototype.injectAttributes = function() {
Helpers.checkNamingCollision(this);
return this;
};
}
HasOne.prototype.mixin = function(obj) {
var association = this;
mixin(obj) {
const association = this;
obj[this.accessors.get] = function(options) {
return association.get(this, options);
......@@ -142,13 +140,13 @@ HasOne.prototype.mixin = function(obj) {
association.injectSetter(obj);
association.injectCreator(obj);
};
}
HasOne.prototype.get = function(instances, options) {
var association = this
, Target = association.target
, instance
, where = {};
get(instances, options) {
const association = this;
const where = {};
let Target = association.target;
let instance;
options = Utils.cloneDeep(options);
......@@ -171,9 +169,7 @@ HasOne.prototype.get = function(instances, options) {
if (instances) {
where[association.foreignKey] = {
$in: instances.map(function (instance) {
return instance.get(association.sourceKey);
})
$in: instances.map(instance => instance.get(association.sourceKey))
};
} else {
where[association.foreignKey] = instance.get(association.sourceKey);
......@@ -188,37 +184,36 @@ HasOne.prototype.get = function(instances, options) {
where;
if (instances) {
return Target.findAll(options).then(function (results) {
var result = {};
instances.forEach(function (instance) {
return Target.findAll(options).then(results => {
const result = {};
for (const instance of instances) {
result[instance.get(association.sourceKey, {raw: true})] = null;
});
}
results.forEach(function (instance) {
for (const instance of results) {
result[instance.get(association.foreignKey, {raw: true})] = instance;
});
}
return result;
});
}
return Target.findOne(options);
};
}
HasOne.prototype.injectSetter = function(instancePrototype) {
var association = this;
injectSetter(instancePrototype) {
const association = this;
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
var instance = this,
alreadyAssociated;
let alreadyAssociated;
options = _.assign({}, options, {
scope: false
});
return instance[association.accessors.get](options).then(function(oldInstance) {
return this[association.accessors.get](options).then(oldInstance => {
// TODO Use equals method once #5605 is resolved
alreadyAssociated = oldInstance && associatedInstance && _.every(association.target.primaryKeyAttributes, function(attribute) {
return oldInstance.get(attribute, {raw: true}) === associatedInstance.get(attribute, {raw: true});
});
alreadyAssociated = oldInstance && associatedInstance && _.every(association.target.primaryKeyAttributes, attribute =>
oldInstance.get(attribute, {raw: true}) === associatedInstance.get(attribute, {raw: true})
);
if (oldInstance && !alreadyAssociated) {
oldInstance[association.foreignKey] = null;
......@@ -228,10 +223,10 @@ HasOne.prototype.injectSetter = function(instancePrototype) {
association: true
}));
}
}).then(function() {
}).then(() => {
if (associatedInstance && !alreadyAssociated) {
if (!(associatedInstance instanceof association.target)) {
var tmpInstance = {};
const tmpInstance = {};
tmpInstance[association.target.primaryKeyAttribute] = associatedInstance;
associatedInstance = association.target.build(tmpInstance, {
isNewRecord: false
......@@ -239,7 +234,7 @@ HasOne.prototype.injectSetter = function(instancePrototype) {
}
_.assign(associatedInstance, association.scope);
associatedInstance.set(association.foreignKey, instance.get(association.sourceIdentifier));
associatedInstance.set(association.foreignKey, this.get(association.sourceIdentifier));
return associatedInstance.save(options);
}
......@@ -248,29 +243,29 @@ HasOne.prototype.injectSetter = function(instancePrototype) {
};
return this;
};
}
HasOne.prototype.injectCreator = function(instancePrototype) {
var association = this;
injectCreator(instancePrototype) {
const association = this;
instancePrototype[this.accessors.create] = function(values, options) {
var instance = this;
values = values || {};
options = options || {};
if (association.scope) {
Object.keys(association.scope).forEach(function (attribute) {
for (const attribute of Object.keys(association.scope)) {
values[attribute] = association.scope[attribute];
if (options.fields) options.fields.push(attribute);
});
}
}
values[association.foreignKey] = instance.get(association.sourceIdentifier);
values[association.foreignKey] = this.get(association.sourceIdentifier);
if (options.fields) options.fields.push(association.foreignKey);
return association.target.create(values, options);
};
return this;
};
}
}
module.exports = HasOne;
'use strict';
var Utils = require('./../utils');
const Utils = require('./../utils');
function checkNamingCollision (association) {
if (association.source.rawAttributes.hasOwnProperty(association.as)) {
......@@ -11,6 +11,7 @@ function checkNamingCollision (association) {
);
}
}
exports.checkNamingCollision = checkNamingCollision;
function addForeignKeyConstraints (newAttribute, source, target, options, key) {
// FK constraints are opt-in: users must either set `foreignKeyConstraints`
......@@ -19,9 +20,9 @@ function addForeignKeyConstraints (newAttribute, source, target, options, key) {
if (options.foreignKeyConstraint || options.onDelete || options.onUpdate) {
// Find primary keys: composite keys not supported with this approach
var primaryKeys = Utils._.chain(source.rawAttributes).keys()
.filter(function($key) { return source.rawAttributes[$key].primaryKey; })
.map(function($key) { return source.rawAttributes[$key].field || $key; }).value();
const primaryKeys = Utils._.chain(source.rawAttributes).keys()
.filter($key => source.rawAttributes[$key].primaryKey)
.map($key => source.rawAttributes[$key].field || $key).value();
if (primaryKeys.length === 1) {
if (!!source.$schema) {
......@@ -42,8 +43,4 @@ function addForeignKeyConstraints (newAttribute, source, target, options, key) {
}
}
}
module.exports = {
checkNamingCollision: checkNamingCollision,
addForeignKeyConstraints: addForeignKeyConstraints
};
exports.addForeignKeyConstraints = addForeignKeyConstraints;
'use strict';
var Association = require('./base');
const Association = require('./base');
Association.BelongsTo = require('./belongs-to');
Association.HasOne = require('./has-one');
Association.HasMany = require('./has-many');
Association.BelongsToMany = require('./belongs-to-many');
module.exports = Association;
module.exports.default = Association;
module.exports.Association = Association;
'use strict';
var Utils = require('./../utils')
, _ = require('lodash')
, HasOne = require('./has-one')
, HasMany = require('./has-many')
, BelongsToMany = require('./belongs-to-many')
, BelongsTo = require('./belongs-to');
const Utils = require('./../utils');
const _ = require('lodash');
const HasOne = require('./has-one');
const HasMany = require('./has-many');
const BelongsToMany = require('./belongs-to-many');
const BelongsTo = require('./belongs-to');
/**
* Creating associations in sequelize is done by calling one of the belongsTo / hasOne / hasMany / belongsToMany functions on a model (the source), and providing another model as the first argument to the function (the target).
......@@ -86,73 +86,8 @@ var Utils = require('./../utils')
* @mixin Associations
* @name Associations
*/
var Mixin = module.exports = function() {};
// The logic for hasOne and belongsTo is exactly the same
var singleLinked = function (Type) {
return function(target, options) { // testhint options:none
if (!target.prototype || !(target.prototype instanceof this.sequelize.Model)) {
throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not a subclass of Sequelize.Model');
}
var source = this;
// Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
options = options || {};
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks);
options.useHooks = options.hooks;
// the id is in the foreign table
var association = new Type(source, target, _.extend(options, source.options));
source.associations[association.associationAccessor] = association.injectAttributes();
if (association.mixin) {
association.mixin(source.prototype);
} else {
association.injectGetter(source.prototype);
association.injectSetter(source.prototype);
association.injectCreator(source.prototype);
}
return association;
};
};
/**
* Creates an association between this (the source) and the provided target. The foreign key is added on the target.
*
* Example: `User.hasOne(Profile)`. This will add userId to the profile table.
*
* @method hasOne
* @param {Model} target
* @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 {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 associated models. Defaults to the singularized name of target
* @param {string|object} [options.foreignKey] The name of the foreign key in the target 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 source + primary key of source
* @param {string} [options.onDelete='SET NULL|CASCADE'] SET NULL if foreignKey allows nulls, CASCADE if otherwise
* @param {string} [options.onUpdate='CASCADE']
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
*/
Mixin.hasOne = singleLinked(HasOne);
/**
* Creates an association between this (the source) and the provided target. The foreign key is added on the source.
*
* Example: `Profile.belongsTo(User)`. This will add userId to the profile table.
*
* @method belongsTo
* @param {Model} target
* @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 {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 associated 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 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|NO ACTION'] SET NULL if foreignKey allows nulls, NO ACTION if otherwise
* @param {string} [options.onUpdate='CASCADE']
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
*/
Mixin.belongsTo = singleLinked(BelongsTo);
/**
const Mixin = {
/**
* Creates a 1:m association between this (the source) and the provided target. The foreign key is added on the target.
*
* Example: `User.hasMany(Profile)`. This will add userId to the profile table.
......@@ -167,14 +102,14 @@ Mixin.belongsTo = singleLinked(BelongsTo);
* @param {string} [options.onUpdate='CASCADE']
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
*/
Mixin.hasMany = function(target, options) { // testhint options:none
hasMany(target, options) { // testhint options:none
if (!target.prototype || !(target.prototype instanceof this.sequelize.Model)) {
throw new Error(this.name + '.hasMany called with something that\'s not a subclass of Sequelize.Model');
}
var source = this;
const source = this;
// Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
// Since this is a mixin, we'll need a unique letiable name for hooks (since Model will override our hooks option)
options = options || {};
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks);
options.useHooks = options.hooks;
......@@ -182,16 +117,16 @@ Mixin.hasMany = function(target, options) { // testhint options:none
options = _.extend(options, _.omit(source.options, ['hooks']));
// the id is in the foreign table or in a connecting table
var association = new HasMany(source, target, options);
const association = new HasMany(source, target, options);
source.associations[association.associationAccessor] = association;
association.injectAttributes();
association.mixin(source.prototype);
return association;
};
},
/**
/**
* Create an N:M association with a join table.
*
* ```js
......@@ -202,7 +137,7 @@ Mixin.hasMany = function(target, options) { // testhint options:none
*
* If you define a through model with custom attributes, these attributes can be set when adding / setting new associations in two ways. Consider users and projects from before with a join table that stores whether the project has been started yet:
* ```js
* var UserProjects = sequelize.define('UserProjects', {
* let UserProjects = sequelize.define('UserProjects', {
* started: Sequelize.BOOLEAN
* })
* User.belongsToMany(Project, { through: UserProjects })
......@@ -225,7 +160,7 @@ Mixin.hasMany = function(target, options) { // testhint options:none
* Similarly, when fetching through a join table with custom attributes, these attributes will be available as an object with the name of the through model.
* ```js
* user.getProjects().then(function (projects) {
* var p1 = projects[0]
* let p1 = projects[0]
* p1.UserProjects.started // Is this project started yet?
* })
* ```
......@@ -246,14 +181,14 @@ Mixin.hasMany = function(target, options) { // testhint options:none
* @param {string} [options.onUpdate='CASCADE']
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
*/
Mixin.belongsToMany = function(targetModel, options) { // testhint options:none
belongsToMany(targetModel, options) { // testhint options:none
if (!targetModel.prototype || !(targetModel.prototype instanceof this.sequelize.Model)) {
throw new Error(this.name + '.belongsToMany called with something that\'s not a subclass of Sequelize.Model');
}
var sourceModel = this;
const sourceModel = this;
// Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
// Since this is a mixin, we'll need a unique letiable name for hooks (since Model will override our hooks option)
options = options || {};
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks);
options.useHooks = options.hooks;
......@@ -261,7 +196,7 @@ Mixin.belongsToMany = function(targetModel, options) { // testhint options:none
options = _.extend(options, _.omit(sourceModel.options, ['hooks', 'timestamps', 'scopes', 'defaultScope']));
// the id is in the foreign table or in a connecting table
var association = new BelongsToMany(sourceModel, targetModel, options);
const association = new BelongsToMany(sourceModel, targetModel, options);
sourceModel.associations[association.associationAccessor] = association.injectAttributes();
association.injectGetter(sourceModel.prototype);
......@@ -269,12 +204,12 @@ Mixin.belongsToMany = function(targetModel, options) { // testhint options:none
association.injectCreator(sourceModel.prototype);
return association;
};
},
Mixin.getAssociation = function(target, alias) {
for (var associationName in this.associations) {
getAssociation(target, alias) {
for (const associationName in this.associations) {
if (this.associations.hasOwnProperty(associationName)) {
var association = this.associations[associationName];
const association = this.associations[associationName];
if (association.target.name === target.name && (alias === undefined ? !association.isAliased : association.as === alias)) {
return association;
......@@ -283,4 +218,74 @@ Mixin.getAssociation = function(target, alias) {
}
return null;
}
};
// The logic for hasOne and belongsTo is exactly the same
function singleLinked(Type) {
return function(target, options) { // testhint options:none
if (!target.prototype || !(target.prototype instanceof this.sequelize.Model)) {
throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not a subclass of Sequelize.Model');
}
const source = this;
// Since this is a mixin, we'll need a unique letiable name for hooks (since Model will override our hooks option)
options = options || {};
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks);
options.useHooks = options.hooks;
// the id is in the foreign table
const association = new Type(source, target, _.extend(options, source.options));
source.associations[association.associationAccessor] = association.injectAttributes();
if (association.mixin) {
association.mixin(source.prototype);
} else {
association.injectGetter(source.prototype);
association.injectSetter(source.prototype);
association.injectCreator(source.prototype);
}
return association;
};
}
/**
* Creates an association between this (the source) and the provided target. The foreign key is added on the target.
*
* Example: `User.hasOne(Profile)`. This will add userId to the profile table.
*
* @method hasOne
* @param {Model} target
* @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 {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 associated models. Defaults to the singularized name of target
* @param {string|object} [options.foreignKey] The name of the foreign key in the target 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 source + primary key of source
* @param {string} [options.onDelete='SET NULL|CASCADE'] SET NULL if foreignKey allows nulls, CASCADE if otherwise
* @param {string} [options.onUpdate='CASCADE']
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
*/
Mixin.hasOne = singleLinked(HasOne);
/**
* Creates an association between this (the source) and the provided target. The foreign key is added on the source.
*
* Example: `Profile.belongsTo(User)`. This will add userId to the profile table.
*
* @method belongsTo
* @param {Model} target
* @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 {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 associated 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 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|NO ACTION'] SET NULL if foreignKey allows nulls, NO ACTION if otherwise
* @param {string} [options.onUpdate='CASCADE']
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
*/
Mixin.belongsTo = singleLinked(BelongsTo);
module.exports = Mixin;
module.exports.Mixin = Mixin;
module.exports.default = Mixin;
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!