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

Commit a883058c by Sascha Depold

Merge pull request #1147 from mickhansen/has-many-alias-fix

Alias support for double linked hasMany / Refactor how double linked associations are determined
2 parents ea8ed2bb d7a26747
...@@ -2,8 +2,8 @@ var Utils = require('./../utils') ...@@ -2,8 +2,8 @@ var Utils = require('./../utils')
, Transaction = require('./../transaction') , Transaction = require('./../transaction')
module.exports = (function() { module.exports = (function() {
var HasManyDoubleLinked = function(definition, instance) { var HasManyDoubleLinked = function(association, instance) {
this.__factory = definition this.association = association
this.instance = instance this.instance = instance
// Alias the quoting methods for code brevity // Alias the quoting methods for code brevity
...@@ -17,32 +17,32 @@ module.exports = (function() { ...@@ -17,32 +17,32 @@ module.exports = (function() {
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {} var where = {}
, through = self.__factory.through , through = self.association.through
, options = _options || {} , options = _options || {}
, queryOptions = {} , queryOptions = {}
, association = self.__factory.target.associations[self.__factory.associationAccessor] , targetAssociation = self.association.targetAssociation
//fully qualify //fully qualify
var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys) var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys)
, instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id' , instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id'
where[through.tableName+"."+self.__factory.identifier] = self.instance[instancePrimaryKey] where[through.tableName+"."+self.association.identifier] = self.instance[instancePrimaryKey]
var primaryKeys = Object.keys(through.primaryKeys) var primaryKeys = Object.keys(through.primaryKeys)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0] , foreignKey = primaryKeys.filter(function(pk) { return pk != self.association.identifier })[0]
, foreignPrimary = Object.keys(self.__factory.target.primaryKeys) , foreignPrimary = Object.keys(self.association.target.primaryKeys)
foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id' foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id'
where[through.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+"."+foreignPrimary} where[through.tableName+"."+foreignKey] = {join: self.association.target.tableName+"."+foreignPrimary}
if (Object(association.through) === association.through) { if (Object(targetAssociation.through) === targetAssociation.through) {
queryOptions.hasJoinTableModel = true queryOptions.hasJoinTableModel = true
queryOptions.joinTableModel = through queryOptions.joinTableModel = through
if (!options.attributes) { if (!options.attributes) {
options.attributes = [ options.attributes = [
self.QueryInterface.quoteIdentifier(self.__factory.target.tableName)+".*" self.QueryInterface.quoteIdentifier(self.association.target.tableName)+".*"
] ]
} }
...@@ -65,14 +65,14 @@ module.exports = (function() { ...@@ -65,14 +65,14 @@ module.exports = (function() {
if (options.where) { if (options.where) {
if (Array.isArray(options.where)) { if (Array.isArray(options.where)) {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect) smart = Utils.smartWhere([where, options.where], self.association.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect) smart = Utils.compileSmartWhere.call(self.association.target, smart, self.association.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) { if (smart.length > 0) {
options.where = smart options.where = smart
} }
} else { } else {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect) smart = Utils.smartWhere([where, options.where], self.association.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect) smart = Utils.compileSmartWhere.call(self.association.target, smart, self.association.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) { if (smart.length > 0) {
options.where = smart options.where = smart
} }
...@@ -81,7 +81,7 @@ module.exports = (function() { ...@@ -81,7 +81,7 @@ module.exports = (function() {
options.where = where; options.where = where;
} }
self.__factory.target.findAllJoin(through.tableName, options, queryOptions) self.association.target.findAllJoin(through.tableName, options, queryOptions)
.on('success', function(objects) { customEventEmitter.emit('success', objects) }) .on('success', function(objects) { customEventEmitter.emit('success', objects) })
.on('error', function(err){ customEventEmitter.emit('error', err) }) .on('error', function(err){ customEventEmitter.emit('error', err) })
.on('sql', function(sql) { customEventEmitter.emit('sql', sql)}) .on('sql', function(sql) { customEventEmitter.emit('sql', sql)})
...@@ -93,10 +93,10 @@ module.exports = (function() { ...@@ -93,10 +93,10 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) { HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) {
var self = this var self = this
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor] , targetAssociation = self.association.targetAssociation
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier , foreignIdentifier = targetAssociation.isSelfAssociation ? targetAssociation.foreignIdentifier : targetAssociation.identifier
, sourceKeys = Object.keys(self.__factory.source.primaryKeys) , sourceKeys = Object.keys(self.association.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys) , targetKeys = Object.keys(self.association.target.primaryKeys)
, obsoleteAssociations = [] , obsoleteAssociations = []
, changedAssociations = [] , changedAssociations = []
, options = {} , options = {}
...@@ -120,13 +120,13 @@ module.exports = (function() { ...@@ -120,13 +120,13 @@ module.exports = (function() {
if (!newObj) { if (!newObj) {
obsoleteAssociations.push(old) obsoleteAssociations.push(old)
} else if (Object(association.through) === association.through) { } else if (Object(targetAssociation.through) === targetAssociation.through) {
var changedAssociation = { var changedAssociation = {
where: {}, where: {},
attributes: Utils._.defaults({}, newObj[self.__factory.through.name], defaultAttributes) attributes: Utils._.defaults({}, newObj[self.association.through.name], defaultAttributes)
} }
changedAssociation.where[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id changedAssociation.where[self.association.identifier] = self.instance[self.association.identifier] || self.instance.id
changedAssociation.where[foreignIdentifier] = newObj[foreignIdentifier] || newObj.id changedAssociation.where[foreignIdentifier] = newObj[foreignIdentifier] || newObj.id
changedAssociations.push(changedAssociation) changedAssociations.push(changedAssociation)
...@@ -140,32 +140,32 @@ module.exports = (function() { ...@@ -140,32 +140,32 @@ module.exports = (function() {
var where = {} var where = {}
where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) where[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.through.destroy(where, options)) chainer.add(self.association.through.destroy(where, options))
} }
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) { var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {} var attributes = {}
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) attributes[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
if (Object(association.through) === association.through) { if (Object(targetAssociation.through) === targetAssociation.through) {
attributes = Utils._.defaults(attributes, unassociatedObject[association.through.name], defaultAttributes) attributes = Utils._.defaults(attributes, unassociatedObject[targetAssociation.through.name], defaultAttributes)
} }
return attributes return attributes
}) })
chainer.add(self.__factory.through.bulkCreate(bulk, options)) chainer.add(self.association.through.bulkCreate(bulk, options))
} }
if (changedAssociations.length > 0) { if (changedAssociations.length > 0) {
changedAssociations.forEach(function (assoc) { changedAssociations.forEach(function (assoc) {
chainer.add(self.__factory.through.update(assoc.attributes, assoc.where, options)) chainer.add(self.association.through.update(assoc.attributes, assoc.where, options))
}) })
} }
...@@ -177,27 +177,25 @@ module.exports = (function() { ...@@ -177,27 +177,25 @@ module.exports = (function() {
} }
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) { HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) {
var attributes = {} var attributes = {}
, association = this.__factory.target.associations[this.__factory.associationAccessor] , targetAssociation = this.association.targetAssociation
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier; , foreignIdentifier = targetAssociation.isSelfAssociation ? targetAssociation.foreignIdentifier : targetAssociation.identifier;
var sourceKeys = Object.keys(this.__factory.source.primaryKeys); var sourceKeys = Object.keys(this.association.source.primaryKeys);
var targetKeys = Object.keys(this.__factory.target.primaryKeys); var targetKeys = Object.keys(this.association.target.primaryKeys);
attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id) attributes[this.association.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id)
if (exists) { // implies hasJoinTableModel === true if (exists) { // implies hasJoinTableModel === true
var where = attributes var where = attributes
attributes = Utils._.defaults({}, newAssociation[association.through.name], additionalAttributes) attributes = Utils._.defaults({}, newAssociation[association.through.name], additionalAttributes)
association.through.update(attributes, where).proxy(emitterProxy) targetAssociation.through.update(attributes, where).proxy(emitterProxy)
} else { } else {
if (Object(association.through) === association.through) { attributes = Utils._.defaults(attributes, newAssociation[targetAssociation.through.name], additionalAttributes)
attributes = Utils._.defaults(attributes, newAssociation[association.through.name], additionalAttributes)
}
this.__factory.through.create(attributes) this.association.through.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) }) .success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) }) .error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) }) .on('sql', function(sql) { emitterProxy.emit('sql', sql) })
......
...@@ -7,42 +7,98 @@ var HasManySingleLinked = require("./has-many-single-linked") ...@@ -7,42 +7,98 @@ var HasManySingleLinked = require("./has-many-single-linked")
, HasManyMultiLinked = require("./has-many-double-linked") , HasManyMultiLinked = require("./has-many-double-linked")
module.exports = (function() { module.exports = (function() {
var HasMany = function(srcDAO, targetDAO, options) { var HasMany = function(source, target, options) {
var self = this
this.associationType = 'HasMany' this.associationType = 'HasMany'
this.source = srcDAO this.source = source
this.target = targetDAO this.target = target
this.targetAssociation = null
this.options = options this.options = options
this.sequelize = source.daoFactoryManager.sequelize
this.through = options.through this.through = options.through
this.isSelfAssociation = (this.source.tableName === this.target.tableName) this.isSelfAssociation = (this.source.tableName === this.target.tableName)
this.doubleLinked = false
// Map to through for BC this.combinedTableName = Utils.combineTableNames(
this.source.tableName,
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
)
/*
* Map joinTableModel/Name to through for BC
*/
if (this.through === undefined) { if (this.through === undefined) {
this.through = this.options.joinTableModel || this.options.joinTableName; this.through = this.options.joinTableModel || this.options.joinTableName;
/*
* If both are undefined, see if useJunctionTable was false (for self associations) - else assume through to be true
*/
if (this.through === undefined) {
if (this.options.useJunctionTable === false) {
this.through = null;
} else {
this.through = true;
}
}
} }
if (typeof this.through === "string") { /*
this.through = this.source.daoFactoryManager.sequelize.define(this.through, {}, _.extend(this.options, { * Determine associationAccessor, especially for include options to identify the correct model
tableName: this.through */
}))
this.associationAccessor = this.options.as
if (!this.associationAccessor && (typeof this.through === "string" || Object(this.through) === this.through)) {
this.associationAccessor = this.through.tableName || this.through
}
else if (!this.associationAccessor) {
this.associationAccessor = this.combinedTableName
} }
if (this.through === undefined) { /*
if (this.options.useJunctionTable === false) { * Find partner DAOFactory if present, to identify double linked association
this.through = null; */
} else { if (this.through) {
this.through = true; _.each(this.target.associations, function (association, accessor) {
} if (self.source === association.target) {
var paired = false
// If through is default, we determine pairing by the accesor value (i.e. DAOFactory's using as won't pair, but regular ones will)
if (self.through === true && accessor === self.associationAccessor) {
paired = true
}
// If through is not default, determine pairing by through value (model/string)
if (self.through !== true && self.options.through === association.options.through) {
paired = true
}
// If paired, set properties identifying both associations as double linked, and allow them to each eachtoerh
if (paired) {
self.doubleLinked = true
association.doubleLinked = true
self.targetAssociation = association
association.targetAssociation = self
}
}
})
} }
if (this.through === true) { /*
this.through = Utils.combineTableNames( * If we are double linked, and through is either default or a string, we create the through model and set it on both associations
this.source.tableName, */
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName if (this.doubleLinked) {
) if (this.through === true) {
this.through = this.combinedTableName
}
if (typeof this.through === "string") {
this.through = this.sequelize.define(this.through, {}, _.extend(this.options, {
tableName: this.through
}))
this.targetAssociation.through = this.through
}
} }
this.options.tableName = this.combinedName = (this.through === Object(this.through) ? this.through.tableName : this.through) this.options.tableName = this.combinedName = (this.through === Object(this.through) ? this.through.tableName : this.through)
this.associationAccessor = this.options.as || this.combinedName
var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language)) var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
...@@ -59,27 +115,27 @@ module.exports = (function() { ...@@ -59,27 +115,27 @@ module.exports = (function() {
// the id is in the target table // the id is in the target table
// or in an extra table which connects two tables // or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() { HasMany.prototype.injectAttributes = function() {
var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor) var doubleLinked = this.doubleLinked
, self = this , self = this
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
// is there already a single sided association between the source and the target? // is there already a single sided association between the source and the target?
// or is the association on the model itself? // or is the association on the model itself?
if ((this.isSelfAssociation && this.through) || multiAssociation) { if ((this.isSelfAssociation && this.through) || doubleLinked) {
// remove the obsolete association identifier from the source // remove the obsolete association identifier from the source
if (this.isSelfAssociation) { if (this.isSelfAssociation) {
this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored) this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored)
} else { } else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier this.foreignIdentifier = this.targetAssociation.identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier this.targetAssociation.foreignIdentifier = this.identifier
if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) { if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[this.foreignIdentifier] delete this.source.rawAttributes[this.foreignIdentifier]
} }
if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) { if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) {
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier] delete this.targetAssociation.source.rawAttributes[this.identifier]
} }
} }
...@@ -92,23 +148,15 @@ module.exports = (function() { ...@@ -92,23 +148,15 @@ module.exports = (function() {
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true} combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true} combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true}
if (Object(this.through) === this.through) { // remove any previously defined PKs
// remove any previously defined PKs Utils._.each(this.through.attributes, function(dataTypeString, attributeName) {
Utils._.each(this.through.attributes, function(dataTypeString, attributeName) { if (dataTypeString.toString().indexOf('PRIMARY KEY') !== -1) {
if (dataTypeString.toString().indexOf('PRIMARY KEY') !== -1) { delete self.through.rawAttributes[attributeName]
delete self.through.rawAttributes[attributeName] }
} })
})
this.through.rawAttributes = Utils._.merge(this.through.rawAttributes, combinedTableAttributes)
this.through.init(this.through.daoFactoryManager)
} else {
this.through = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
}
if (!this.isSelfAssociation) { this.through.rawAttributes = Utils._.merge(this.through.rawAttributes, combinedTableAttributes)
this.target.associations[this.associationAccessor].through = this.through this.through.init(this.through.daoFactoryManager)
}
if (this.options.syncOnAssociation) { if (this.options.syncOnAssociation) {
this.through.sync() this.through.sync()
...@@ -189,7 +237,7 @@ module.exports = (function() { ...@@ -189,7 +237,7 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]() instance[self.accessors.get]()
.success(function(oldAssociatedObjects) { .success(function(oldAssociatedObjects) {
var Class = Object(self.through) === self.through ? HasManyMultiLinked : HasManySingleLinked var Class = self.doubleLinked ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectSetter(emitter, oldAssociatedObjects, newAssociatedObjects, defaultAttributes) new Class(self, instance).injectSetter(emitter, oldAssociatedObjects, newAssociatedObjects, defaultAttributes)
}) })
.error(function(err) { .error(function(err) {
...@@ -214,7 +262,7 @@ module.exports = (function() { ...@@ -214,7 +262,7 @@ module.exports = (function() {
.error(function(err){ emitter.emit('error', err)}) .error(function(err){ emitter.emit('error', err)})
.success(function(currentAssociatedObjects) { .success(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0 || self.hasJoinTableModel === true) { if (currentAssociatedObjects.length === 0 || self.hasJoinTableModel === true) {
var Class = Object(self.through) === self.through ? HasManyMultiLinked : HasManySingleLinked var Class = self.doubleLinked ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectAdder(emitter, newAssociatedObject, additionalAttributes, !!currentAssociatedObjects.length) new Class(self, instance).injectAdder(emitter, newAssociatedObject, additionalAttributes, !!currentAssociatedObjects.length)
} else { } else {
emitter.emit('success', newAssociatedObject); emitter.emit('success', newAssociatedObject);
......
...@@ -6,14 +6,14 @@ var Utils = require("./../utils") ...@@ -6,14 +6,14 @@ var Utils = require("./../utils")
/* Defines Mixin for all models. */ /* Defines Mixin for all models. */
var Mixin = module.exports = function(){} var Mixin = module.exports = function(){}
Mixin.hasOne = function(associatedDAO, options) { Mixin.hasOne = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option) // Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {} options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks) options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks options.useHooks = options.hooks
// the id is in the foreign table // the id is in the foreign table
var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasOne(this, associatedDAOFactory, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype); association.injectGetter(this.DAO.prototype);
...@@ -22,14 +22,14 @@ Mixin.hasOne = function(associatedDAO, options) { ...@@ -22,14 +22,14 @@ Mixin.hasOne = function(associatedDAO, options) {
return this return this
} }
Mixin.belongsTo = function(associatedDAO, options) { Mixin.belongsTo = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option) // Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {} options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks) options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks options.useHooks = options.hooks
// the id is in this table // the id is in this table
var association = new BelongsTo(this, associatedDAO, Utils._.extend(options, this.options)) var association = new BelongsTo(this, associatedDAOFactory, Utils._.extend(options, this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype) association.injectGetter(this.DAO.prototype)
...@@ -38,14 +38,14 @@ Mixin.belongsTo = function(associatedDAO, options) { ...@@ -38,14 +38,14 @@ Mixin.belongsTo = function(associatedDAO, options) {
return this return this
} }
Mixin.hasMany = function(associatedDAO, options) { Mixin.hasMany = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option) // Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {} options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks) options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks options.useHooks = options.hooks
// the id is in the foreign table or in a connecting table // the id is in the foreign table or in a connecting table
var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasMany(this, associatedDAOFactory, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype) association.injectGetter(this.DAO.prototype)
......
...@@ -64,7 +64,8 @@ ...@@ -64,7 +64,8 @@
"chai-spies": "~0.5.1", "chai-spies": "~0.5.1",
"lcov-result-merger": "0.0.2", "lcov-result-merger": "0.0.2",
"istanbul": "~0.1.45", "istanbul": "~0.1.45",
"coveralls": "~2.5.0" "coveralls": "~2.5.0",
"async": "~0.2.9"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
...@@ -84,4 +85,4 @@ ...@@ -84,4 +85,4 @@
"node": ">=0.6.21" "node": ">=0.6.21"
}, },
"license": "MIT" "license": "MIT"
} }
\ No newline at end of file
...@@ -676,10 +676,8 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -676,10 +676,8 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
{ tableName: 'tasks' } { tableName: 'tasks' }
) )
this.User.hasMany(this.Task, this.User.hasMany(this.Task, { joinTableName: 'user_has_tasks' })
{ joinTableName: 'user_has_tasks' } this.Task.hasMany(this.User, { joinTableName: 'user_has_tasks' })
)
this.Task.hasMany(this.User)
this.User.sync({ force: true }).success(function() { this.User.sync({ force: true }).success(function() {
self.Task.sync({force: true}).success(function() { self.Task.sync({force: true}).success(function() {
...@@ -702,7 +700,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -702,7 +700,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
expect(tableName).to.equal(associationName) expect(tableName).to.equal(associationName)
} }
} }
done() setTimeout(function () {
done()
}, 50)
}) })
}) })
...@@ -900,6 +900,77 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -900,6 +900,77 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
}) })
describe('alias', function () {
it("creates the join table when through is a string", function (done) {
var self = this
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
User.hasMany(Group, { as: 'MyGroups', through: 'group_user'})
Group.hasMany(User, { as: 'MyUsers', through: 'group_user'})
this.sequelize.sync({force:true}).success(function () {
self.sequelize.getQueryInterface().showAllTables().success(function (result) {
expect(result.indexOf('group_user')).not.to.equal(-1)
done()
})
})
})
it("creates the join table when through is a model", function (done) {
var self = this
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
, UserGroup = this.sequelize.define('GroupUser', {}, {tableName: 'user_groups'})
User.hasMany(Group, { as: 'MyGroups', through: UserGroup})
Group.hasMany(User, { as: 'MyUsers', through: UserGroup})
this.sequelize.sync({force:true}).success(function () {
self.sequelize.getQueryInterface().showAllTables().success(function (result) {
expect(result.indexOf('user_groups')).not.to.equal(-1)
done()
})
})
})
it("correctly identifies its counterpart when through is a string", function (done) {
var self = this
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
User.hasMany(Group, { as: 'MyGroups', through: 'group_user'})
Group.hasMany(User, { as: 'MyUsers', through: 'group_user'})
expect(Group.associations.MyUsers.through === User.associations.MyGroups.through);
expect(Group.associations.MyUsers.through.rawAttributes.UserId).to.exist;
expect(Group.associations.MyUsers.through.rawAttributes.GroupId).to.exist;
setTimeout(function () {
done()
}, 50)
})
it("correctly identifies its counterpart when through is a model", function (done) {
var self = this
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
, UserGroup = this.sequelize.define('GroupUser', {}, {tableName: 'user_groups'})
User.hasMany(Group, { as: 'MyGroups', through: UserGroup})
Group.hasMany(User, { as: 'MyUsers', through: UserGroup})
expect(Group.associations.MyUsers.through === User.associations.MyGroups.through);
expect(Group.associations.MyUsers.through.rawAttributes.UserId).to.exist;
expect(Group.associations.MyUsers.through.rawAttributes.GroupId).to.exist;
setTimeout(function () {
done()
}, 50)
})
})
}) })
describe("Foreign key constraints", function() { describe("Foreign key constraints", function() {
......
...@@ -11,6 +11,7 @@ var chai = require('chai') ...@@ -11,6 +11,7 @@ var chai = require('chai')
, datetime = require('chai-datetime') , datetime = require('chai-datetime')
, _ = require('lodash') , _ = require('lodash')
, moment = require('moment') , moment = require('moment')
, async = require('async')
chai.use(datetime) chai.use(datetime)
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
...@@ -2451,6 +2452,120 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2451,6 +2452,120 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
}) })
describe('hasMany (N:M) with alias', function () {
beforeEach(function (done) {
this.Product = this.sequelize.define('Product', { title: Sequelize.STRING })
this.Tag = this.sequelize.define('Tag', { name: Sequelize.STRING })
done();
})
it('returns the associated models when using through as string and alias', function (done) {
var self = this
this.Product.hasMany(this.Tag, {as: 'Tags', through: 'product_tag'})
this.Tag.hasMany(this.Product, {as: 'Products', through: 'product_tag'})
this.sequelize.sync().done(function (err) {
async.auto({
createProducts: function (callback) {
self.Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Handbag'},
{title: 'Dress'},
{title: 'Jan'}
]).done(callback)
},
// bulkCreate doesn't include id for some reason, not going to fix tis now
products: ['createProducts', function (callback) {
self.Product.findAll().done(callback)
}],
createTags: function (callback) {
self.Tag.bulkCreate([
{title: 'Furniture'},
{title: 'Clothing'},
{title: 'People'}
]).done(callback)
},
tags: ['createTags', function (callback) {
self.Tag.findAll().done(callback)
}],
}, function (err, results) {
expect(err).not.to.exist
var products = results.products
, tags = results.tags
async.parallel([
function (callback) {
products[0].setTags([tags[0], tags[1]]).done(callback)
},
function (callback) {
products[1].addTag(tags[0]).done(callback)
},
function (callback) {
products[2].addTag(tags[1]).done(callback)
},
function (callback) {
products[3].setTags([tags[1]]).done(callback)
},
function (callback) {
products[4].setTags([tags[2]]).done(callback)
}
], function (err) {
expect(err).not.to.exist
async.parallel([
function (callback) {
self.Tag.find({
where: {
id: tags[0].id
},
include: [
{model: self.Product, as: 'Products'}
]
}).done(function (err, tag) {
expect(tag).to.exist
expect(tag.products.length).to.equal(2)
callback()
})
},
function (callback) {
tags[1].getProducts().done(function (err, products) {
expect(products.length).to.equal(3)
callback()
})
},
function (callback) {
self.Product.find({
where: {
id: products[0].id
},
include: [
{model: self.Tag, as: 'Tags'}
]
}).done(function (err, product) {
expect(product).to.exist
expect(product.tags.length).to.equal(2)
callback()
})
},
function (callback) {
products[1].getTags().done(function (err, tags) {
expect(tags.length).to.equal(1)
callback()
})
},
], done)
})
})
})
})
it('returns the associated models when using through as model and alias')
})
}) })
describe('queryOptions', function() { describe('queryOptions', function() {
......
...@@ -38,12 +38,16 @@ if (dialect.match(/^postgres/)) { ...@@ -38,12 +38,16 @@ if (dialect.match(/^postgres/)) {
it("should not use a combined name", function(done) { it("should not use a combined name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist
done() setTimeout(function () {
done()
}, 50)
}) })
it("should use the specified name", function(done) { it("should use the specified name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist
done() setTimeout(function () {
done()
}, 50)
}) })
}) })
}) })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!