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

Commit cdf00c41 by tornillo

Association mixins refactoring

Fix es6 syntax, docs and changelog
1 parent 95d3a571
......@@ -4,6 +4,7 @@
- [FIXED] Issue with query generation in MSSQL, an identifier was not escaped [#6686] (https://github.com/sequelize/sequelize/pull/6686)
- [FIXED] GroupedLimit when foreignKey has a field alias
- [FIXED] groupedLimit.through.where support
- [FIXED] Issue with overrriding custom methods with association mixins (all association methods are now exposed) [#6682](https://github.com/sequelize/sequelize/issues/6682)
# 4.0.0-2
- [ADDED] include now supports string as an argument (on top of model/association), string will expand into an association matched literally from Model.associations
......
......@@ -444,6 +444,18 @@ class BelongsToMany extends Association {
return this;
}
mixin(obj) {
const methods = ['get', 'count', 'hasSingle', 'hasAll', 'set', 'add', 'addMultiple', 'remove', 'removeMultiple', 'create'];
const aliases = {
hasSingle: 'has',
hasAll: 'has',
addMultiple: 'add',
removeMultiple: 'remove'
};
Helpers.mixinMethods(this, obj, methods, aliases);
}
get(instance, options) {
options = Utils.cloneDeep(options) || {};
......@@ -503,264 +515,251 @@ class BelongsToMany extends Association {
return model.findAll(options);
}
injectGetter(obj) {
count(instance, options) {
const association = this;
const model = association.target;
const sequelize = model.sequelize;
options = Utils.cloneDeep(options);
options.attributes = [
[sequelize.fn('COUNT', sequelize.col([association.target.name, model.primaryKeyAttribute].join('.'))), 'count']
];
options.joinTableAttributes = [];
options.raw = true;
options.plain = true;
return association.get(instance, options).then(result => parseInt(result.count, 10));
}
obj[this.accessors.get] = function(options) {
return association.get(this, options);
};
obj[this.accessors.count] = function(options) {
const model = association.target;
const sequelize = model.sequelize;
options = Utils.cloneDeep(options);
options.attributes = [
[sequelize.fn('COUNT', sequelize.col([association.target.name, model.primaryKeyAttribute].join('.'))), 'count']
];
options.joinTableAttributes = [];
options.raw = true;
options.plain = true;
has(sourceInstance, instances, options) {
const association = this;
const where = {};
return obj[association.accessors.get].call(this, options).then(result => parseInt(result.count, 10));
};
if (!Array.isArray(instances)) {
instances = [instances];
}
obj[this.accessors.hasSingle] = obj[this.accessors.hasAll] = function(instances, options) {
const where = {};
options = _.assign({
raw: true
}, options, {
scope: false
});
if (!Array.isArray(instances)) {
instances = [instances];
where.$or = instances.map(instance => {
if (instance instanceof association.target) {
return instance.where();
} else {
const where = {};
where[association.target.primaryKeyAttribute] = instance;
return where;
}
});
options = _.assign({
raw: true
}, options, {
scope: false
});
where.$or = instances.map(instance => {
if (instance instanceof association.target) {
return instance.where();
} else {
const where = {};
where[association.target.primaryKeyAttribute] = instance;
return where;
}
});
options.where = {
$and: [
where,
options.where
]
};
return this[association.accessors.get](options).then(associatedObjects => associatedObjects.length === instances.length);
options.where = {
$and: [
where,
options.where
]
};
return this;
return association.get(sourceInstance, options).then(associatedObjects => associatedObjects.length === instances.length);
}
injectSetter(obj) {
const association = this;
obj[this.accessors.set] = function(newAssociatedObjects, options) {
options = options || {};
const sourceKey = association.source.primaryKeyAttribute;
const targetKey = association.target.primaryKeyAttribute;
const identifier = association.identifier;
const foreignIdentifier = association.foreignIdentifier;
const where = {};
if (newAssociatedObjects === null) {
newAssociatedObjects = [];
} else {
newAssociatedObjects = association.toInstanceArray(newAssociatedObjects);
}
where[identifier] = this.get(sourceKey);
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']);
set(sourceInstance, newAssociatedObjects, options) {
options = options || {};
const unassociatedObjects = newAssociatedObjects.filter(obj =>
!_.find(currentRows, currentRow => currentRow[foreignIdentifier] === obj.get(targetKey))
);
const association = this;
const sourceKey = association.source.primaryKeyAttribute;
const targetKey = association.target.primaryKeyAttribute;
const identifier = association.identifier;
const foreignIdentifier = association.foreignIdentifier;
const where = {};
if (newAssociatedObjects === null) {
newAssociatedObjects = [];
} else {
newAssociatedObjects = association.toInstanceArray(newAssociatedObjects);
}
for (const currentRow of currentRows) {
const newObj = _.find(newAssociatedObjects, obj => currentRow[foreignIdentifier] === obj.get(targetKey));
where[identifier] = sourceInstance.get(sourceKey);
return association.through.model.findAll(_.defaults({where, raw: true}, options)).then(currentRows => {
const obsoleteAssociations = [];
const promises = [];
let defaultAttributes = options;
if (!newObj) {
obsoleteAssociations.push(currentRow);
} else {
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 = {};
}
// Don't try to insert the transaction as an attribute in the through table
defaultAttributes = _.omit(defaultAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
const where = {};
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
const unassociatedObjects = newAssociatedObjects.filter(obj =>
!_.find(currentRows, currentRow => currentRow[foreignIdentifier] === obj.get(targetKey))
);
where[identifier] = this.get(sourceKey);
where[foreignIdentifier] = newObj.get(targetKey);
for (const currentRow of currentRows) {
const newObj = _.find(newAssociatedObjects, obj => currentRow[foreignIdentifier] === obj.get(targetKey));
if (Object.keys(attributes).length) {
promises.push(association.through.model.update(attributes, _.extend(options, {where})));
}
if (!newObj) {
obsoleteAssociations.push(currentRow);
} else {
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 = {};
}
}
if (obsoleteAssociations.length > 0) {
const where = {};
where[identifier] = this.get(sourceKey);
where[foreignIdentifier] = obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]);
promises.push(association.through.model.destroy(_.defaults({where}, options)));
}
if (unassociatedObjects.length > 0) {
const bulk = unassociatedObjects.map(unassociatedObject => {
let attributes = {};
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
attributes[identifier] = this.get(sourceKey);
attributes[foreignIdentifier] = unassociatedObject.get(targetKey);
where[identifier] = sourceInstance.get(sourceKey);
where[foreignIdentifier] = newObj.get(targetKey);
attributes = _.defaults(attributes, unassociatedObject[association.through.model.name], defaultAttributes);
if (Object.keys(attributes).length) {
promises.push(association.through.model.update(attributes, _.extend(options, {where})));
}
}
}
_.assign(attributes, association.through.scope);
if (obsoleteAssociations.length > 0) {
const where = {};
where[identifier] = sourceInstance.get(sourceKey);
where[foreignIdentifier] = obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]);
return attributes;
});
promises.push(association.through.model.destroy(_.defaults({where}, options)));
}
promises.push(association.through.model.bulkCreate(bulk, _.assign({ validate: true }, options)));
}
if (unassociatedObjects.length > 0) {
const bulk = unassociatedObjects.map(unassociatedObject => {
let attributes = {};
return Utils.Promise.all(promises);
});
};
attributes[identifier] = sourceInstance.get(sourceKey);
attributes[foreignIdentifier] = unassociatedObject.get(targetKey);
obj[this.accessors.addMultiple] = obj[this.accessors.add] = function(newInstances, additionalAttributes) {
// If newInstances is null or undefined, no-op
if (!newInstances) return Utils.Promise.resolve();
attributes = _.defaults(attributes, unassociatedObject[association.through.model.name], defaultAttributes);
additionalAttributes = _.clone(additionalAttributes) || {};
_.assign(attributes, association.through.scope);
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;
return attributes;
});
newInstances = association.toInstanceArray(newInstances);
promises.push(association.through.model.bulkCreate(bulk, _.assign({ validate: true }, options)));
}
const where = {};
where[identifier] = this.get(sourceKey);
where[foreignIdentifier] = newInstances.map(newInstance => newInstance.get(targetKey));
return Utils.Promise.all(promises);
});
}
_.assign(where, association.through.scope);
add(sourceInstance, newInstances, additionalAttributes) {
// If newInstances is null or undefined, no-op
if (!newInstances) return Utils.Promise.resolve();
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));
additionalAttributes = _.clone(additionalAttributes) || {};
if (!existingAssociation) {
unassociatedObjects.push(obj);
} else {
const throughAttributes = obj[association.through.model.name];
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
const association = this;
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);
const where = {};
where[identifier] = sourceInstance.get(sourceKey);
where[foreignIdentifier] = newInstances.map(newInstance => newInstance.get(targetKey));
_.assign(where, association.through.scope);
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 {
const throughAttributes = obj[association.through.model.name];
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
if (_.some(Object.keys(attributes), attribute => attributes[attribute] !== existingAssociation[attribute])) {
changedAssociations.push(obj);
}
if (_.some(Object.keys(attributes), attribute => attributes[attribute] !== existingAssociation[attribute])) {
changedAssociations.push(obj);
}
}
}
if (unassociatedObjects.length > 0) {
const bulk = unassociatedObjects.map(unassociatedObject => {
const throughAttributes = unassociatedObject[association.through.model.name];
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
if (unassociatedObjects.length > 0) {
const bulk = unassociatedObjects.map(unassociatedObject => {
const throughAttributes = unassociatedObject[association.through.model.name];
const attributes = _.defaults({}, throughAttributes, defaultAttributes);
attributes[identifier] = this.get(sourceKey);
attributes[foreignIdentifier] = unassociatedObject.get(targetKey);
attributes[identifier] = sourceInstance.get(sourceKey);
attributes[foreignIdentifier] = unassociatedObject.get(targetKey);
_.assign(attributes, association.through.scope);
_.assign(attributes, association.through.scope);
return attributes;
});
return attributes;
});
promises.push(association.through.model.bulkCreate(bulk, _.assign({ validate: true }, options)));
}
promises.push(association.through.model.bulkCreate(bulk, _.assign({ validate: true }, options)));
}
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 = {};
}
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] = this.get(sourceKey);
where[foreignIdentifier] = assoc.get(targetKey);
where[identifier] = sourceInstance.get(sourceKey);
where[foreignIdentifier] = assoc.get(targetKey);
promises.push(association.through.model.update(attributes, _.extend(options, {where})));
}
promises.push(association.through.model.update(attributes, _.extend(options, {where})));
}
return Utils.Promise.all(promises);
});
};
return Utils.Promise.all(promises);
});
}
obj[this.accessors.removeMultiple] = obj[this.accessors.remove] = function(oldAssociatedObjects, options) {
options = options || {};
remove(sourceInstance, oldAssociatedObjects, options) {
const association = this;
oldAssociatedObjects = association.toInstanceArray(oldAssociatedObjects);
options = options || {};
const where = {};
where[association.identifier] = this.get(association.source.primaryKeyAttribute);
where[association.foreignIdentifier] = oldAssociatedObjects.map(newInstance => newInstance.get(association.target.primaryKeyAttribute));
oldAssociatedObjects = association.toInstanceArray(oldAssociatedObjects);
return association.through.model.destroy(_.defaults({where}, options));
};
const where = {};
where[association.identifier] = sourceInstance.get(association.source.primaryKeyAttribute);
where[association.foreignIdentifier] = oldAssociatedObjects.map(newInstance => newInstance.get(association.target.primaryKeyAttribute));
return this;
return association.through.model.destroy(_.defaults({where}, options));
}
injectCreator(obj) {
create(sourceInstance, values, options) {
const association = this;
obj[this.accessors.create] = function(values, options) {
options = options || {};
values = values || {};
options = options || {};
values = values || {};
if (Array.isArray(options)) {
options = {
fields: options
};
}
if (Array.isArray(options)) {
options = {
fields: options
};
}
if (association.scope) {
_.assign(values, association.scope);
if (options.fields) {
options.fields = options.fields.concat(Object.keys(association.scope));
}
if (association.scope) {
_.assign(values, association.scope);
if (options.fields) {
options.fields = options.fields.concat(Object.keys(association.scope));
}
}
// Create the related model instance
return association.target.create(values, options).then(newAssociatedObject =>
this[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject)
);
};
return this;
// Create the related model instance
return association.target.create(values, options).then(newAssociatedObject =>
sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject)
);
}
}
......
......@@ -140,14 +140,9 @@ class BelongsTo extends Association {
}
mixin(obj) {
const association = this;
obj[this.accessors.get] = function(options) {
return association.get(this, options);
};
const methods = ['get', 'set', 'create'];
association.injectSetter(obj);
association.injectCreator(obj);
Helpers.mixinMethods(this, obj, methods);
}
get(instances, options) {
......@@ -206,57 +201,47 @@ class BelongsTo extends Association {
return result;
});
}
return Target.findOne(options);
}
// Add setAssociation method to the prototype of the model instance
injectSetter(instancePrototype) {
set(sourceInstance, associatedInstance, options) {
const association = this;
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
options = options || {};
options = options || {};
let value = associatedInstance;
if (associatedInstance instanceof association.target) {
value = associatedInstance[association.targetKey];
}
this.set(association.foreignKey, value);
if (options.save === false) return;
let value = associatedInstance;
if (associatedInstance instanceof association.target) {
value = associatedInstance[association.targetKey];
}
options = _.extend({
fields: [association.foreignKey],
allowNull: [association.foreignKey],
association: true
}, options);
sourceInstance.set(association.foreignKey, value);
if (options.save === false) return;
// passes the changed field to save, so only that field get updated.
return this.save(options);
};
options = _.extend({
fields: [association.foreignKey],
allowNull: [association.foreignKey],
association: true
}, options);
return this;
// passes the changed field to save, so only that field get updated.
return sourceInstance.save(options);
}
// Add createAssociation method to the prototype of the model instance
injectCreator(instancePrototype) {
create(sourceInstance, values, fieldsOrOptions) {
const association = this;
instancePrototype[this.accessors.create] = function(values, fieldsOrOptions) {
const options = {};
if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction;
}
options.logging = (fieldsOrOptions || {}).logging;
const options = {};
return association.target.create(values, fieldsOrOptions).then(newAssociatedObject =>
this[association.accessors.set](newAssociatedObject, options)
);
};
if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction;
}
options.logging = (fieldsOrOptions || {}).logging;
return this;
return association.target.create(values, fieldsOrOptions).then(newAssociatedObject =>
sourceInstance[association.accessors.set](newAssociatedObject, options)
);
}
}
......
......@@ -233,37 +233,15 @@ class HasMany extends Association {
}
mixin(obj) {
const association = this;
obj[this.accessors.get] = function(options) {
return association.get(this, options);
};
if (this.accessors.count) {
obj[this.accessors.count] = function(options) {
return association.count(this, options);
};
}
obj[this.accessors.hasSingle] = obj[this.accessors.hasAll] = function(instances, options) {
return association.has(this, instances, options);
const methods = ['get', 'count', 'hasSingle', 'hasAll', 'set', 'add', 'addMultiple', 'remove', 'removeMultiple', 'create'];
const aliases = {
hasSingle: 'has',
hasAll: 'has',
addMultiple: 'add',
removeMultiple: 'remove'
};
obj[this.accessors.set] = function(instances, options) {
return association.set(this, instances, options);
};
obj[this.accessors.add] = obj[this.accessors.addMultiple] = function(instances, options) {
return association.add(this, instances, options);
};
obj[this.accessors.remove] = obj[this.accessors.removeMultiple] = function(instances, options) {
return association.remove(this, instances, options);
};
obj[this.accessors.create] = function(values, options) {
return association.create(this, values, options);
};
Helpers.mixinMethods(this, obj, methods, aliases);
}
get(instances, options) {
......@@ -351,7 +329,7 @@ class HasMany extends Association {
options.raw = true;
options.plain = true;
return this.get(instance, options).then(result => parseInt(result.count, 10));
return association.get(instance, options).then(result => parseInt(result.count, 10));
}
has(sourceInstance, targetInstances, options) {
......@@ -384,7 +362,7 @@ class HasMany extends Association {
]
};
return this.get(sourceInstance, options).then(associatedObjects => associatedObjects.length === targetInstances.length);
return association.get(sourceInstance, options).then(associatedObjects => associatedObjects.length === targetInstances.length);
}
set(sourceInstance, targetInstances, options) {
......
......@@ -138,14 +138,9 @@ class HasOne extends Association {
}
mixin(obj) {
const association = this;
obj[this.accessors.get] = function(options) {
return association.get(this, options);
};
const methods = ['get', 'set', 'create'];
association.injectSetter(obj);
association.injectCreator(obj);
Helpers.mixinMethods(this, obj, methods);
}
get(instances, options) {
......@@ -206,71 +201,70 @@ class HasOne extends Association {
return Target.findOne(options);
}
injectSetter(instancePrototype) {
set(sourceInstance, associatedInstance, options) {
const association = this;
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
let alreadyAssociated;
let alreadyAssociated;
options = _.assign({}, options, {
scope: false
});
return this[association.accessors.get](options).then(oldInstance => {
// TODO Use equals method once #5605 is resolved
alreadyAssociated = oldInstance && associatedInstance && _.every(association.target.primaryKeyAttributes, attribute =>
oldInstance.get(attribute, {raw: true}) === (associatedInstance.get ? associatedInstance.get(attribute, {raw: true}) : associatedInstance)
);
if (oldInstance && !alreadyAssociated) {
oldInstance[association.foreignKey] = null;
return oldInstance.save(_.extend({}, options, {
fields: [association.foreignKey],
allowNull: [association.foreignKey],
association: true
}));
}
}).then(() => {
if (associatedInstance && !alreadyAssociated) {
if (!(associatedInstance instanceof association.target)) {
const tmpInstance = {};
tmpInstance[association.target.primaryKeyAttribute] = associatedInstance;
associatedInstance = association.target.build(tmpInstance, {
isNewRecord: false
});
}
_.assign(associatedInstance, association.scope);
associatedInstance.set(association.foreignKey, this.get(association.sourceIdentifier));
return associatedInstance.save(options);
options = _.assign({}, options, {
scope: false
});
return sourceInstance[association.accessors.get](options).then(oldInstance => {
// TODO Use equals method once #5605 is resolved
alreadyAssociated = oldInstance && associatedInstance && _.every(association.target.primaryKeyAttributes, attribute =>
oldInstance.get(attribute, {raw: true}) === (associatedInstance.get ? associatedInstance.get(attribute, {raw: true}) : associatedInstance)
);
if (oldInstance && !alreadyAssociated) {
oldInstance[association.foreignKey] = null;
return oldInstance.save(_.extend({}, options, {
fields: [association.foreignKey],
allowNull: [association.foreignKey],
association: true
}));
}
}).then(() => {
if (associatedInstance && !alreadyAssociated) {
if (!(associatedInstance instanceof association.target)) {
const tmpInstance = {};
tmpInstance[association.target.primaryKeyAttribute] = associatedInstance;
associatedInstance = association.target.build(tmpInstance, {
isNewRecord: false
});
}
return null;
});
};
return this;
_.assign(associatedInstance, association.scope);
associatedInstance.set(association.foreignKey, sourceInstance.get(association.sourceIdentifier));
return associatedInstance.save(options);
}
return null;
});
}
injectCreator(instancePrototype) {
create(sourceInstance, values, options) {
const association = this;
instancePrototype[this.accessors.create] = function(values, options) {
values = values || {};
options = options || {};
values = values || {};
options = options || {};
if (association.scope) {
for (const attribute of Object.keys(association.scope)) {
values[attribute] = association.scope[attribute];
if (options.fields) options.fields.push(attribute);
if (association.scope) {
for (const attribute of Object.keys(association.scope)) {
values[attribute] = association.scope[attribute];
if (options.fields) {
options.fields.push(attribute);
}
}
}
values[association.foreignKey] = this.get(association.sourceIdentifier);
if (options.fields) options.fields.push(association.foreignKey);
return association.target.create(values, options);
};
values[association.foreignKey] = sourceInstance.get(association.sourceIdentifier);
if (options.fields) {
options.fields.push(association.foreignKey);
}
return this;
return association.target.create(values, options);
}
}
......
......@@ -44,3 +44,30 @@ function addForeignKeyConstraints(newAttribute, source, target, options, key) {
}
}
exports.addForeignKeyConstraints = addForeignKeyConstraints;
/**
* Mixin (inject) association methods to model prototype
*
* @param {Object} Association instance
* @param {Object} Model prototype
* @param {Array} Method names to inject
* @param {Object} Mapping between model and association method names
*/
function mixinMethods(association, obj, methods, aliases) {
aliases = aliases || {};
for (const method of methods) {
// don't override custom methods
if (!obj[association.accessors[method]]) {
const realMethod = aliases[method] || method;
obj[association.accessors[method]] = function() {
const instance = this;
const args = [instance].concat(Array.from(arguments));
return association[realMethod].apply(association, args);
};
}
}
}
exports.mixinMethods = mixinMethods;
......@@ -201,11 +201,10 @@ const Mixin = {
// the id is in the foreign table or in a connecting table
const association = new BelongsToMany(sourceModel, targetModel, options);
sourceModel.associations[association.associationAccessor] = association.injectAttributes();
sourceModel.associations[association.associationAccessor] = association;
association.injectGetter(sourceModel.prototype);
association.injectSetter(sourceModel.prototype);
association.injectCreator(sourceModel.prototype);
association.injectAttributes();
association.mixin(sourceModel.prototype);
return association;
},
......@@ -241,15 +240,10 @@ function singleLinked(Type) {
// the id is in the foreign table
const association = new Type(source, target, _.extend(options, source.options));
source.associations[association.associationAccessor] = association.injectAttributes();
source.associations[association.associationAccessor] = association;
if (association.mixin) {
association.mixin(source.prototype);
} else {
association.injectGetter(source.prototype);
association.injectSetter(source.prototype);
association.injectCreator(source.prototype);
}
association.injectAttributes();
association.mixin(source.prototype);
return association;
};
......
......@@ -58,6 +58,39 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() {
expect(AB.options.validate).to.deep.equal({});
});
it('should not override custom methods with association mixin', function(){
const methods = {
getTasks: 'get',
countTasks: 'count',
hasTask: 'has',
hasTasks: 'has',
setTasks: 'set',
addTask: 'add',
addTasks: 'add',
removeTask: 'remove',
removeTasks: 'remove',
createTask: 'create',
};
const User = current.define('User');
const Task = current.define('Task');
current.Utils._.each(methods, (alias, method) => {
User.prototype[method] = function () {
const realMethod = this.constructor.associations.task[alias];
expect(realMethod).to.be.a('function');
return realMethod;
};
});
User.belongsToMany(Task, { through: 'UserTasks', as: 'task' });
const user = User.build();
current.Utils._.each(methods, (alias, method) => {
expect(user[method]()).to.be.a('function');
});
});
describe('timestamps', function () {
it('follows the global timestamps true option', function () {
var User = current.define('User', {})
......
'use strict';
/* jshint -W030 */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, current = Support.sequelize;
describe(Support.getTestDialectTeaser('belongsTo'), function() {
it('should not override custom methods with association mixin', function(){
const methods = {
getTask : 'get',
setTask: 'set',
createTask: 'create'
};
const User = current.define('User');
const Task = current.define('Task');
current.Utils._.each(methods, (alias, method) => {
User.prototype[method] = function () {
const realMethod = this.constructor.associations.task[alias];
expect(realMethod).to.be.a('function');
return realMethod;
};
});
User.belongsTo(Task, { as: 'task' });
const user = User.build();
current.Utils._.each(methods, (alias, method) => {
expect(user[method]()).to.be.a('function');
});
});
});
......@@ -84,6 +84,37 @@ describe(Support.getTestDialectTeaser('hasMany'), function() {
expect(obj[association.accessors.hasAll]).to.be.an('function');
expect(obj[association.accessors.count]).to.be.an('function');
});
it('should not override custom methods', function(){
const methods = {
getTasks: 'get',
countTasks: 'count',
hasTask: 'has',
hasTasks: 'has',
setTasks: 'set',
addTask: 'add',
addTasks: 'add',
removeTask: 'remove',
removeTasks: 'remove',
createTask: 'create',
};
current.Utils._.each(methods, (alias, method) => {
User.prototype[method] = function () {
const realMethod = this.constructor.associations.task[alias];
expect(realMethod).to.be.a('function');
return realMethod;
};
});
User.hasMany(Task, { as: 'task' });
const user = User.build();
current.Utils._.each(methods, (alias, method) => {
expect(user[method]()).to.be.a('function');
});
});
});
describe('get', function () {
......
......@@ -18,4 +18,30 @@ describe(Support.getTestDialectTeaser('hasOne'), function() {
User.hasOne(Task, {as : 'Shabda'});
expect(Task.attributes.ShabdaId).not.to.be.empty;
});
it('should not override custom methods with association mixin', function(){
const methods = {
getTask : 'get',
setTask: 'set',
createTask: 'create'
};
const User = current.define('User');
const Task = current.define('Task');
current.Utils._.each(methods, (alias, method) => {
User.prototype[method] = function () {
const realMethod = this.constructor.associations.task[alias];
expect(realMethod).to.be.a('function');
return realMethod;
};
});
User.hasOne(Task, { as: 'task' });
const user = User.build();
current.Utils._.each(methods, (alias, method) => {
expect(user[method]()).to.be.a('function');
});
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!