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

Commit 0299ce63 by Daniel Durante

Adds hooks / callbacks / lifecycle events

This commit adds the ability of hooks for the DAOFactory. The following
hooks (in their order of operations) are:

(1) beforeValidate(dao, fn)

(-) validate

(2) afterValidate(dao, fn)

(3) beforeBulkCreate(daos, fields, fn)
    beforeBulkDestroy(daos, fields, fn)
    beforeBulkUpdate(daos, fields, fn)

(4) beforeCreate(dao, fn)
    beforeDestroy(dao, fn)
    beforeUpdate(dao, fn)

(-) create / destroy / update

(5) afterCreate(dao, fn)
    aftreDestroy(dao, fn)
    afterUpdate(dao, fn)

(6) afterBulkCreate(daos, fields, fn)
    afterBulkDestory(daos, fields, fn)
    afterBulkUpdate(daos, fields, fn)

There's a new file called hooks.js which works very similar to
mixins.js which just extends a prototype.

You can add the hooks like so...

... via .define():

var User = sequelize.define('User', {
  username: DataTypes.STRING,
  mood: {
    type: DataTypes.ENUM,
    values: ['happy', 'sad', 'neutral']
  }
}, {
  hooks: {
    beforeValidate: function(user, fn) {
      user.mood = 'happy'
      fn(null, user)
    },
    afterValidate: function(user, fn) {
      user.username = 'Toni'
      fn(null, user)
    }
  }
})

... via .hook() method

var User = sequelize.define('User', {
  username: DataTypes.STRING,
  mood: {
    type: DataTypes.ENUM,
    values: ['happy', 'sad', 'neutral']
  }
})

User.hook('beforeValidate', function(user, fn) {
  user.mood = 'happy'
  fn(null, user)
})

User.hook('afterValidate', function(user, fn) {
  user.username = 'Toni'
  fn(null, user)
})

... via direct method:

var User = sequelize.define('User', {
  username: DataTypes.STRING,
  mood: {
    type: DataTypes.ENUM,
    values: ['happy', 'sad', 'neutral']
  }
})

User.beforeValidate(function(user, fn) {
  user.mood = 'happy'
  fn(null, user)
})

User.afterValidate(function(user, fn) {
  user.username = 'Toni'
  fn(null, user)
})

Quick example:

User.beforeCreate(function(user, fn) {
  if (user.accessLevel > 10 && user.username !== "Boss") {
    return fn("You can't grant this user that level!")
  }

  return fn()
})

User.create({
  username: 'Not a Boss',
  accessLevel: 20
}).error(function(err) {
  console.log(err) // You can't grant this user that level!
})

As of right now, each hook will process in the order they where
implemented / added to the factory.

To invoke the hooks simply run...

Model.runHooks.call(Model.options.hooks.<hook>, <args>, <callback>)

Some model hooks have two or three paramters sent to each hook
depending on it's type.

Model.beforeBulkCreate(function(records, fields, fn) {
  // records = the first argument sent to .bulkCreate
  // fields = the second argument sent to .bulkCreate
})

Model.bulkCreate([
  {username: 'Toni'}, // part of records argument
  {username: 'Tobi'} // part of records argument
], ['username'] /* part of fields argument */)

Model.beforeBulkUpdate(function(attributes, where, fn) {
  // attributes = first argument sent to Model.update
  // where = second argument sent to Model.update
})

Model.update(
  {gender: 'Male'} /*attribures argument*/,
  {username: 'Tom'} /*where argument*/
)

Model.beforeBulkDestroy(function(whereClause, fn) {
  // whereClause = first argument sent to Model.destroy
})

Model.destroy({username: 'Tom'} /*whereClause argument*/)

For 1.7.x backwards compatibility, I've added a new method called
.hookValidate() since .validate() is a synchronous function. All of
Sequelize's API functions will invoke .hookValidate(), but if you
utilize the .validate() function outside of Sequelize then you'll
need to update your code if you want to run before/afterValidate hooks.

Sequelzie 2.0.x will not need this change simply because it's
.validate() method is already asynchronous. However, it will have the
.hookValdate() function in order to make the transition from 1.7 to 2.0
smoother and easier. Eventually we'll want to deprecate this function.

In addition to this commit, I've also completed the following tasks:

Move validation of enum attribute value to validate method

I had to complete that task in order to get the validate hooks
working properly.

And the last thing, I fixed executables.test.js if your DB didn't use
the default values for config/config.js, this was causing errors for me
on my local machine.
1 parent 4339799d
...@@ -33,6 +33,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/ ...@@ -33,6 +33,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/
- Associations - Associations
- Importing definitions from single files - Importing definitions from single files
- Promises - Promises
- Hooks/callbacks/lifecycle events
## Documentation and Updates ## ## Documentation and Updates ##
...@@ -62,7 +63,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -62,7 +63,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- Support for update of tables without primary key - Support for update of tables without primary key
- MariaDB support - MariaDB support
- ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude - ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) - Eager loading of nested associations [#388](https://github.com/sequelize/sequelize/issues/388)
- ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy)) - ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
- ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango - ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- Move validation of enum attribute value to validate method - Move validation of enum attribute value to validate method
......
...@@ -14,6 +14,8 @@ module.exports = (function() { ...@@ -14,6 +14,8 @@ module.exports = (function() {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.source.options.language) + "Id", this.source.options.underscored) this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.source.options.language) + "Id", this.source.options.underscored)
} }
this.options.useHooks = options.useHooks
this.associationAccessor = this.isSelfAssociation this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName : this.options.as || this.target.tableName
...@@ -31,7 +33,7 @@ module.exports = (function() { ...@@ -31,7 +33,7 @@ module.exports = (function() {
Utils._.defaults(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes to DAO proto each time a new assoc is added
this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes); this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes)
return this return this
} }
......
...@@ -30,7 +30,7 @@ module.exports = (function() { ...@@ -30,7 +30,7 @@ module.exports = (function() {
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this var self = this
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {}).daoFactory.primaryKeys || {}) , associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {daoFactory: {primaryKeys: {}}}).daoFactory.primaryKeys || {})
, associationKey = associationKeys.length === 1 ? associationKeys[0] : 'id' , associationKey = associationKeys.length === 1 ? associationKeys[0] : 'id'
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, obsoleteAssociations = oldAssociations.filter(function (old) { , obsoleteAssociations = oldAssociations.filter(function (old) {
......
...@@ -21,6 +21,8 @@ module.exports = (function() { ...@@ -21,6 +21,8 @@ module.exports = (function() {
this.associationAccessor = this.combinedName = (this.options.joinTableName || combinedTableName) this.associationAccessor = this.combinedName = (this.options.joinTableName || combinedTableName)
this.options.tableName = this.combinedName this.options.tableName = this.combinedName
this.options.useHooks = options.useHooks
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))
this.accessors = { this.accessors = {
...@@ -184,15 +186,46 @@ module.exports = (function() { ...@@ -184,15 +186,46 @@ module.exports = (function() {
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]().success(function(currentAssociatedObjects) { instance[self.accessors.get]().success(function(currentAssociatedObjects) {
var newAssociations = [] var newAssociations = []
, oldAssociations = []
currentAssociatedObjects.forEach(function(association) { currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) {
newAssociations.push(association) newAssociations[newAssociations.length] = association
} else {
oldAssociations[oldAssociations.length] = association
}
}) })
var tick = 0
var next = function(err, i) {
if (!!err || i >= oldAssociations.length) {
return run(err)
}
oldAssociations[i].destroy().error(function(err) {
next(err)
})
.success(function() {
tick++
next(null, tick)
})
}
var run = function(err) {
if (!!err) {
return customEventEmitter.emit('error', err)
}
instance[self.accessors.set](newAssociations) instance[self.accessors.set](newAssociations)
.success(function() { customEventEmitter.emit('success', null) }) .success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('error', err) }) .error(function(err) { customEventEmitter.emit('error', err) })
}
if (oldAssociations.length > 0) {
next(null, tick)
} else {
run()
}
}) })
}) })
return customEventEmitter.run() return customEventEmitter.run()
......
...@@ -18,6 +18,8 @@ module.exports = (function() { ...@@ -18,6 +18,8 @@ module.exports = (function() {
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName : this.options.as || this.target.tableName
this.options.useHooks = options.useHooks
this.accessors = { this.accessors = {
get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))), get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))),
set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))) set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
......
...@@ -7,6 +7,11 @@ var Utils = require("./../utils") ...@@ -7,6 +7,11 @@ var Utils = require("./../utils")
var Mixin = module.exports = function(){} var Mixin = module.exports = function(){}
Mixin.hasOne = function(associatedDAO, options) { Mixin.hasOne = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in the foreign table // the id is in the foreign table
var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
...@@ -18,8 +23,13 @@ Mixin.hasOne = function(associatedDAO, options) { ...@@ -18,8 +23,13 @@ Mixin.hasOne = function(associatedDAO, options) {
} }
Mixin.belongsTo = function(associatedDAO, options) { Mixin.belongsTo = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(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, associatedDAO, 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)
...@@ -29,6 +39,11 @@ Mixin.belongsTo = function(associatedDAO, options) { ...@@ -29,6 +39,11 @@ Mixin.belongsTo = function(associatedDAO, options) {
} }
Mixin.hasMany = function(associatedDAO, options) { Mixin.hasMany = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in the foreign table 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, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
......
...@@ -569,7 +569,9 @@ module.exports = (function() { ...@@ -569,7 +569,9 @@ module.exports = (function() {
*/ */
DAOFactory.prototype.bulkCreate = function(records, fields, options) { DAOFactory.prototype.bulkCreate = function(records, fields, options) {
options = options || {} options = options || {}
options.validate = options.validate || false options.validate = options.validate === undefined ? false : Boolean(options.validate)
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
fields = fields || [] fields = fields || []
var self = this var self = this
...@@ -577,25 +579,62 @@ module.exports = (function() { ...@@ -577,25 +579,62 @@ module.exports = (function() {
, createdAtAttr = Utils._.underscoredIf(self.options.createdAt, self.options.underscored) , createdAtAttr = Utils._.underscoredIf(self.options.createdAt, self.options.underscored)
, errors = [] , errors = []
, daos = records.map(function(v) { , daos = records.map(function(v) {
var build = self.build(v) return self.build(v)
if (options.validate === true) { })
var valid = build.validate({skip: fields})
if (valid !== null) { return new Utils.CustomEventEmitter(function(emitter) {
errors[errors.length] = {record: v, errors: valid} var done = function() {
self.runHooks('afterBulkCreate', daos, fields, function(err, newRecords, newFields) {
if (!!err) {
return emitter.emit('error', err)
} }
daos = newRecords || daos
fields = newFields || fields
emitter.emit('success', daos, fields)
})
}
var next = function() {
if (options.hooks === false) {
return runQuery()
}
var i = 0
var iterate = function(i) {
self.runHooks('beforeCreate', daos[i], function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
} }
return build
daos[i] = newValues || daos[i]
daos[i].save().error(function(err) {
emitter.emit('error', err)
}).success(function() {
self.runHooks('afterCreate', daos[i], function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
daos[i] = newValues || daos[i]
i++
if (i >= daos.length) {
return done()
}
iterate(i)
})
}) })
})
}
if (options.validate === true && errors.length > 0) { iterate(i)
return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', errors)
}).run()
} }
var runQuery = function() {
// we will re-create from DAOs, which may have set up default attributes // we will re-create from DAOs, which may have set up default attributes
records = [] records = []
var found = false
daos.forEach(function(dao) { daos.forEach(function(dao) {
var values = fields.length > 0 ? {} : dao.dataValues var values = fields.length > 0 ? {} : dao.dataValues
...@@ -609,26 +648,63 @@ module.exports = (function() { ...@@ -609,26 +648,63 @@ module.exports = (function() {
values[updatedAtAttr] = Utils.now() values[updatedAtAttr] = Utils.now()
} }
records.push(values) records[records.length] = values
})
self.QueryInterface.bulkInsert(self.tableName, records)
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
.error(function(err) {
emitter.emit('error', err)
}).success(function() {
done()
}) })
}
// Validate enums self.runHooks('beforeBulkCreate', daos, fields, function(err, newRecords, newFields) {
records.forEach(function(values) { if (!!err) {
for (var attrName in self.rawAttributes) { return emitter.emit('error', err)
if (self.rawAttributes.hasOwnProperty(attrName)) { }
var definition = self.rawAttributes[attrName]
, isEnum = (definition.type && (definition.type.toString() === DataTypes.ENUM.toString())) daos = newRecords || daos
, hasValue = (typeof values[attrName] !== 'undefined') fields = newFields || fields
, valueOutOfScope = ((definition.values || []).indexOf(values[attrName]) === -1)
if (options.validate === true) {
if (options.hooks === true) {
var iterate = function(i) {
daos[i].hookValidate({skip: fields}).error(function(err) {
errors[errors.length] = {record: v, errors: err}
i++
if (i > daos.length) {
if (errors.length > 0) {
return emitter.emit('error', errors)
}
if (isEnum && hasValue && valueOutOfScope && !(definition.allowNull === true && values[attrName] === null)) { return next()
throw new Error('Value "' + values[attrName] + '" for ENUM ' + attrName + ' is out of allowed scope. Allowed values: ' + definition.values.join(', '))
} }
iterate(i)
})
} }
} else {
daos.forEach(function(v) {
var valid = v.validate({skip: fields})
if (valid !== null) {
errors[errors.length] = {record: v, errors: valid}
} }
}) })
return self.QueryInterface.bulkInsert(self.tableName, records) if (errors.length > 0) {
return emitter.emit('error', errors)
}
next()
}
} else {
next()
}
})
}).run()
} }
/** /**
...@@ -647,12 +723,12 @@ module.exports = (function() { ...@@ -647,12 +723,12 @@ module.exports = (function() {
, args = [] , args = []
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
self.runHooks.call(self, self.options.hooks.beforeBulkDestroy, where, function(err, newValues) { self.runHooks(self.options.hooks.beforeBulkDestroy, where, function(err, newWhere) {
if (!!err) { if (!!err) {
return emitter.emit('error', err) return emitter.emit('error', err)
} }
where = newValues where = newWhere || where
if (self.options.timestamps && self.options.paranoid) { if (self.options.timestamps && self.options.paranoid) {
var attr = Utils._.underscoredIf(self.options.deletedAt, self.options.underscored) var attr = Utils._.underscoredIf(self.options.deletedAt, self.options.underscored)
...@@ -683,7 +759,7 @@ module.exports = (function() { ...@@ -683,7 +759,7 @@ module.exports = (function() {
return emitter.emit('error', err) return emitter.emit('error', err)
} }
self.runHooks.call(self, self.options.hooks.afterBulkDestroy, where, function(err) { self.runHooks(self.options.hooks.afterBulkDestroy, where, function(err) {
if (!!err) { if (!!err) {
return emitter.emit('error', err) return emitter.emit('error', err)
} }
...@@ -695,15 +771,15 @@ module.exports = (function() { ...@@ -695,15 +771,15 @@ module.exports = (function() {
if (options && options.hooks === true) { if (options && options.hooks === true) {
var tick = 0 var tick = 0
var next = function(i) { var next = function(i) {
self.runHooks.call(self, self.options.hooks.afterDestroy, records[i].dataValues, function(err, newValues) { self.runHooks(self.options.hooks.afterDestroy, records[i], function(err, newValues) {
if (!!err) { if (!!err) {
return finished(err) return finished(err)
} }
records[i].dataValues = newValues records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++ tick++
if (tick === records.length) { if (tick >= records.length) {
return finished() return finished()
} }
...@@ -720,17 +796,18 @@ module.exports = (function() { ...@@ -720,17 +796,18 @@ module.exports = (function() {
if (options && options.hooks === true) { if (options && options.hooks === true) {
var tick = 0 var tick = 0
self.all({where: where}).success(function(records) { self.all({where: where}).error(function(err) { emitter.emit('error', err) })
.success(function(records) {
var next = function(i) { var next = function(i) {
self.runHooks.call(self, self.options.hooks.beforeDestroy, records[i].values, function(err, newValues) { self.runHooks(self.options.hooks.beforeDestroy, records[i], function(err, newValues) {
if (!!err) { if (!!err) {
return runQuery(err) return runQuery(err)
} }
records[i].dataValues = newValues records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++ tick++
if (tick === records.length) { if (tick >= records.length) {
return runQuery(null, records) return runQuery(null, records)
} }
...@@ -756,24 +833,129 @@ module.exports = (function() { ...@@ -756,24 +833,129 @@ module.exports = (function() {
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
*/ */
DAOFactory.prototype.update = function(attrValueHash, where, options) { DAOFactory.prototype.update = function(attrValueHash, where, options) {
var self = this
, query = null
, tick = 0
options = options || {} options = options || {}
options.validate = options.validate || true options.validate = options.validate === undefined ? true : Boolean(options.validate)
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
if(this.options.timestamps) { if (self.options.timestamps) {
var attr = Utils._.underscoredIf(this.options.updatedAt, this.options.underscored) var attr = Utils._.underscoredIf(self.options.updatedAt, self.options.underscored)
attrValueHash[attr] = Utils.now() attrValueHash[attr] = Utils.now()
} }
if (options.validate === true) {
var validate = this.build(attrValueHash).validate()
if (validate !== null && Object.keys(validate).length > 0) {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', validate) var runSave = function() {
}).run() self.runHooks(self.options.hooks.beforeBulkUpdate, attrValueHash, where, function(err, attributes, _where) {
if (!!err) {
return emitter.emit('error', err)
} }
where = _where || where
attrValueHash = attributes || attrValueHash
var runQuery = function(err, records) {
if (!!err) {
return emitter.emit('error', err)
} }
return this.QueryInterface.bulkUpdate(this.tableName, attrValueHash, where, options) query = self.QueryInterface.bulkUpdate(self.tableName, attrValueHash, where, options)
query.on('sql', function(sql) {
emitter.emit('sql', sql)
})
.error(function(err) {
emitter.emit('error', err)
})
.success(function(results) {
var finished = function(err, records) {
if (!!err) {
return emitter.emit('error', err)
}
self.runHooks(self.options.hooks.afterBulkUpdate, attrValueHash, where, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
emitter.emit('success', records)
})
}
if (options && options.hooks === true && !!records && records.length > 0) {
var tick = 0
var next = function(i) {
self.runHooks(self.options.hooks.afterUpdate, records[i], function(err, newValues) {
if (!!err) {
return finished(err)
}
records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++
if (tick >= records.length) {
return finished(null, records)
}
next(tick)
})
}
next(tick)
} else {
finished(null, results)
}
})
}
if (options.hooks === true) {
self.all({where: where}).error(function(err) { emitter.emit('error', err) })
.success(function(records) {
if (records === null || records.length < 1) {
return runQuery(null)
}
var next = function(i) {
self.runHooks(self.options.hooks.beforeUpdate, records[i], function(err, newValues) {
if (!!err) {
return runQuery(err)
}
records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++
if (tick >= records.length) {
return runQuery(null, records)
}
next(tick)
})
}
next(tick)
})
} else {
runQuery()
}
})
}
if (options.validate === true) {
var build = self.build(attrValueHash)
build.hookValidate({skip: attrValueHash}).error(function(err) {
emitter.emit('error', err)
}).success(function(attributes) {
if (!!attributes && !!attributes.dataValues) {
attrValueHash = Utils._.pick.apply(Utils._, [].concat(attributes.dataValues).concat(Object.keys(attrValueHash)))
}
runSave()
})
} else {
runSave()
}
}).run()
} }
DAOFactory.prototype.describe = function(schema) { DAOFactory.prototype.describe = function(schema) {
......
...@@ -18,19 +18,50 @@ DaoValidator.prototype.validate = function() { ...@@ -18,19 +18,50 @@ DaoValidator.prototype.validate = function() {
return errors return errors
} }
DaoValidator.prototype.hookValidate = function() {
var self = this
, errors = {}
return new Utils.CustomEventEmitter(function(emitter) {
self.model.daoFactory.runHooks('beforeValidate', self.model.dataValues, function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
self.model.dataValues = newValues || self.model.dataValues
errors = Utils._.extend(errors, validateAttributes.call(self))
errors = Utils._.extend(errors, validateModel.call(self))
if (Object.keys(errors).length > 0) {
return emitter.emit('error', errors)
}
self.model.daoFactory.runHooks('afterValidate', self.model.dataValues, function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
self.model.dataValues = newValues || self.model.dataValues
emitter.emit('success', self.model)
})
})
}).run()
}
// private // private
var validateModel = function() { var validateModel = function() {
var errors = {} var self = this
, errors = {}
// for each model validator for this DAO // for each model validator for this DAO
Utils._.each(this.model.__options.validate, function(validator, validatorType) { Utils._.each(this.model.__options.validate, function(validator, validatorType) {
try { try {
validator.apply(this.model) validator.apply(self.model)
} catch (err) { } catch (err) {
errors[validatorType] = [err.message] // TODO: data structure needs to change for 2.0 errors[validatorType] = [err.message] // TODO: data structure needs to change for 2.0
} }
}.bind(this)) })
return errors return errors
} }
...@@ -54,11 +85,12 @@ var validateAttributes = function() { ...@@ -54,11 +85,12 @@ var validateAttributes = function() {
} }
var validateAttribute = function(value, field) { var validateAttribute = function(value, field) {
var errors = {} var self = this
, errors = {}
// for each validator // for each validator
Utils._.each(this.model.validators[field], function(details, validatorType) { Utils._.each(this.model.validators[field], function(details, validatorType) {
var validator = prepareValidationOfAttribute.call(this, value, details, validatorType) var validator = prepareValidationOfAttribute.call(self, value, details, validatorType)
try { try {
validator.fn.apply(null, validator.args) validator.fn.apply(null, validator.args)
...@@ -74,7 +106,7 @@ var validateAttribute = function(value, field) { ...@@ -74,7 +106,7 @@ var validateAttribute = function(value, field) {
errors[field] = errors[field] || [] errors[field] = errors[field] || []
errors[field].push(msg) errors[field].push(msg)
} }
}.bind(this)) // for each validator for this field })
return errors return errors
} }
......
...@@ -105,11 +105,11 @@ module.exports = (function() { ...@@ -105,11 +105,11 @@ module.exports = (function() {
if (fields) { if (fields) {
if (self.__options.timestamps) { if (self.__options.timestamps) {
if (fields.indexOf(updatedAtAttr) === -1) { if (fields.indexOf(updatedAtAttr) === -1) {
fields.push(updatedAtAttr) fields[fields.length] = updatedAtAttr
} }
if (fields.indexOf(createdAtAttr) === -1 && this.isNewRecord === true) { if (fields.indexOf(createdAtAttr) === -1 && this.isNewRecord === true) {
fields.push(createdAtAttr) fields[fields.length] = createdAtAttr
} }
} }
...@@ -122,25 +122,20 @@ module.exports = (function() { ...@@ -122,25 +122,20 @@ module.exports = (function() {
}) })
} }
var errors = this.validate()
if (!!errors) {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', errors) self.hookValidate().error(function(err) {
}).run() emitter.emit('error', err)
} }).success(function() {
for (var attrName in self.daoFactory.rawAttributes) {
for (var attrName in this.daoFactory.rawAttributes) { if (self.daoFactory.rawAttributes.hasOwnProperty(attrName)) {
if (this.daoFactory.rawAttributes.hasOwnProperty(attrName)) { var definition = self.daoFactory.rawAttributes[attrName]
var definition = this.daoFactory.rawAttributes[attrName]
, isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type , isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type
, hasValue = values[attrName] !== undefined , isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isMySQL = this.daoFactory.daoFactoryManager.sequelize.options.dialect === "mysql" , isMySQL = self.daoFactory.daoFactoryManager.sequelize.options.dialect === "mysql"
, ciCollation = !!this.daoFactory.options.collate && this.daoFactory.options.collate.match(/_ci$/i) , ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i)
, valueOutOfScope
if (isEnum && isMySQL && ciCollation && hasValue) { // Unfortunately for MySQL CI collation we need to map/lowercase values again
if (isEnum && isMySQL && ciCollation) {
var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase()) var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase())
valueOutOfScope = scopeIndex === -1 valueOutOfScope = scopeIndex === -1
...@@ -148,12 +143,6 @@ module.exports = (function() { ...@@ -148,12 +143,6 @@ module.exports = (function() {
if (!valueOutOfScope) { if (!valueOutOfScope) {
values[attrName] = definition.values[scopeIndex] values[attrName] = definition.values[scopeIndex]
} }
} else {
valueOutOfScope = ((definition.values || []).indexOf(values[attrName]) === -1)
}
if (isEnum && hasValue && valueOutOfScope && !(definition.allowNull === true && values[attrName] === null)) {
throw new Error('Value "' + values[attrName] + '" for ENUM ' + attrName + ' is out of allowed scope. Allowed values: ' + definition.values.join(', '))
} }
if (isHstore) { if (isHstore) {
...@@ -164,56 +153,56 @@ module.exports = (function() { ...@@ -164,56 +153,56 @@ module.exports = (function() {
} }
} }
if (this.__options.timestamps && this.dataValues.hasOwnProperty(updatedAtAttr)) { if (self.__options.timestamps && self.dataValues.hasOwnProperty(updatedAtAttr)) {
this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now(this.sequelize.options.dialect) self.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now(self.sequelize.options.dialect)
} }
var query = null var query = null
, args = [] , args = []
, hook = '' , hook = ''
if (this.isNewRecord) { if (self.isNewRecord) {
this.isDirty = false self.isDirty = false
query = 'insert' query = 'insert'
args = [this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values] args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values]
hook = 'Create' hook = 'Create'
} else { } else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }; var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id }
if (identifier === null && this.__options.whereCollection !== null) { if (identifier === null && self.__options.whereCollection !== null) {
identifier = this.__options.whereCollection; identifier = self.__options.whereCollection;
} }
this.isDirty = false self.isDirty = false
query = 'update' query = 'update'
args = [this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values, identifier, options] args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values, identifier, options]
hook = 'Update' hook = 'Update'
} }
return new Utils.CustomEventEmitter(function(saveEmitter) { self.__factory.runHooks('before' + hook, values, function(err, newValues) {
self.__factory.runHooks.call(self, self.__factory.options.hooks['before' + hook], values, function(err, newValues) {
if (!!err) { if (!!err) {
return saveEmitter.emit('error', err) return emitter.emit('error', err)
} }
// redeclare our new values // redeclare our new values
args[2] = newValues args[2] = newValues || args[2]
self.QueryInterface[query].apply(self.QueryInterface, args) self.QueryInterface[query].apply(self.QueryInterface, args)
.on('sql', function(sql) { .on('sql', function(sql) {
saveEmitter.emit('sql', sql) emitter.emit('sql', sql)
}) })
.error(function(err) { .error(function(err) {
saveEmitter.emit('err', err) emitter.emit('error', err)
}) })
.success(function(result) { .success(function(result) {
self.__factory.runHooks.call(self, self.__factory.options.hooks['after' + hook], result.values, function(err, newValues) { self.__factory.runHooks('after' + hook, result.values, function(err, newValues) {
if (!!err) { if (!!err) {
return saveEmitter.emit('error', err) return emitter.emit('error', err)
} }
result.dataValues = newValues result.dataValues = newValues
saveEmitter.emit('success', result) emitter.emit('success', result)
})
}) })
}) })
}) })
...@@ -259,14 +248,22 @@ module.exports = (function() { ...@@ -259,14 +248,22 @@ module.exports = (function() {
* @return null if and only if validation successful; otherwise an object containing { field name : [error msgs] } entries. * @return null if and only if validation successful; otherwise an object containing { field name : [error msgs] } entries.
*/ */
DAO.prototype.validate = function(object) { DAO.prototype.validate = function(object) {
var self = this
var validator = new DaoValidator(this, object) var validator = new DaoValidator(this, object)
, errors = validator.validate() , errors = validator.validate()
return (Utils._.isEmpty(errors) ? null : errors) return (Utils._.isEmpty(errors) ? null : errors)
} }
/*
* Validate this dao's attribute values according to validation rules set in the dao definition.
*
* @return CustomEventEmitter with null if validation successful; otherwise an object containing { field name : [error msgs] } entries.
*/
DAO.prototype.hookValidate = function(object) {
var validator = new DaoValidator(this, object)
return validator.hookValidate()
}
DAO.prototype.updateAttributes = function(updates, fields) { DAO.prototype.updateAttributes = function(updates, fields) {
this.setAttributes(updates) this.setAttributes(updates)
...@@ -317,13 +314,11 @@ module.exports = (function() { ...@@ -317,13 +314,11 @@ module.exports = (function() {
, query = null , query = null
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
self.daoFactory.runHooks.call(self, self.daoFactory.options.hooks.beforeDestroy, self.dataValues, function(err, newValues) { self.daoFactory.runHooks(self.daoFactory.options.hooks.beforeDestroy, self.dataValues, function(err) {
if (!!err) { if (!!err) {
return emitter.emit('error', err) return emitter.emit('error', err)
} }
self.dataValues = newValues
if (self.__options.timestamps && self.__options.paranoid) { if (self.__options.timestamps && self.__options.paranoid) {
var attr = Utils._.underscoredIf(self.__options.deletedAt, self.__options.underscored) var attr = Utils._.underscoredIf(self.__options.deletedAt, self.__options.underscored)
self.dataValues[attr] = new Date() self.dataValues[attr] = new Date()
...@@ -340,12 +335,11 @@ module.exports = (function() { ...@@ -340,12 +335,11 @@ module.exports = (function() {
emitter.emit('error', err) emitter.emit('error', err)
}) })
.success(function(results) { .success(function(results) {
self.daoFactory.runHooks.call(self, self.daoFactory.options.hooks.afterDestroy, self.dataValues, function(err, newValues) { self.daoFactory.runHooks(self.daoFactory.options.hooks.afterDestroy, self.dataValues, function(err) {
if (!!err) { if (!!err) {
return emitter.emit('error', err) return emitter.emit('error', err)
} }
self.dataValues = newValues
emitter.emit('success', results) emitter.emit('success', results)
}) })
}) })
......
var Utils = require("./utils")
var Hooks = module.exports = function(){} var Hooks = module.exports = function(){}
Hooks.runHooks = function(hooks, daoValues, fn) { Hooks.runHooks = function() {
var self = this var self = this
, tick = 0 , tick = 0
, hooks = arguments[0]
, args = Array.prototype.slice.call(arguments, 1, arguments.length-1)
, fn = arguments[arguments.length-1]
if (typeof hooks === "string") {
hooks = this.options.hooks[hooks] || []
}
if (!Array.isArray(hooks)) {
hooks = [hooks]
}
if (hooks === undefined || hooks.length < 1) {
return fn.apply(this, [null].concat(args))
}
var run = function(hook) { var run = function(hook) {
if (!hook) { if (!hook) {
return fn(null, daoValues); return fn.apply(this, [null].concat(args))
} }
if (typeof hook === "object") { if (typeof hook === "object") {
hook = hook.fn hook = hook.fn
} }
hook.call(self, daoValues, function(err, newValues) { hook.apply(self, args.concat(function() {
tick++ tick++
if (!!err) {
return fn(err) if (!!arguments[0]) {
return fn(arguments[0])
} }
daoValues = newValues // daoValues = newValues
return run(hooks[tick]) return run(hooks[tick])
}) }))
} }
run(hooks[tick]) run(hooks[tick])
} }
Hooks.hook = function(hookType, name, fn) { Hooks.hook = function(hookType, name, fn) {
// For aliases, we may want to incorporate some sort of way to mitigate this
if (hookType === "beforeDelete") {
hookType = 'beforeDestroy'
}
else if (hookType === "afterDelete") {
hookType = 'afterDestroy'
}
Hooks.addHook.call(this, hookType, name, fn) Hooks.addHook.call(this, hookType, name, fn)
} }
...@@ -39,8 +61,8 @@ Hooks.addHook = function(hookType, name, fn) { ...@@ -39,8 +61,8 @@ Hooks.addHook = function(hookType, name, fn) {
name = null name = null
} }
var method = function(daoValues, callback) { var method = function() {
fn.call(this, daoValues, callback) fn.apply(this, Array.prototype.slice.call(arguments, 0, arguments.length-1).concat(arguments[arguments.length-1]))
} }
// Just in case if we override the default DAOFactory.options // Just in case if we override the default DAOFactory.options
...@@ -72,6 +94,30 @@ Hooks.afterDestroy = function(name, fn) { ...@@ -72,6 +94,30 @@ Hooks.afterDestroy = function(name, fn) {
Hooks.addHook.call(this, 'afterDestroy', name, fn) Hooks.addHook.call(this, 'afterDestroy', name, fn)
} }
Hooks.beforeDelete = function(name, fn) {
Hooks.addHook.call(this, 'beforeDestroy', name, fn)
}
Hooks.afterDelete = function(name, fn) {
Hooks.addHook.call(this, 'afterDestroy', name, fn)
}
Hooks.beforeUpdate = function(name, fn) {
Hooks.addHook.call(this, 'beforeUpdate', name, fn)
}
Hooks.afterUpdate = function(name, fn) {
Hooks.addHook.call(this, 'afterUpdate', name, fn)
}
Hooks.beforeBulkCreate = function(name, fn) {
Hooks.addHook.call(this, 'beforeBulkCreate', name, fn)
}
Hooks.afterBulkCreate = function(name, fn) {
Hooks.addHook.call(this, 'afterBulkCreate', name, fn)
}
Hooks.beforeBulkDestroy = function(name, fn) { Hooks.beforeBulkDestroy = function(name, fn) {
Hooks.addHook.call(this, 'beforeBulkDestroy', name, fn) Hooks.addHook.call(this, 'beforeBulkDestroy', name, fn)
} }
...@@ -80,40 +126,10 @@ Hooks.afterBulkDestroy = function(name, fn) { ...@@ -80,40 +126,10 @@ Hooks.afterBulkDestroy = function(name, fn) {
Hooks.addHook.call(this, 'afterBulkDestroy', name, fn) Hooks.addHook.call(this, 'afterBulkDestroy', name, fn)
} }
// - beforeSave Hooks.beforeBulkUpdate = function(name, fn) {
// - afterSave Hooks.addHook.call(this, 'beforeBulkUpdate', name, fn)
// - beforeUpdate }
// - afterUpdate
// - beforeDestroy Hooks.afterBulkUpdate = function(name, fn) {
// - afterDestroy Hooks.addHook.call(this, 'afterBulkUpdate', name, fn)
// - beforeValidate }
// - afterValidate
// user.save(callback); // If Model.id isn't set, save will invoke Model.create() instead
// // beforeValidate
// // afterValidate
// // beforeSave
// // beforeUpdate
// // afterUpdate
// // afterSave
// // callback
// user.updateAttribute('email', 'email@example.com', callback);
// // beforeValidate
// // afterValidate
// // beforeSave
// // beforeUpdate
// // afterUpdate
// // afterSave
// // callback
// user.destroy(callback);
// // beforeDestroy
// // afterDestroy
// // callback
// User.create(data, callback);
// // beforeValidate
// // afterValidate
// // beforeCreate
// // beforeSave
// // afterSave
// // afterCreate
// // callback
...@@ -481,6 +481,7 @@ module.exports = (function() { ...@@ -481,6 +481,7 @@ module.exports = (function() {
QueryInterface.prototype.delete = function(dao, tableName, identifier) { QueryInterface.prototype.delete = function(dao, tableName, identifier) {
var self = this var self = this
, restrict = false , restrict = false
, cascades = []
, sql = self.QueryGenerator.deleteQuery(tableName, identifier, null, dao.daoFactory) , sql = self.QueryGenerator.deleteQuery(tableName, identifier, null, dao.daoFactory)
// Check for a restrict field // Check for a restrict field
...@@ -489,13 +490,61 @@ module.exports = (function() { ...@@ -489,13 +490,61 @@ module.exports = (function() {
, length = keys.length , length = keys.length
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
if (dao.daoFactory.associations[keys[i]].options && dao.daoFactory.associations[keys[i]].options.onDelete && dao.daoFactory.associations[keys[i]].options.onDelete === "restrict") { if (dao.daoFactory.associations[keys[i]].options && dao.daoFactory.associations[keys[i]].options.onDelete) {
if (dao.daoFactory.associations[keys[i]].options.onDelete === "restrict") {
restrict = true restrict = true
} }
else if (dao.daoFactory.associations[keys[i]].options.onDelete === "cascade" && dao.daoFactory.associations[keys[i]].options.useHooks === true) {
cascades[cascades.length] = dao.daoFactory.associations[keys[i]].accessors.get
}
}
} }
} }
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var tick = 0
var iterate = function(err, i) {
if (!!err || i >= cascades.length) {
return run(err)
}
dao[cascades[i]]().success(function(tasks) {
if (tasks === null || tasks.length < 1) {
return run()
}
tasks = Array.isArray(tasks) ? tasks : [tasks]
var ii = 0
var next = function(err, ii) {
if (!!err || ii >= tasks.length) {
return iterate(err)
}
tasks[ii].destroy().error(function(err) {
return iterate(err)
})
.success(function() {
ii++
if (ii >= tasks.length) {
tick++
return iterate(null, tick)
}
next(null, ii)
})
}
next(null, ii)
})
}
var run = function(err) {
if (!!err) {
return emitter.emit('error', err)
}
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', []) chainer.add(self, 'enableForeignKeyConstraints', [])
...@@ -512,6 +561,13 @@ module.exports = (function() { ...@@ -512,6 +561,13 @@ module.exports = (function() {
emitter.emit('sql', sql) emitter.emit('sql', sql)
emitter.emit('error', err) emitter.emit('error', err)
}) })
}
if (cascades.length > 0) {
iterate(null, tick)
} else {
run()
}
}).run() }).run()
} }
......
...@@ -159,17 +159,8 @@ module.exports = (function() { ...@@ -159,17 +159,8 @@ module.exports = (function() {
Sequelize.prototype.define = function(daoName, attributes, options) { Sequelize.prototype.define = function(daoName, attributes, options) {
options = options || {} options = options || {}
var globalOptions = this.options var self = this
, globalOptions = this.options
// If you don't specify a valid data type lets help you debug it
Utils._.each(attributes, function(dataType, name){
if (Utils.isHash(dataType)) {
dataType = dataType.type
}
if (dataType === undefined) {
throw new Error('Unrecognized data type for field '+ name)
}
})
if (globalOptions.define) { if (globalOptions.define) {
options = Utils._.extend({}, globalOptions.define, options) options = Utils._.extend({}, globalOptions.define, options)
...@@ -184,6 +175,40 @@ module.exports = (function() { ...@@ -184,6 +175,40 @@ module.exports = (function() {
options.omitNull = globalOptions.omitNull options.omitNull = globalOptions.omitNull
options.language = globalOptions.language options.language = globalOptions.language
// If you don't specify a valid data type lets help you debug it
Utils._.each(attributes, function(dataType, name) {
if (Utils.isHash(dataType)) {
dataType = dataType.type
}
if (dataType === undefined) {
throw new Error('Unrecognized data type for field '+ name)
}
if (dataType.toString() === "ENUM") {
attributes[name].validate = attributes[name].validate || {
_checkEnum: function(value) {
var hasValue = value !== undefined
, isMySQL = self.options.dialect === "mysql"
, ciCollation = !!options.collate && options.collate.match(/_ci$/i) !== null
, valueOutOfScope
if (isMySQL && ciCollation && hasValue) {
var scopeIndex = (attributes[name].values || []).map(function(d) { return d.toLowerCase() }).indexOf(value.toLowerCase())
valueOutOfScope = scopeIndex === -1
} else {
valueOutOfScope = ((attributes[name].values || []).indexOf(value) === -1)
}
if (hasValue && valueOutOfScope && !(attributes[name].allowNull === true && values[attrName] === null)) {
throw new Error('Value "' + value + '" for ENUM ' + name + ' is out of allowed scope. Allowed values: ' + attributes[name].values.join(', '))
}
}
}
}
})
// if you call "define" multiple times for the same daoName, do not clutter the factory // if you call "define" multiple times for the same daoName, do not clutter the factory
if(this.isDefined(daoName)) { if(this.isDefined(daoName)) {
this.daoFactoryManager.removeDAO(this.daoFactoryManager.getDAO(daoName)) this.daoFactoryManager.removeDAO(this.daoFactoryManager.getDAO(daoName))
......
module.exports = { module.exports = {
username: "root", username: process.env.SEQ_USER || "root",
password: null, password: process.env.SEQ_PW || null,
database: 'sequelize_test', database: process.env.SEQ_DB || 'sequelize_test',
host: '127.0.0.1', host: process.env.SEQ_HOST || '127.0.0.1',
pool: { maxConnections: 5, maxIdleTime: 30000}, pool: {
maxConnections: process.env.SEQ_POOL_MAX || 5,
maxIdleTime: process.env.SEQ_POOL_IDLE || 30000
},
rand: function() { rand: function() {
return parseInt(Math.random() * 999, 10) return parseInt(Math.random() * 999, 10)
...@@ -11,21 +14,29 @@ module.exports = { ...@@ -11,21 +14,29 @@ module.exports = {
//make maxIdleTime small so that tests exit promptly //make maxIdleTime small so that tests exit promptly
mysql: { mysql: {
username: "root", database: process.env.SEQ_MYSQL_DB || process.env.SEQ_DB || 'sequelize_test',
password: null, username: process.env.SEQ_MYSQL_USER || process.env.SEQ_USER || "root",
database: 'sequelize_test', password: process.env.SEQ_MYSQL_PW || process.env.SEQ_PW || null,
host: '127.0.0.1', host: process.env.SEQ_MYSQL_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: 3306, port: process.env.SEQ_MYSQL_PORT || process.env.SEQ_PORT || 3306,
pool: { maxConnections: 5, maxIdleTime: 30} pool: {
maxConnections: process.env.SEQ_MYSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5,
maxIdleTime: process.env.SEQ_MYSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 30
}
}, },
sqlite: { sqlite: {
}, },
postgres: { postgres: {
database: 'sequelize_test', database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test',
username: "postgres", username: process.env.SEQ_PG_USER || process.env.SEQ_USER || "postgres",
port: 5432, password: process.env.SEQ_PG_PW || process.env.SEQ_PW || null,
pool: { maxConnections: 5, maxIdleTime: 3000} host: process.env.SEQ_PG_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432,
pool: {
maxConnections: process.env.SEQ_PG_POOL_MAX || process.env.SEQ_POOL_MAX || 5,
maxIdleTime: process.env.SEQ_PG_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000
}
} }
} }
This diff could not be displayed because it is too large.
...@@ -122,19 +122,18 @@ if (dialect.match(/^mysql/)) { ...@@ -122,19 +122,18 @@ if (dialect.match(/^mysql/)) {
}) })
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
expect(function() { User.create({mood: 'happy'}).error(function(err) {
User.create({mood: 'happy'}) expect(err).to.deep.equal({ mood: [ 'Value "happy" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver' ] })
}).to.throw(Error, 'Value "happy" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver')
expect(function() {
var u = User.build({mood: 'SAD'}) var u = User.build({mood: 'SAD'})
u.save() u.save().error(function(err) {
}).to.throw(Error, 'Value "SAD" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver') expect(err).to.deep.equal({ mood: [ 'Value "SAD" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver' ] })
done() done()
}) })
}) })
}) })
}) })
})
})
describe('primaryKeys', function() { describe('primaryKeys', function() {
it("determines the correct primaryKeys", function(done) { it("determines the correct primaryKeys", function(done) {
......
...@@ -423,14 +423,13 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -423,14 +423,13 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
}) })
it("doesn't save an instance if value is not in the range of enums", function(done) { it("doesn't save an instance if value is not in the range of enums", function(done) {
var self = this this.Review.create({status: 'fnord'}).error(function(err) {
expect(function() { expect(err).to.deep.equal({ status: [ 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished' ] })
self.Review.create({ status: 'fnord' })
}).to.throw(Error, 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished')
done() done()
}) })
}) })
}) })
})
describe('table', function() { describe('table', function() {
[ [
......
var fs = require('fs') var fs = require('fs')
, Sequelize = require(__dirname + "/../index") , Sequelize = require(__dirname + "/../index")
, DataTypes = require(__dirname + "/../lib/data-types") , DataTypes = require(__dirname + "/../lib/data-types")
, config = require(__dirname + "/config/config") , Config = require(__dirname + "/config/config")
var Support = { var Support = {
Sequelize: Sequelize, Sequelize: Sequelize,
...@@ -26,15 +26,17 @@ var Support = { ...@@ -26,15 +26,17 @@ var Support = {
createSequelizeInstance: function(options) { createSequelizeInstance: function(options) {
options = options || {} options = options || {}
options.dialect = options.dialect || 'mysql' options.dialect = options.dialect || 'mysql'
var config = Config[options.dialect]
options.logging = (options.hasOwnProperty('logging') ? options.logging : false) options.logging = (options.hasOwnProperty('logging') ? options.logging : false)
options.pool = options.pool || config.pool options.pool = options.pool || config.pool
var sequelizeOptions = { var sequelizeOptions = {
logging: options.logging, logging: options.logging,
dialect: options.dialect, dialect: options.dialect,
port: options.port || process.env.SEQ_PORT || config[options.dialect].port, port: options.port || config.port,
pool: options.pool pool: options.pool
} }
...@@ -50,12 +52,7 @@ var Support = { ...@@ -50,12 +52,7 @@ var Support = {
sequelizeOptions.native = true sequelizeOptions.native = true
} }
return this.getSequelizeInstance( return this.getSequelizeInstance(config.database, config.username, config.password, sequelizeOptions)
process.env.SEQ_DB || config[options.dialect].database,
process.env.SEQ_USER || process.env.SEQ_USERNAME || config[options.dialect].username,
process.env.SEQ_PW || process.env.SEQ_PASSWORD || config[options.dialect].password,
sequelizeOptions
)
}, },
getSequelizeInstance: function(db, user, pass, options) { getSequelizeInstance: function(db, user, pass, options) {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!