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

Commit a4c10f8c by Mick Hansen

Refactor how doubleLink is determined

1 parent ea8ed2bb
...@@ -7,42 +7,92 @@ var HasManySingleLinked = require("./has-many-single-linked") ...@@ -7,42 +7,92 @@ 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
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 +109,27 @@ module.exports = (function() { ...@@ -59,27 +109,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 +142,15 @@ module.exports = (function() { ...@@ -92,23 +142,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()
......
...@@ -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,15 +38,16 @@ Mixin.belongsTo = function(associatedDAO, options) { ...@@ -38,15 +38,16 @@ 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()
associatedDAOFactory.daoFactoryManager.sequelize.associations.push(association)
association.injectGetter(this.DAO.prototype) association.injectGetter(this.DAO.prototype)
association.injectSetter(this.DAO.prototype) association.injectSetter(this.DAO.prototype)
......
...@@ -114,6 +114,7 @@ module.exports = (function() { ...@@ -114,6 +114,7 @@ module.exports = (function() {
this.daoFactoryManager = new DAOFactoryManager(this) this.daoFactoryManager = new DAOFactoryManager(this)
this.transactionManager = new TransactionManager(this) this.transactionManager = new TransactionManager(this)
this.associations = []
this.importCache = {} this.importCache = {}
} }
......
...@@ -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() {
...@@ -900,6 +898,73 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -900,6 +898,73 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
}) })
describe('as', 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.query("SHOW TABLES LIKE 'group_user'").success(function (res) {
expect(res).to.deep.equal(['group_user'])
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.query("SHOW TABLES LIKE 'user_groups'").success(function (res) {
expect(res).to.deep.equal(['user_groups'])
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;
done();
})
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;
done();
})
})
}) })
describe("Foreign key constraints", function() { describe("Foreign key constraints", function() {
......
...@@ -83,6 +83,7 @@ var Support = { ...@@ -83,6 +83,7 @@ var Support = {
.dropAllTables() .dropAllTables()
.success(function() { .success(function() {
sequelize.daoFactoryManager.daos = [] sequelize.daoFactoryManager.daos = []
sequelize.associations = []
callback && callback() callback && callback()
}) })
.error(function(err) { console.log(err) }) .error(function(err) { console.log(err) })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!