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

Commit be7dfd48 by Jan Aagaard Meier

Removed synconassocation, added ondelete and onupdate to n:m and made cascade th…

…e default, made cascade / set null the default for hasone and belongsto
1 parent c9f39ebe
......@@ -2,6 +2,8 @@ Notice: All 1.7.x changes are present in 2.0.x aswell
# v2.0.0-dev11
- [PERFORMANCE] increased build performance when using include, which speeds up findAll etc.
- [FEATURE] n:m now marks the columns of the through table as foreign keys and cascades them on delete and update by default.
- [FEATURE] 1:1 and 1:m marks columns as foreign keys, and sets them to cascade on update and set null on delete. If you are working with an existing DB which does not allow null values, be sure to override those options, or disable them completely by passing useConstrations: false to your assocation call (`M1.belongsTo(M2, { useConstraints: false})`).
#### Backwards compatability changes
- selectedValues has been removed for performance reasons, if you depend on this, please open an issue and we will help you work around it.
......@@ -9,6 +11,8 @@ Notice: All 1.7.x changes are present in 2.0.x aswell
- if you have any 1:1 relations where both sides use an alias, you'll need to set the foreign key, or they'll each use a different foreign key based on their alias.
- foreign keys for non-id primary keys will now be named for the foreign key, i.e. pub_name rather than pub_id
- if you have non-id primary keys you should go through your associations and set the foreignKey option if relying on a incorrect _id foreign key
- syncOnAssocation has been removed. It only worked for n:m, and having a synchronous function (hasMany) that invokes an asynchronous function (sync) without returning an emitter does not make a lot of sense. If you (implicitly) depended on this feature, sequelize.sync is your friend. If you do not want to do a full sync, use custom through models for n:m (`M1.hasMany(M2, { through: M3})`) and sync the through model explicitly.
- Join tables will be no longer be paranoid (have a deletedAt timestamp added), even though other models are.
# v1.7.0
- [FEATURE] covers more advanced include cases with limiting and filtering (specifically cases where a include would be in the subquery but its child include wouldnt be, for cases where a 1:1 association had a 1:M association as a nested include)
......
......@@ -44,6 +44,10 @@ module.exports = (function() {
var newAttributes = {}
newAttributes[this.identifier] = { type: this.options.keyType || this.target.rawAttributes[this.targetIdentifier].type }
if (this.options.useConstraints !== false) {
this.options.onDelete = this.options.onDelete || 'SET NULL'
this.options.onUpdate = this.options.onUpdate || 'CASCADE'
}
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes)
......
......@@ -103,7 +103,8 @@ module.exports = (function() {
if (typeof this.through === "string") {
this.through = this.sequelize.define(this.through, {}, _.extend(this.options, {
tableName: this.through
tableName: this.through,
paranoid: false // A paranoid join table does not make sense
}))
if (this.targetAssociation) {
......@@ -174,28 +175,46 @@ module.exports = (function() {
// define a new model, which connects the models
var combinedTableAttributes = {}
var sourceKeyType = this.source.rawAttributes[this.source.primaryKeyAttribute].type
var targetKeyType = this.target.rawAttributes[this.target.primaryKeyAttribute].type
, sourceKeyType = this.source.rawAttributes[this.source.primaryKeyAttribute].type
, targetKeyType = this.target.rawAttributes[this.target.primaryKeyAttribute].type
, sourceAttribute = { type: sourceKeyType }
, targetAttribute = { type: targetKeyType }
if (this.options.useConstraints !== false) {
sourceAttribute.references = this.source.getTableName()
sourceAttribute.referencesKey = this.source.primaryKeyAttribute
sourceAttribute.onDelete = this.options.onDelete || 'CASCADE'
sourceAttribute.onUpdate = this.options.onUpdate || 'CASCADE'
}
if (this.targetAssociation.options.useConstraints !== false) {
targetAttribute.references = this.target.getTableName()
targetAttribute.referencesKey = this.target.primaryKeyAttribute
targetAttribute.onDelete = this.targetAssociation.options.onDelete || 'CASCADE'
targetAttribute.onUpdate = this.targetAssociation.options.onUpdate || 'CASCADE'
}
if (primaryKeyDeleted) {
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true}
targetAttribute.primaryKey = sourceAttribute.primaryKey = true
} else {
var uniqueKey = [this.through.tableName, this.identifier, this.foreignIdentifier, 'unique'].join('_')
combinedTableAttributes[this.identifier] = {type: sourceKeyType, unique: uniqueKey}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, unique: uniqueKey}
targetAttribute.unique = sourceAttribute.unique = uniqueKey
}
combinedTableAttributes[this.identifier] = sourceAttribute
combinedTableAttributes[this.foreignIdentifier] = targetAttribute
this.through.rawAttributes = Utils._.merge(this.through.rawAttributes, combinedTableAttributes)
this.through.init(this.through.daoFactoryManager)
if (this.options.syncOnAssociation) {
this.through.sync()
}
} else {
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
newAttributes[this.identifier] = { type: this.options.keyType || this.target.rawAttributes[this.target.primaryKeyAttribute].type }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
if (this.options.useConstraints !== false) {
constraintOptions.onDelete = constraintOptions.onDelete || 'SET NULL'
constraintOptions.onUpdate = constraintOptions.onUpdate || 'CASCADE'
}
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, constraintOptions)
Utils._.defaults(this.target.rawAttributes, newAttributes)
}
......
......@@ -45,6 +45,11 @@ module.exports = (function() {
newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Utils._.defaults(this.target.rawAttributes, newAttributes)
if (this.options.useConstraints !== false) {
this.options.onDelete = this.options.onDelete || 'SET NULL'
this.options.onUpdate = this.options.onUpdate || 'CASCADE'
}
Helpers.addForeignKeyConstraints(this.target.rawAttributes[this.identifier], this.source, this.target, this.options)
// Sync attributes and setters/getters to DAO prototype
......
var Toposort = require('toposort-class')
, DaoFactory = require('./dao-factory')
, _ = require('lodash')
module.exports = (function() {
var DAOFactoryManager = function(sequelize) {
......@@ -39,9 +40,14 @@ module.exports = (function() {
* take foreign key constraints into account so that dependencies are visited
* before dependents.
*/
DAOFactoryManager.prototype.forEachDAO = function(iterator) {
DAOFactoryManager.prototype.forEachDAO = function(iterator, options) {
var daos = {}
, sorter = new Toposort()
, sorted
options = _.defaults(options || {}, {
reverse: true
})
this.daos.forEach(function(dao) {
var deps = []
......@@ -62,7 +68,11 @@ module.exports = (function() {
sorter.add(dao.tableName, deps)
})
sorter.sort().reverse().forEach(function(name) {
sorted = sorter.sort()
if (options.reverse) {
sorted = sorted.reverse()
}
sorted.forEach(function(name) {
iterator(daos[name], name)
})
}
......
......@@ -19,7 +19,6 @@ module.exports = (function() {
validate: {},
freezeTableName: false,
underscored: false,
syncOnAssociation: true,
paranoid: false,
whereCollection: null,
schema: null,
......@@ -1609,7 +1608,15 @@ module.exports = (function() {
var optClone = function (options) {
return Utils._.cloneDeep(options, function (elem) {
// The DAOFactories used for include are pass by ref, so don't clone them.
if (elem instanceof DAOFactory || elem instanceof Utils.col || elem instanceof Utils.literal || elem instanceof Utils.cast || elem instanceof Utils.fn || elem instanceof Utils.and || elem instanceof Utils.or) {
if (elem instanceof DAOFactory ||
elem instanceof Utils.col ||
elem instanceof Utils.literal ||
elem instanceof Utils.cast ||
elem instanceof Utils.fn ||
elem instanceof Utils.and ||
elem instanceof Utils.or ||
elem instanceof Transaction
) {
return elem
}
// Unfortunately, lodash.cloneDeep doesn't preserve Buffer.isBuffer, which we have to rely on for binary data
......
......@@ -54,7 +54,15 @@ module.exports = (function() {
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr)
if (Utils._.includes(dataType, 'REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
var m = dataType.match(/^(.+) (REFERENCES.*)$/)
attrStr.push(this.quoteIdentifier(attr) + " " + m[1].replace(/PRIMARY KEY/, ''))
foreignKeys[attr] = m[2]
} else {
attrStr.push(this.quoteIdentifier(attr) + " " + dataType.replace(/PRIMARY KEY/, ''))
}
} else if (Utils._.includes(dataType, 'REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
var m = dataType.match(/^(.+) (REFERENCES.*)$/)
......
......@@ -387,7 +387,7 @@ module.exports = (function() {
// Topologically sort by foreign key constraints to give us an appropriate
// creation order
this.daoFactoryManager.forEachDAO(function(dao, daoName) {
this.daoFactoryManager.forEachDAO(function(dao) {
if (dao) {
chainer.add(dao, 'sync', [options])
} else {
......@@ -402,12 +402,12 @@ module.exports = (function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer
var chainer = new Utils.QueryChainer()
self.daoFactoryManager.daos.forEach(function(dao) { chainer.add(dao.drop()) })
self.daoFactoryManager.forEachDAO(function(dao) { chainer.add(dao, 'drop', []) }, { reverse: false})
chainer
.run()
.runSerially()
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('error', err) })
}).run()
......
......@@ -239,19 +239,41 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
})
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
it("are enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: DataTypes.STRING })
, User = this.sequelize.define('User', { username: DataTypes.STRING })
Task.belongsTo(User)
Task.belongsTo(User) // defaults to SET NULL
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks).to.have.length(1)
task.reload().success(function() {
expect(task.UserId).to.equal(null)
done()
})
})
})
})
})
})
})
it("should be possible to disable them", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, { useConstraints: false })
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().success(function() {
task.reload().success(function() {
expect(task.UserId).to.equal(user.id)
done()
})
})
......@@ -403,7 +425,7 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
var tableName = 'TaskXYZ_' + dataType.toString()
Tasks[dataType] = self.sequelize.define(tableName, { title: DataTypes.STRING })
Tasks[dataType].belongsTo(User, { foreignKey: 'userId', keyType: dataType })
Tasks[dataType].belongsTo(User, { foreignKey: 'userId', keyType: dataType, useConstraints: false })
})
self.sequelize.sync({ force: true })
......
......@@ -27,19 +27,15 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
describe('(1:N)', function() {
describe('hasSingle', function() {
beforeEach(function(done) {
var self = this
this.Article = this.sequelize.define('Article', { 'title': DataTypes.STRING })
this.Label = this.sequelize.define('Label', { 'text': DataTypes.STRING })
this.Article.hasMany(this.Label)
this.Label.sync({ force: true }).success(function() {
self.Article.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
......@@ -48,6 +44,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Article.hasMany(Label)
sequelize.drop().success(function () {
sequelize.sync({ force: true }).success(function() {
Article.create({ title: 'foo' }).success(function(article) {
Label.create({ text: 'bar' }).success(function(label) {
......@@ -71,10 +68,10 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
it('does not have any labels assigned to it initially', function(done) {
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Articl2e' }),
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
this.Label.create({ text: 'Epicness' })
......@@ -130,12 +127,10 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
this.Article.hasMany(this.Label)
this.Label.sync({ force: true }).success(function() {
self.Article.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
......@@ -144,6 +139,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Article.hasMany(Label)
sequelize.drop().success(function () {
sequelize.sync({ force: true }).success(function() {
Article.create({ title: 'foo' }).success(function(article) {
Label.create({ text: 'bar' }).success(function(label) {
......@@ -167,6 +163,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
it('answers false if only some labels have been assigned', function(done) {
var chainer = new Sequelize.Utils.QueryChainer([
......@@ -242,8 +239,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Task.hasMany(User)
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUsers([ user ]).success(function() {
......@@ -263,7 +259,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
describe('addAssociations', function() {
it('supports transactions', function(done) {
......@@ -307,8 +302,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Task.hasMany(User)
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUsers([ user ]).success(function() {
......@@ -327,7 +321,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
describe('createAssociations', function() {
it('creates a new associated object', function(done) {
......@@ -391,8 +384,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
this.User.hasMany(self.Task)
this.User.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
var chainer = new Sequelize.Utils.QueryChainer([
self.User.create({ username: 'John'}),
self.Task.create({ title: 'Get rich', active: true}),
......@@ -406,7 +398,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
it('should treat the where object of associations as a first class citizen', function(done) {
var self = this
......@@ -420,8 +411,8 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
this.Article.hasMany(this.Label)
this.Label.sync({ force: true }).success(function() {
self.Article.sync({ force: true }).success(function() {
this.sequelize.drop().success(function () {
self.sequelize.sync({ force: true }).success(function() {
var chainer = new Sequelize.Utils.QueryChainer([
self.Article.create({ title: 'Article' }),
self.Label.create({ text: 'Awesomeness', until: '2014-01-01 01:00:00' }),
......@@ -549,11 +540,10 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
this.User = this.sequelize.define('User', { username: DataTypes.STRING })
this.Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN })
self.User.hasMany(self.Task)
self.Task.hasMany(self.User)
this.User.hasMany(this.Task)
this.Task.hasMany(this.User)
this.User.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
var chainer = new Sequelize.Utils.QueryChainer([
self.User.create({ username: 'John'}),
self.Task.create({ title: 'Get rich', active: true}),
......@@ -567,7 +557,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
......@@ -577,6 +566,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Article.hasMany(Label)
Label.hasMany(Article)
sequelize.drop().success(function () {
sequelize.sync({ force: true }).success(function() {
Article.create({ title: 'foo' }).success(function(article) {
Label.create({ text: 'bar' }).success(function(label) {
......@@ -600,6 +590,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
it("gets all associated objects when no options are passed", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) {
......@@ -658,8 +649,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
User.hasMany(Task)
Task.hasMany(User)
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUsers([ user ]).success(function() {
......@@ -678,7 +668,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
it("joins an association with custom primary keys", function(done) {
var Group = this.sequelize.define('group', {
......@@ -693,8 +682,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Group.hasMany(Member, {joinTableName: 'group_members', foreignKey: 'group_id'})
Member.hasMany(Group, {joinTableName: 'group_members', foreignKey: 'member_id'})
Group.sync({ force: true }).success(function() {
Member.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
Group.create({group_id: 1, name: 'Group1'}).success(function(){
Member.create({member_id: 10, email: 'team@sequelizejs.com'}).success(function() {
Group.find(1).success(function(group) {
......@@ -715,7 +703,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
describe('createAssociations', function() {
it('creates a new associated object', function(done) {
......@@ -725,8 +712,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
User.hasMany(Task)
Task.hasMany(User)
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
Task.create({ title: 'task' }).success(function(task) {
task.createUser({ username: 'foo' }).success(function() {
task.getUsers().success(function(_users) {
......@@ -738,7 +724,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
it('supports transactions', function(done) {
var self = this
......@@ -749,8 +734,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
User.hasMany(Task)
Task.hasMany(User)
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
sequelize.sync({ force: true }).success(function() {
Task.create({ title: 'task' }).success(function(task) {
sequelize.transaction(function (t) {
task.createUser({ username: 'foo' }, { transaction: t }).success(function() {
......@@ -769,7 +753,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
describe('addAssociations', function() {
it('supports transactions', function(done) {
......@@ -810,12 +793,10 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
this.User.hasMany(this.Task)
this.Task.hasMany(this.User)
this.User.sync({ force: true }).success(function() {
self.Task.sync({force: true}).success(function() {
this.sequelize.sync({force: true}).success(function() {
done()
})
})
})
it('uses one insert into statement', function (done) {
var self = this
......@@ -880,7 +861,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
it('uses the specified joinTableName or a reasonable default', function(done) {
it('uses the specified joinTableName or a reasonable default', function() {
for (var associationName in this.User.associations) {
expect(associationName).not.to.equal(this.User.tableName)
expect(associationName).not.to.equal(this.Task.tableName)
......@@ -894,9 +875,26 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
expect(tableName).to.equal(associationName)
}
}
setTimeout(function () {
done()
}, 50)
})
it('makes join table non-paranoid by default', function () {
var paranoidSequelize = new Sequelize('','','', {
define: {
paranoid: true
}
})
, ParanoidUser = paranoidSequelize.define('ParanoidUser', {})
, ParanoidTask = paranoidSequelize.define('ParanoidTask', {})
ParanoidUser.hasMany(ParanoidTask)
ParanoidTask.hasMany(ParanoidUser)
expect(ParanoidUser.options.paranoid).to.be.ok
expect(ParanoidTask.options.paranoid).to.be.ok
_.forEach(ParanoidUser.associations, function (association) {
expect(association.through.options.paranoid).not.to.be.ok
})
})
})
......@@ -1360,20 +1358,20 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
describe('1:m', function () {
it("sets null by default", function(done) {
var Task = this.sequelize.define('Task', { title: DataTypes.STRING })
, User = this.sequelize.define('User', { username: DataTypes.STRING })
User.hasMany(Task)
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks).to.have.length(1)
task.reload().success(function() {
expect(task.UserId).to.equal(null)
done()
})
})
......@@ -1382,6 +1380,27 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
it("should be possible to remove all constraints", function(done) {
var Task = this.sequelize.define('Task', { title: DataTypes.STRING })
, User = this.sequelize.define('User', { username: DataTypes.STRING })
User.hasMany(Task, { useConstraints: false })
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().success(function() {
task.reload().success(function() {
expect(task.UserId).to.equal(user.id)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
......@@ -1498,6 +1517,156 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
describe('n:m', function () {
beforeEach(function () {
this.Task = this.sequelize.define('task', { title: DataTypes.STRING })
this.User = this.sequelize.define('user', { username: DataTypes.STRING })
this.UserTasks = this.sequelize.define('tasksusers', { userId: DataTypes.INTEGER, taskId: DataTypes.INTEGER })
})
it("can cascade deletes both ways by default", function (done) {
var _done = _.after(2, done)
, self = this
self.User.hasMany(self.Task)
self.Task.hasMany(self.User)
this.sequelize.sync({ force: true }).success(function() {
self.User.create({ id: 67, username: 'foo' }).success(function(user) {
self.Task.create({ id: 52, title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().success(function() {
self.UserTasks.findAll({ where: { userId: user.id }}).success(function(usertasks) {
expect(usertasks).to.have.length(0)
_done()
})
})
})
})
})
self.User.create({ id: 89, username: 'bar' }).success(function(user) {
self.Task.create({ id: 42, title: 'kast' }).success(function(task) {
task.setUsers([user]).success(function() {
task.destroy().success(function() {
self.UserTasks.findAll({ where: { taskId: task.id }}).success(function(usertasks) {
expect(usertasks).to.have.length(0)
_done()
})
})
})
})
})
})
})
it("can restrict deletes both ways", function (done) {
var _done = _.after(2, done)
, self = this
self.User.hasMany(self.Task, { onDelete: 'RESTRICT'})
self.Task.hasMany(self.User, { onDelete: 'RESTRICT'})
this.sequelize.sync({ force: true }).success(function() {
self.User.create({ id: 67, username: 'foo' }).success(function(user) {
self.Task.create({ id: 52, title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().error(function() {
// This should error, because updates are restricted
_done()
})
})
})
})
self.User.create({ id: 89, username: 'bar' }).success(function(user) {
self.Task.create({ id: 42, title: 'kast' }).success(function(task) {
task.setUsers([user]).success(function() {
task.destroy().error(function () {
// This should error, because updates are restricted
_done()
})
})
})
})
})
})
it("can cascade and restrict deletes", function (done) {
var _done = _.after(2, done)
, self = this
self.User.hasMany(self.Task, { onDelete: 'RESTRICT'})
self.Task.hasMany(self.User) // Implicit CASCADE
this.sequelize.sync({ force: true }).success(function() {
self.User.create({ id: 67, username: 'foo' }).success(function(user) {
self.Task.create({ id: 52, title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().error(function() {
// This should error, because deletes are restricted
_done()
})
})
})
})
self.User.create({ id: 89, username: 'bar' }).success(function(user) {
self.Task.create({ id: 42, title: 'kast' }).success(function(task) {
task.setUsers([user]).success(function() {
task.destroy().success(function () {
self.UserTasks.findAll({ where: { taskId: task.id }}).success(function(usertasks) {
// This should not exist because deletes cascade
expect(usertasks).to.have.length(0)
_done()
})
})
})
})
})
})
})
it("should be possible to remove all constraints", function (done) {
var _done = _.after(2, done)
, self = this
self.User.hasMany(self.Task, { useConstraints: false })
self.Task.hasMany(self.User, { useConstraints: false })
this.sequelize.sync({ force: true }).success(function() {
self.User.create({ id: 67, username: 'foo' }).success(function(user) {
self.Task.create({ id: 52, title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().success(function () {
self.UserTasks.findAll({ where: { userId: user.id }}).success(function(usertasks) {
// When we're not using foreign keys, join table rows will be orphaned
expect(usertasks).to.have.length(1)
_done()
})
})
})
})
})
self.User.create({ id: 89, username: 'bar' }).success(function(user) {
self.Task.create({ id: 42, title: 'kast' }).success(function(task) {
task.setUsers([user]).success(function() {
task.destroy().success(function () {
self.UserTasks.findAll({ where: { taskId: task.id }}).success(function(usertasks) {
// When we're not using foreign keys, join table rows will be orphaned
expect(usertasks).to.have.length(1)
_done()
})
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING })
......@@ -1509,7 +1678,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
var tableName = 'TaskXYZ_' + dataType.toString()
Tasks[dataType] = self.sequelize.define(tableName, { title: DataTypes.STRING })
User.hasMany(Tasks[dataType], { foreignKey: 'userId', keyType: dataType })
User.hasMany(Tasks[dataType], { foreignKey: 'userId', keyType: dataType, useConstraints: false })
Tasks[dataType].sync({ force: true }).success(function() {
expect(Tasks[dataType].rawAttributes.userId.type.toString())
......
......@@ -212,11 +212,11 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
})
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
it("are enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task)
User.hasOne(Task) // defaults to set NULL
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
......@@ -224,8 +224,32 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks).to.have.length(1)
task.reload().success(function() {
expect(task.UserId).to.equal(null)
done()
})
})
})
})
})
})
})
})
it("should be possible to disable them", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, { useConstraints: false })
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().success(function() {
task.reload().success(function() {
expect(task.UserId).to.equal(user.id)
done()
})
})
......@@ -387,7 +411,7 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
var tableName = 'TaskXYZ_' + dataType.toString()
Tasks[dataType] = self.sequelize.define(tableName, { title: Sequelize.STRING })
User.hasOne(Tasks[dataType], { foreignKey: 'userId', keyType: dataType })
User.hasOne(Tasks[dataType], { foreignKey: 'userId', keyType: dataType, useConstraints: false })
Tasks[dataType].sync({ force: true }).success(function() {
expect(Tasks[dataType].rawAttributes.userId.type.toString())
......
......@@ -277,14 +277,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
this.init = function(callback) {
self.Task.sync({ force: true }).success(function() {
self.Worker.sync({ force: true }).success(function() {
self.sequelize.drop().success(function () {
self.sequelize.sync({ force: true }).success(function() {
self.Worker.create({ name: 'worker' }).success(function(worker) {
self.Task.create({ title: 'homework' }).success(function(task) {
self.worker = worker
self.task = task
callback()
})
})
})
})
......@@ -386,8 +387,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
self.User.belongsTo(self.Group)
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone', GroupPKeagerbelongName: 'people' }).success(function() {
self.Group.create({ name: 'people' }).success(function() {
self.User.create({ username: 'someone', GroupPKeagerbelongName: 'people' }).success(function() {
self.User.find({
where: {
username: 'someone'
......@@ -406,15 +407,12 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
it('getting parent data in many to one relationship', function(done) {
var self = this;
var User = self.sequelize.define('User', {
var User = this.sequelize.define('User', {
id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
username: {type: Sequelize.STRING}
})
var Message = self.sequelize.define('Message', {
var Message = this.sequelize.define('Message', {
id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
user_id: {type: Sequelize.INTEGER},
message: {type: Sequelize.STRING}
......@@ -423,8 +421,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
User.hasMany(Message)
Message.belongsTo(User, { foreignKey: 'user_id' })
Message.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
User.create({username: 'test_testerson'}).success(function(user) {
Message.create({user_id: user.id, message: 'hi there!'}).success(function(message) {
Message.create({user_id: user.id, message: 'a second message'}).success(function(message) {
......@@ -438,7 +435,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
include: [{ model: User, attributes: ['username'] }]
}).success(function(messages) {
expect(messages.length).to.equal(2);
expect(messages[0].message).to.equal('hi there!');
......@@ -450,14 +446,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done()
})
})
})
})
})
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
......@@ -527,9 +519,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
self.Group.hasOne(self.User)
self.sequelize.drop().success(function () {
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone', GroupPKeageroneName: 'people' }).success(function() {
self.Group.create({ name: 'people' }).success(function() {
self.User.create({ username: 'someone', GroupPKeageroneName: 'people' }).success(function() {
self.Group.find({
where: {
name: 'people'
......@@ -547,6 +540,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
})
describe('hasOne with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
......@@ -660,6 +654,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
self.Contact.hasMany(self.Photo, { as: 'Photos' })
self.Contact.hasMany(self.PhoneNumber)
self.sequelize.drop().success(function () {
self.sequelize.sync({ force: true }).success(function() {
self.Contact.create({ name: 'Boris' }).success(function(someContact) {
self.Photo.create({ img: 'img.jpg' }).success(function(somePhoto) {
......@@ -688,6 +683,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
})
it('eager loads with non-id primary keys', function(done) {
var self = this
......@@ -706,6 +702,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
self.Group.hasMany(self.User)
self.User.hasMany(self.Group)
self.sequelize.drop().success(function () {
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone' }).success(function(someUser) {
self.Group.create({ name: 'people' }).success(function(someGroup) {
......@@ -729,6 +726,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
})
describe('hasMany with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
......
......@@ -313,8 +313,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
User.hasMany(Session, { as: 'Sessions' })
Session.belongsTo(User)
Session.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
User.create({name: 'Name1', password: '123', isAdmin: false}).success(function(user) {
var sess = Session.build({
lastUpdate: new Date(),
......@@ -328,7 +327,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
})
it('should be able to find a row between a certain date', function(done) {
this.User.findAll({
......@@ -849,9 +847,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
self.Country.hasMany(self.Person, { as: 'Residents', foreignKey: 'CountryResidentId' })
self.Person.belongsTo(self.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' })
async.forEach([ self.Continent, self.Country, self.Industry, self.Person ], function(model, callback) {
model.sync({ force: true }).done(callback)
}, function () {
this.sequelize.sync({ force: true }).success(function () {
async.parallel({
europe: function(callback) {self.Continent.create({ name: 'Europe' }).done(callback)},
england: function(callback) {self.Country.create({ name: 'England' }).done(callback)},
......@@ -965,9 +961,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
self.Country.hasMany(self.Person, { as: 'Residents', foreignKey: 'CountryResidentId' })
self.Person.belongsTo(self.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' })
async.forEach([ self.Continent, self.Country, self.Person ], function(model, callback) {
model.sync({ force: true }).done(callback)
}, function () {
this.sequelize.sync({ force: true }).success(function () {
async.parallel({
europe: function(callback) {self.Continent.create({ name: 'Europe' }).done(callback)},
asia: function(callback) {self.Continent.create({ name: 'Asia' }).done(callback)},
......@@ -1120,9 +1114,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
self.Country.hasMany(self.Industry, {through: self.IndustryCountry})
self.Industry.hasMany(self.Country, {through: self.IndustryCountry})
async.forEach([ self.Country, self.Industry ], function(model, callback) {
model.sync({ force: true }).done(callback)
}, function () {
this.sequelize.sync({ force: true }).success(function () {
async.parallel({
england: function(callback) {self.Country.create({ name: 'England' }).done(callback)},
france: function(callback) {self.Country.create({ name: 'France' }).done(callback)},
......
......@@ -353,16 +353,14 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
})
Project.hasOne(Task)
Task.hasOne(Project)
Task.belongsTo(Project)
Project.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
self.Project = Project
self.Task = Task
done()
})
})
})
it('correctly throws an error using create method ', function(done) {
this.Project.create({name: 'nope'}).error(function(err) {
......
......@@ -6334,12 +6334,10 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.Projects.hasOne(this.Tasks, {onDelete: 'cascade', hooks: true})
this.Tasks.belongsTo(this.Projects)
this.Projects.sync({ force: true }).success(function() {
self.Tasks.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
})
describe('#remove', function() {
it('with no errors', function(done) {
......@@ -6733,12 +6731,10 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.Projects.hasMany(this.Tasks)
this.Tasks.belongsTo(this.Projects)
this.Projects.sync({ force: true }).success(function() {
self.Tasks.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
})
describe('#remove', function() {
it('with no errors', function(done) {
......@@ -6842,12 +6838,10 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.Projects.hasMany(this.Tasks, {cascade: 'onDelete', joinTableName: 'projects_and_tasks', hooks: true})
this.Tasks.hasMany(this.Projects, {cascade: 'onDelete', joinTableName: 'projects_and_tasks', hooks: true})
this.Projects.sync({ force: true }).success(function() {
self.Tasks.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
})
describe('#remove', function() {
it('with no errors', function(done) {
......@@ -6952,12 +6946,10 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.Projects.hasMany(this.Tasks, {hooks: true})
this.Tasks.hasMany(this.Projects, {hooks: true})
this.Projects.sync({ force: true }).success(function() {
self.Tasks.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
})
describe('#remove', function() {
it('with no errors', function(done) {
......
......@@ -57,8 +57,8 @@ if (Support.dialectIsMySQL()) {
this.users = null
this.tasks = null
this.User.hasMany(this.Task, {as:'Tasks'})
this.Task.hasMany(this.User, {as:'Users'})
this.User.hasMany(this.Task, {as:'Tasks', through: 'UserTasks'})
this.Task.hasMany(this.User, {as:'Users', through: 'UserTasks'})
var self = this
, users = []
......@@ -72,8 +72,7 @@ if (Support.dialectIsMySQL()) {
tasks[tasks.length] = {name: 'Task' + Math.random()}
}
this.User.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
self.User.bulkCreate(users).success(function() {
self.Task.bulkCreate(tasks).success(function() {
done()
......@@ -81,7 +80,6 @@ if (Support.dialectIsMySQL()) {
})
})
})
})
describe('addDAO / getDAO', function() {
beforeEach(function(done) {
......
......@@ -63,8 +63,8 @@ if (dialect.match(/^postgres/)) {
this.users = null
this.tasks = null
this.User.hasMany(this.Task, {as:'Tasks'})
this.Task.hasMany(this.User, {as:'Users'})
this.User.hasMany(this.Task, {as:'Tasks', through: 'usertasks'})
this.Task.hasMany(this.User, {as:'Users', through: 'usertasks'})
var self = this
, users = []
......@@ -78,8 +78,7 @@ if (dialect.match(/^postgres/)) {
tasks[tasks.length] = {name: 'Task' + Math.random()}
}
self.User.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
self.User.bulkCreate(users).success(function() {
self.Task.bulkCreate(tasks).success(function() {
self.User.all().success(function(_users) {
......@@ -93,7 +92,6 @@ if (dialect.match(/^postgres/)) {
})
})
})
})
it('should correctly add an association to the dao', function(done) {
var self = this
......@@ -122,8 +120,8 @@ if (dialect.match(/^postgres/)) {
this.users = null
this.tasks = null
this.User.hasMany(this.Task, {as:'Tasks'})
this.Task.hasMany(this.User, {as:'Users'})
this.User.hasMany(this.Task, {as:'Tasks', through: 'usertasks'})
this.Task.hasMany(this.User, {as:'Users', through: 'usertasks'})
for (var i = 0; i < 5; ++i) {
users[users.length] = {id: i+1, name: 'User' + Math.random()}
......@@ -133,8 +131,7 @@ if (dialect.match(/^postgres/)) {
tasks[tasks.length] = {id: x+1, name: 'Task' + Math.random()}
}
self.User.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
this.sequelize.sync({ force: true }).success(function() {
self.User.bulkCreate(users).done(function(err) {
expect(err).not.to.be.ok
self.Task.bulkCreate(tasks).done(function(err) {
......@@ -169,5 +166,4 @@ if (dialect.match(/^postgres/)) {
})
})
})
})
}
......@@ -7,10 +7,12 @@ var chai = require('chai')
, exec = require('child_process').exec
, version = (require(__dirname + '/../package.json')).version
, path = require('path')
, os = require('os')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Executable"), function() {
if (os.type().toLowerCase().indexOf('windows') === -1) {
describe(Support.getTestDialectTeaser("Executable"), function() {
describe('call without arguments', function() {
it("prints usage instructions", function(done) {
exec('bin/sequelize', function(err, stdout, stderr) {
......@@ -433,4 +435,6 @@ describe(Support.getTestDialectTeaser("Executable"), function() {
})
})
})(['--url', '-U'])
})
})
}
var fs = require('fs')
, path = require('path')
, _ = require('lodash')
, Sequelize = require(__dirname + "/../index")
, DataTypes = require(__dirname + "/../lib/data-types")
, Config = require(__dirname + "/config/config")
......@@ -44,25 +45,14 @@ var Support = {
var config = Config[options.dialect]
options.logging = (options.hasOwnProperty('logging') ? options.logging : false)
options.pool = options.pool !== undefined ? options.pool : config.pool
var sequelizeOptions = {
var sequelizeOptions = _.defaults(options, {
host: options.host || config.host,
logging: options.logging,
logging: false,
dialect: options.dialect,
port: options.port || process.env.SEQ_PORT || config.port,
pool: options.pool,
pool: config.pool,
dialectOptions: options.dialectOptions || {}
}
if (!!options.define) {
sequelizeOptions.define = options.define
}
if (!!config.storage) {
sequelizeOptions.storage = config.storage
}
})
if (process.env.DIALECT === 'postgres-native') {
sequelizeOptions.native = true
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!