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

Commit 4b7c0e94 by Mick Hansen

Update 2.0.0 branch

2 parents 525760c2 8547ea1a
REPORTER ?= dot
REPORTER ?= spec
TESTS = $(shell find ./test/* -name "*.test.js")
DIALECT ?= mysql
......
......@@ -10,6 +10,8 @@ There is a parallel "branch" of the project, released as `2.0.0-alphaX` in NPM.
and will get all the changes of the master. However, `2.0.0` will contain backwards compatibility breaking changes. Check the
changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/2.0.0/changelog.md
##### 2.0.0 API should be considered unstable
### 1.6.0 ###
- We changed the way timestamps are handled. From v1.6.0 on timestamps are stored and loaded as UTC.
......@@ -37,11 +39,11 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/
## Documentation and Updates ##
You can find the documentation and announcements of updates on the [project's website](http://www.sequelizejs.com).
You can find the documentation and announcements of updates on the [project's website](http://sequelizejs.com).
If you want to know about latest development and releases, follow me on [Twitter](http://twitter.com/sdepold).
Also make sure to take a look at the examples in the repository. The website will contain them soon, as well.
- [Documentation](http://www.sequelizejs.com)
- [Documentation](http://sequelizejs.com)
- [Twitter](http://twitter.com/sdepold)
- [IRC](http://webchat.freenode.net?channels=sequelizejs)
- [Google Groups](https://groups.google.com/forum/#!forum/sequelize)
......@@ -105,7 +107,16 @@ a productive developer, I would recommend the latest v0.8. Also I usually recomm
Once Node.JS is installed on your computer, you will also have access to the lovely
Node Package Manager (NPM).
### 2. Database... Come to me! ###
### 2. Install the dependencies ###
Just "cd" into sequelize directory and run `npm install`, see an example below:
```console
$ cd path/to/sequelize
$ npm install
```
### 3. Database... Come to me! ###
First class citizen of Sequelize was MySQL. Over time, Sequelize began to
become compatible to SQLite and PostgreSQL. In order to provide a fully
......@@ -130,16 +141,32 @@ $ echo "CREATE DATABASE sequelize_test;" | mysql -uroot
**AND ONE LAST THING:** Once `npm install` worked for you (see below), you'll
get SQLite tests for free :)
#### 3a. Docker
If you don't feel like setting up databases and users, you can use our [docker](http://docker.io) [image](https://index.docker.io/u/mhansen/sequelize-contribution/) for sequelize contribution.
Getting the image:
```console
$ sudo docker pull mhansen/sequelize-contribution
```
### 3. Install the dependencies ###
Start the container and save references to container id and ip:
```console
$ CONTAINER=$(sudo docker run -d -i -t mhansen/sequelize-contribution)
$ CONTAINER_IP=$(sudo docker inspect -format='{{.NetworkSettings.IPAddress}}' $CONTAINER)
```
Just "cd" into sequelize directory and run `npm install`, see an example below:
Run tests:
```console
$ SEQ_HOST=$CONTAINER_IP SEQ_USER=sequelize_test make all
```
Stop the container:
```console
$ cd path/to/sequelize
$ npm install
$ sudo docker stop $CONTAINER
```
When running tests repeatedly, you only need to redo step 3 if you haven't stopped the container.
### 4. Run the tests ###
Right now, the test base is split into the `test` folder (which contains the
......
......@@ -22,6 +22,15 @@ var relativeConfigFile = function() {
return path.relative(process.cwd(), configuration.configFile)
}
// Taken from
// http://stackoverflow.com/questions/15375544/how-can-i-robustly-detect-a-relative-path-in-node-js/17521358#17521358
var isRelativePath = function(p) {
var normal = path.normalize(p)
, absolute = path.resolve(p);
return normal != absolute;
}
var writeDefaultConfig = function(config) {
var configPath = path.dirname(configuration.configFile)
......@@ -109,7 +118,11 @@ program
.parse(process.argv)
if(typeof program.config === 'string') {
if (isRelativePath(program.config)) {
configuration.configFile = path.join(process.cwd(), program.config);
} else {
configuration.configFile = program.config
}
}
if(typeof program.env === 'string') {
......
......@@ -3,22 +3,32 @@ var Utils = require("./../utils")
, Helpers = require('./helpers')
module.exports = (function() {
var BelongsTo = function(srcDAO, targetDAO, options) {
var BelongsTo = function(source, target, options) {
this.associationType = 'BelongsTo'
this.source = srcDAO
this.target = targetDAO
this.source = source
this.target = target
this.options = options
this.isSingleAssociation = true
this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.source.options.language) + "Id", this.source.options.underscored)
}
this.options.useHooks = options.useHooks
if (!this.options.as) {
this.options.as = Utils.singularize(this.target.tableName, this.target.options.language)
}
this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName
? Utils.combineTableNames(this.target.tableName, this.options.as)
: this.options.as
this.options.useHooks = options.useHooks
this.accessors = {
get: Utils._.camelize('get_' + this.options.as),
set: Utils._.camelize('set_' + this.options.as)
}
}
// the id is in the source table
......@@ -40,11 +50,10 @@ module.exports = (function() {
BelongsTo.prototype.injectGetter = function(obj) {
var self = this
, accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
, primaryKeys = Object.keys(self.target.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
obj[accessor] = function(params) {
obj[this.accessors.get] = function(params) {
var id = this[self.identifier]
, where = {}
, options = Utils._.pick(params || {}, 'transaction')
......@@ -69,9 +78,8 @@ module.exports = (function() {
BelongsTo.prototype.injectSetter = function(obj) {
var self = this
, accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
obj[accessor] = function(associatedObject, options) {
obj[this.accessors.set] = function(associatedObject, options) {
var primaryKeys = !!associatedObject && !!associatedObject.daoFactory ? Object.keys(associatedObject.daoFactory.primaryKeys) : []
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
......
......@@ -2,8 +2,8 @@ var Utils = require('./../utils')
, Transaction = require('./../transaction')
module.exports = (function() {
var HasManyDoubleLinked = function(definition, instance) {
this.__factory = definition
var HasManyDoubleLinked = function(association, instance) {
this.association = association
this.instance = instance
// Alias the quoting methods for code brevity
......@@ -17,47 +17,47 @@ module.exports = (function() {
var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {}
, connectorDAO = self.__factory.connectorDAO
, through = self.association.through
, options = _options || {}
, queryOptions = {}
, association = self.__factory.target.associations[self.__factory.associationAccessor]
, targetAssociation = self.association.targetAssociation
//fully qualify
var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys)
, instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id'
where[connectorDAO.tableName+"."+self.__factory.identifier] = self.instance[instancePrimaryKey]
where[through.tableName+"."+self.association.identifier] = self.instance[instancePrimaryKey]
var primaryKeys = Object.keys(connectorDAO.primaryKeys)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
, foreignPrimary = Object.keys(self.__factory.target.primaryKeys)
var primaryKeys = Object.keys(through.primaryKeys)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.association.identifier })[0]
, foreignPrimary = Object.keys(self.association.target.primaryKeys)
foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id'
where[connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+"."+foreignPrimary}
where[through.tableName+"."+foreignKey] = {join: self.association.target.tableName+"."+foreignPrimary}
if (association.hasJoinTableModel) {
if (Object(targetAssociation.through) === targetAssociation.through) {
queryOptions.hasJoinTableModel = true
queryOptions.joinTableModel = connectorDAO
queryOptions.joinTableModel = through
if (!options.attributes) {
options.attributes = [
self.QueryInterface.quoteIdentifier(self.__factory.target.tableName)+".*"
self.QueryInterface.quoteIdentifier(self.association.target.tableName)+".*"
]
}
if (options.joinTableAttributes) {
options.joinTableAttributes.forEach(function (elem) {
options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + elem) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + elem, true)
self.QueryInterface.quoteIdentifiers(through.tableName + '.' + elem) + ' as ' +
self.QueryInterface.quoteIdentifier(through.name + '.' + elem, true)
)
})
} else {
Utils._.forOwn(connectorDAO.rawAttributes, function (elem, key) {
Utils._.forOwn(through.rawAttributes, function (elem, key) {
options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + key) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + key, true)
self.QueryInterface.quoteIdentifiers(through.tableName + '.' + key) + ' as ' +
self.QueryInterface.quoteIdentifier(through.name + '.' + key, true)
)
})
}
......@@ -65,14 +65,14 @@ module.exports = (function() {
if (options.where) {
if (Array.isArray(options.where)) {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.smartWhere([where, options.where], self.association.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.association.target, smart, self.association.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) {
options.where = smart
}
} else {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.smartWhere([where, options.where], self.association.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.association.target, smart, self.association.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) {
options.where = smart
}
......@@ -81,7 +81,7 @@ module.exports = (function() {
options.where = where;
}
self.__factory.target.findAllJoin(connectorDAO.tableName, options, queryOptions)
self.association.target.findAllJoin(through.tableName, options, queryOptions)
.on('success', function(objects) { customEventEmitter.emit('success', objects) })
.on('error', function(err){ customEventEmitter.emit('error', err) })
.on('sql', function(sql) { customEventEmitter.emit('sql', sql)})
......@@ -93,10 +93,10 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) {
var self = this
, chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, sourceKeys = Object.keys(self.__factory.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys)
, targetAssociation = self.association.targetAssociation
, foreignIdentifier = targetAssociation.isSelfAssociation ? targetAssociation.foreignIdentifier : targetAssociation.identifier
, sourceKeys = Object.keys(self.association.source.primaryKeys)
, targetKeys = Object.keys(self.association.target.primaryKeys)
, obsoleteAssociations = []
, changedAssociations = []
, options = {}
......@@ -120,17 +120,25 @@ module.exports = (function() {
if (!newObj) {
obsoleteAssociations.push(old)
} else if (association.hasJoinTableModel) {
} else if (Object(targetAssociation.through) === targetAssociation.through) {
var throughAttributes = newObj[self.association.through.name];
// Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object)
if (throughAttributes instanceof self.association.through.DAO) {
throughAttributes = {};
}
var changedAssociation = {
where: {},
attributes: Utils._.defaults({}, newObj[self.__factory.connectorDAO.name], defaultAttributes)
attributes: Utils._.defaults({}, throughAttributes, defaultAttributes)
}
changedAssociation.where[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id
changedAssociation.where[self.association.identifier] = self.instance[self.association.identifier] || self.instance.id
changedAssociation.where[foreignIdentifier] = newObj[foreignIdentifier] || newObj.id
if (Object.keys(changedAssociation.attributes).length) {
changedAssociations.push(changedAssociation)
}
}
})
if (obsoleteAssociations.length > 0) {
......@@ -140,32 +148,32 @@ module.exports = (function() {
var where = {}
where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where, options))
chainer.add(self.association.through.destroy(where, options))
}
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {}
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
if (association.hasJoinTableModel) {
attributes = Utils._.defaults(attributes, unassociatedObject[association.connectorDAO.name], defaultAttributes)
if (Object(targetAssociation.through) === targetAssociation.through) {
attributes = Utils._.defaults(attributes, unassociatedObject[targetAssociation.through.name], defaultAttributes)
}
return attributes
})
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk, options))
chainer.add(self.association.through.bulkCreate(bulk, options))
}
if (changedAssociations.length > 0) {
changedAssociations.forEach(function (assoc) {
chainer.add(self.__factory.connectorDAO.update(assoc.attributes, assoc.where, options))
chainer.add(self.association.through.update(assoc.attributes, assoc.where, options))
})
}
......@@ -178,26 +186,28 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) {
var attributes = {}
, association = this.__factory.target.associations[this.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier;
, targetAssociation = this.association.targetAssociation
, foreignIdentifier = targetAssociation.isSelfAssociation ? targetAssociation.foreignIdentifier : targetAssociation.identifier;
var sourceKeys = Object.keys(this.__factory.source.primaryKeys);
var targetKeys = Object.keys(this.__factory.target.primaryKeys);
var sourceKeys = Object.keys(this.association.source.primaryKeys);
var targetKeys = Object.keys(this.association.target.primaryKeys);
attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[this.association.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id)
if (exists) { // implies hasJoinTableModel === true
if (exists) {
var where = attributes
attributes = Utils._.defaults({}, newAssociation[association.connectorDAO.name], additionalAttributes)
attributes = Utils._.defaults({}, newAssociation[targetAssociation.through.name], additionalAttributes)
association.connectorDAO.update(attributes, where).proxy(emitterProxy)
if (Object.keys(attributes).length) {
targetAssociation.through.update(attributes, where).proxy(emitterProxy)
} else {
if (association.hasJoinTableModel === true) {
attributes = Utils._.defaults(attributes, newAssociation[association.connectorDAO.name], additionalAttributes)
emitterProxy.emit('success')
}
} else {
attributes = Utils._.defaults(attributes, newAssociation[targetAssociation.through.name], additionalAttributes)
this.__factory.connectorDAO.create(attributes)
this.association.through.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
......
var Utils = require("./../utils")
, DataTypes = require('./../data-types')
, Helpers = require('./helpers')
, _ = require('lodash')
var HasManySingleLinked = require("./has-many-single-linked")
, HasManyMultiLinked = require("./has-many-double-linked")
module.exports = (function() {
var HasMany = function(srcDAO, targetDAO, options) {
var HasMany = function(source, target, options) {
var self = this
this.associationType = 'HasMany'
this.source = srcDAO
this.target = targetDAO
this.source = source
this.target = target
this.targetAssociation = null
this.options = options
this.useJunctionTable = this.options.useJunctionTable === undefined ? true : this.options.useJunctionTable
this.sequelize = source.daoFactoryManager.sequelize
this.through = options.through
this.isMultiAssociation = true
this.isSelfAssociation = (this.source.tableName === this.target.tableName)
this.hasJoinTableModel = !!this.options.joinTableModel
var combinedTableName;
if (this.hasJoinTableModel) {
combinedTableName = this.options.joinTableModel.tableName
} else if (this.options.joinTableName) {
combinedTableName = this.options.joinTableName
} else {
combinedTableName = Utils.combineTableNames(
this.doubleLinked = false
this.combinedTableName = Utils.combineTableNames(
this.source.tableName,
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
)
/*
* Map joinTableModel/Name to through for BC
*/
if (this.through === undefined) {
this.through = this.options.joinTableModel || this.options.joinTableName;
/*
* If both are undefined, see if useJunctionTable was false (for self associations) - else assume through to be true
*/
if (this.through === undefined) {
if (this.options.useJunctionTable === false) {
this.through = null;
} else {
this.through = true;
}
}
}
/*
* Determine associationAccessor, especially for include options to identify the correct model
*/
this.associationAccessor = this.options.as
if (!this.associationAccessor && (typeof this.through === "string" || Object(this.through) === this.through)) {
this.associationAccessor = this.through.tableName || this.through
}
else if (!this.associationAccessor) {
this.associationAccessor = this.combinedTableName
}
/*
* Find partner DAOFactory if present, to identify double linked association
*/
if (this.through) {
_.each(this.target.associations, function (association, accessor) {
if (self.source === association.target) {
var paired = false
// If through is default, we determine pairing by the accesor value (i.e. DAOFactory's using as won't pair, but regular ones will)
if (self.through === true && accessor === self.associationAccessor) {
paired = true
}
// If through is not default, determine pairing by through value (model/string)
if (self.through !== true && self.options.through === association.options.through) {
paired = true
}
// If paired, set properties identifying both associations as double linked, and allow them to each eachtoerh
if (paired) {
self.doubleLinked = true
association.doubleLinked = true
self.targetAssociation = association
association.targetAssociation = self
}
}
})
}
this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName)
this.options.useHooks = options.useHooks
this.associationAccessor = this.options.as || this.combinedName
/*
* If we are double linked, and through is either default or a string, we create the through model and set it on both associations
*/
if (this.doubleLinked) {
if (this.through === true) {
this.through = this.combinedTableName
}
if (typeof this.through === "string") {
this.through = this.sequelize.define(this.through, {}, _.extend(this.options, {
tableName: this.through
}))
this.targetAssociation.through = this.through
}
}
this.options.tableName = this.combinedName = (this.through === Object(this.through) ? this.through.tableName : this.through)
var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
......@@ -46,27 +116,27 @@ module.exports = (function() {
// the id is in the target table
// or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() {
var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor)
var doubleLinked = this.doubleLinked
, self = this
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
// is there already a single sided association between the source and the target?
// or is the association on the model itself?
if ((this.isSelfAssociation && this.useJunctionTable) || multiAssociation) {
if ((this.isSelfAssociation && this.through) || doubleLinked) {
// remove the obsolete association identifier from the source
if (this.isSelfAssociation) {
this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored)
} else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier
this.foreignIdentifier = this.targetAssociation.identifier
this.targetAssociation.foreignIdentifier = this.identifier
if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[this.foreignIdentifier]
}
if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) {
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier]
delete this.targetAssociation.source.rawAttributes[this.identifier]
}
}
......@@ -79,28 +149,18 @@ module.exports = (function() {
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true}
if (this.hasJoinTableModel === true) {
this.connectorDAO = this.options.joinTableModel
// remove any previously defined PKs
Utils._.each(this.connectorDAO.attributes, function(dataTypeString, attributeName) {
Utils._.each(this.through.attributes, function(dataTypeString, attributeName) {
if (dataTypeString.toString().indexOf('PRIMARY KEY') !== -1) {
delete self.connectorDAO.rawAttributes[attributeName]
delete self.through.rawAttributes[attributeName]
}
})
this.connectorDAO.rawAttributes = Utils._.merge(this.connectorDAO.rawAttributes, combinedTableAttributes)
this.connectorDAO.init(this.connectorDAO.daoFactoryManager)
} else {
this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
}
if (!this.isSelfAssociation) {
this.target.associations[this.associationAccessor].connectorDAO = this.connectorDAO
}
this.through.rawAttributes = Utils._.merge(this.through.rawAttributes, combinedTableAttributes)
this.through.init(this.through.daoFactoryManager)
if (this.options.syncOnAssociation) {
this.connectorDAO.sync()
this.through.sync()
}
} else {
var newAttributes = {}
......@@ -120,7 +180,7 @@ module.exports = (function() {
var self = this
obj[this.accessors.get] = function(options) {
var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked
var Class = Object(self.through) === self.through ? HasManyMultiLinked : HasManySingleLinked
return new Class(self, this).injectGetter(options)
}
......@@ -178,15 +238,10 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]()
.success(function(oldAssociatedObjects) {
var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked
var Class = self.doubleLinked ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectSetter(emitter, oldAssociatedObjects, newAssociatedObjects, defaultAttributes)
})
.error(function(err) {
emitter.emit('error', err)
})
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
.proxy(emitter, {events: ['error', 'sql']})
}).run()
}
......@@ -200,10 +255,10 @@ module.exports = (function() {
where[newAssociatedObject.daoFactory.tableName+'.'+primaryKey] = newAssociatedObject[primaryKey]
return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]({ where: where })
.error(function(err){ emitter.emit('error', err)})
.proxy(emitter, {events: ['error', 'sql']})
.success(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0 || self.hasJoinTableModel === true) {
var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked
if (currentAssociatedObjects.length === 0 || Object(self.through) === self.through) {
var Class = self.doubleLinked ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectAdder(emitter, newAssociatedObject, additionalAttributes, !!currentAssociatedObjects.length)
} else {
emitter.emit('success', newAssociatedObject);
......@@ -214,7 +269,7 @@ module.exports = (function() {
obj[this.accessors.remove] = function(oldAssociatedObject) {
var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() {
return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]().success(function(currentAssociatedObjects) {
var newAssociations = []
, oldAssociations = []
......@@ -242,12 +297,10 @@ module.exports = (function() {
var run = function(err) {
if (!!err) {
return customEventEmitter.emit('error', err)
return emitter.emit('error', err)
}
instance[self.accessors.set](newAssociations)
.success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('error', err) })
instance[self.accessors.set](newAssociations).proxy(emitter)
}
if (oldAssociations.length > 0) {
......@@ -256,8 +309,7 @@ module.exports = (function() {
run()
}
})
})
return customEventEmitter.run()
}).run()
}
return this
......
......@@ -8,21 +8,26 @@ module.exports = (function() {
this.source = srcDAO
this.target = targetDAO
this.options = options
this.isSingleAssociation = true
this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.target.options.language) + "Id", this.options.underscored)
}
if (!this.options.as) {
this.options.as = Utils.singularize(this.target.tableName, this.target.options.language)
}
this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName
? Utils.combineTableNames(this.target.tableName, this.options.as)
: this.options.as
this.options.useHooks = options.useHooks
this.accessors = {
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)))
get: Utils._.camelize('get_' + this.options.as),
set: Utils._.camelize('set_' + this.options.as)
}
}
......
......@@ -6,14 +6,14 @@ var Utils = require("./../utils")
/* Defines Mixin for all models. */
var Mixin = module.exports = function(){}
Mixin.hasOne = function(associatedDAO, options) {
Mixin.hasOne = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in the foreign table
var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options))
var association = new HasOne(this, associatedDAOFactory, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype);
......@@ -22,14 +22,14 @@ Mixin.hasOne = function(associatedDAO, options) {
return this
}
Mixin.belongsTo = function(associatedDAO, options) {
Mixin.belongsTo = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in this table
var association = new BelongsTo(this, associatedDAO, Utils._.extend(options, this.options))
var association = new BelongsTo(this, associatedDAOFactory, Utils._.extend(options, this.options))
this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype)
......@@ -38,14 +38,14 @@ Mixin.belongsTo = function(associatedDAO, options) {
return this
}
Mixin.hasMany = function(associatedDAO, options) {
Mixin.hasMany = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
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
var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options))
var association = new HasMany(this, associatedDAOFactory, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype)
......
......@@ -168,7 +168,10 @@ module.exports = (function() {
})
this.DAO.prototype.__factory = this
this.DAO.prototype.daoFactory = this
this.DAO.prototype.Model = this
this.DAO.prototype.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues)
this.DAO.prototype.daoFactoryName = this.name
return this
}
......@@ -555,13 +558,23 @@ module.exports = (function() {
DAOFactory.prototype.build = function(values, options) {
options = options || { isNewRecord: true, isDirty: true }
var self = this
, instance = new this.DAO(values, this.options, options.isNewRecord)
if (options.hasOwnProperty('include') && (!options.includeValidated || !options.includeNames)) {
options.includeNames = []
options.include = options.include.map(function(include) {
include = validateIncludedElement.call(this, include)
options.includeNames.push(include.as)
return include
}.bind(this))
}
instance.isNewRecord = options.isNewRecord
instance.daoFactoryName = this.name
instance.daoFactory = this
instance.isDirty = options.isDirty
if (options.includeNames) {
options.includeNames = options.includeNames.concat(options.includeNames.map(function (key) {
return key.slice(0,1).toLowerCase() + key.slice(1)
}))
}
var self = this
, instance = new this.DAO(values, options)
return instance
}
......@@ -843,6 +856,9 @@ module.exports = (function() {
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
*/
DAOFactory.prototype.destroy = function(where, options) {
options = options || {}
options.force = options.force === undefined ? false : Boolean(options.force)
var self = this
, query = null
, args = []
......@@ -855,7 +871,7 @@ module.exports = (function() {
where = newWhere || where
if (self.options.timestamps && self.options.paranoid) {
if (self.options.timestamps && self.options.paranoid && options.force === false) {
var attr = Utils._.underscoredIf(self.options.deletedAt, self.options.underscored)
var attrValueHash = {}
attrValueHash[attr] = Utils.now()
......@@ -1118,18 +1134,20 @@ module.exports = (function() {
options = options || {}
options.where = options.where || {}
var deletedAtCol = Utils._.underscoredIf(this.options.deletedAt, this.options.underscored)
// Don't overwrite our explicit deletedAt search value if we provide one
if (!!options.where[this.options.deletedAt]) {
if (!!options.where[deletedAtCol]) {
return options
}
if (typeof options.where === "string") {
options.where += ' AND ' + this.QueryInterface.quoteIdentifier(this.options.deletedAt) + ' IS NULL '
options.where += ' AND ' + this.QueryInterface.quoteIdentifier(deletedAtCol) + ' IS NULL '
}
else if (Array.isArray(options.where)) {
options.where[0] += ' AND ' + this.QueryInterface.quoteIdentifier(this.options.deletedAt) + ' IS NULL '
options.where[0] += ' AND ' + this.QueryInterface.quoteIdentifier(deletedAtCol) + ' IS NULL '
} else {
options.where[this.options.deletedAt] = null
options.where[deletedAtCol] = null
}
}
......@@ -1215,6 +1233,11 @@ module.exports = (function() {
var usesAlias = (include.as !== include.daoFactory.tableName)
, association = (usesAlias ? this.getAssociationByAlias(include.as) : this.getAssociation(include.daoFactory))
// If single (1:1) association, we singularize the alias, so it will match the automatically generated alias of belongsTo/HasOne
if (association && !usesAlias && association.isSingleAssociation) {
include.as = Utils.singularize(include.daoFactory.tableName, include.daoFactory.options.language)
}
// check if the current daoFactory is actually associated with the passed daoFactory
if (!!association && (!association.options.as || (association.options.as === include.as))) {
include.association = association
......
......@@ -3,16 +3,22 @@ var Utils = require("./utils")
, DaoValidator = require("./dao-validator")
, DataTypes = require("./data-types")
, hstore = require('./dialects/postgres/hstore')
, _ = require('lodash')
module.exports = (function() {
var DAO = function(values, options, isNewRecord) {
var DAO = function(values, options) {
this.dataValues = {}
this.__options = options
this.hasPrimaryKeys = options.hasPrimaryKeys
this.selectedValues = values
this.__options = this.__factory.options
this.options = options
this.hasPrimaryKeys = this.__factory.options.hasPrimaryKeys
// What is selected values even used for?
this.selectedValues = options.include ? _.omit(values, options.includeNames) : values
this.__eagerlyLoadedAssociations = []
this.isNewRecord = options.isNewRecord
initAttributes.call(this, values, isNewRecord)
initAttributes.call(this, values, options)
this.isDirty = options.isDirty
}
Utils._.extend(DAO.prototype, Mixin.prototype)
......@@ -36,17 +42,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'values', {
get: function() {
var result = {}
, self = this
this.attributes.concat(this.__eagerlyLoadedAssociations).forEach(function(attr) {
result[attr] = self.dataValues.hasOwnProperty(attr)
? self.dataValues[attr]
: self[attr]
;
})
return result
return this.dataValues
}
})
......@@ -104,7 +100,7 @@ module.exports = (function() {
options = Utils._.extend({}, options, fieldsOrOptions)
var self = this
, values = options.fields ? {} : this.dataValues
, values = options.fields ? {} : (this.options.includeNames ? _.omit(this.dataValues, this.options.includeNames) : this.dataValues)
, updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
......@@ -215,7 +211,7 @@ module.exports = (function() {
return emitter.emit('error', err)
}
result.dataValues = newValues
result.dataValues = _.extend(result.dataValues, newValues)
emitter.emit('success', result)
})
})
......@@ -241,7 +237,7 @@ module.exports = (function() {
this.__factory.find({
where: where,
limit: 1,
include: this.__eagerlyLoadedOptions || []
include: this.options.include || []
}, options)
.on('sql', function(sql) { emitter.emit('sql', sql) })
.on('error', function(error) { emitter.emit('error', error) })
......@@ -327,6 +323,9 @@ module.exports = (function() {
}
DAO.prototype.destroy = function(options) {
options = options || {}
options.force = options.force === undefined ? false : Boolean(options.force)
var self = this
, query = null
......@@ -336,7 +335,7 @@ module.exports = (function() {
return emitter.emit('error', err)
}
if (self.__options.timestamps && self.__options.paranoid) {
if (self.__options.timestamps && self.__options.paranoid && options.force === false) {
var attr = Utils._.underscoredIf(self.__options.deletedAt, self.__options.underscored)
self.dataValues[attr] = new Date()
query = self.save(options)
......@@ -469,25 +468,20 @@ module.exports = (function() {
}
DAO.prototype.toJSON = function() {
return this.values;
return this.dataValues;
}
// private
var initAttributes = function(values, isNewRecord) {
var initAttributes = function(values, options) {
// set id to null if not passed as value, a newly created dao has no id
var defaults = this.hasPrimaryKeys ? {} : { id: null },
attrs = {},
key;
// add all passed values to the dao and store the attribute names in this.attributes
for (key in values) {
if (values.hasOwnProperty(key)) {
this.addAttribute(key, values[key])
}
}
if (isNewRecord) {
if (options.isNewRecord) {
if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function(valueFn, key) {
if (!defaults.hasOwnProperty(key)) {
......@@ -532,22 +526,43 @@ module.exports = (function() {
}
for (key in attrs) {
this.addAttribute(key, attrs[key])
}
if (options.include && options.includeNames.indexOf(key) !== -1) {
if (!Array.isArray(attrs[key])) attrs[key] = [attrs[key]];
// this.addAttributes COMPLETELY destroys the structure of our DAO due to __defineGetter__ resetting the object
// so now we have to rebuild for bulkInserts, bulkUpdates, etc.
var rebuild = {}
var include = _.find(options.include, function (include) {
return include.as === key || (include.as.slice(0,1).toLowerCase() + include.as.slice(1)) === key
})
var association = include.association
, self = this
// Get the correct map....
Utils._.each(this.attributes, function(key) {
if (this.dataValues.hasOwnProperty(key)) {
rebuild[key] = this.dataValues[key]
var accessor = Utils._.camelize(key)
// downcase the first char
accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1)
attrs[key].forEach(function(data) {
var daoInstance = include.daoFactory.build(data, { isNewRecord: false, isDirty: false })
, isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
if (association.isSingleAssociation) {
accessor = Utils.singularize(accessor, self.sequelize.language)
self.dataValues[accessor] = isEmpty ? null : daoInstance
self[accessor] = self.dataValues[accessor]
} else {
if (!self.dataValues[accessor]) {
self.dataValues[accessor] = []
self[accessor] = self.dataValues[accessor]
}
}.bind(this))
// This allows for aliases, etc.
this.dataValues = Utils._.extend(rebuild, this.dataValues)
if (!isEmpty) {
self.dataValues[accessor].push(daoInstance)
}
}
}.bind(this))
} else {
this.addAttribute(key, attrs[key])
}
}
}
return DAO
......
......@@ -188,7 +188,7 @@ module.exports = {
FLOAT: FLOAT,
NOW: 'NOW',
BLOB: BLOB,
UUID: 'CHAR(36)',
UUID: 'UUID',
UUIDV1: 'UUIDV1',
UUIDV4: 'UUIDV4',
......
......@@ -388,7 +388,7 @@ module.exports = (function() {
var table = include.daoFactory.tableName
, as = include.as
if (!include.association.connectorDAO) {
if (!(Object(include.association.through) === include.association.through)) {
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
, tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
......@@ -407,7 +407,7 @@ module.exports = (function() {
, identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
var tableJunction = include.association.connectorDAO.tableName
var tableJunction = include.association.through.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableSource) + "." + this.quoteIdentifier(attrSource) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identSource)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
}
......@@ -635,14 +635,14 @@ module.exports = (function() {
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.identifier)
joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField)
} else if (association.connectorDAO){
joinedTables[association.connectorDAO.tableName] = true;
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.connectorDAO.tableName)
} else if (Object(association.through) === association.through) {
joinedTables[association.through.tableName] = true;
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.through.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.source.autoIncrementField)
joins += ' = ' + self.quoteIdentifiers(association.connectorDAO.tableName + '.' + association.identifier)
joins += ' = ' + self.quoteIdentifiers(association.through.tableName + '.' + association.identifier)
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.connectorDAO.tableName + '.' + association.foreignIdentifier)
joins += ' ON ' + self.quoteIdentifiers(association.through.tableName + '.' + association.foreignIdentifier)
joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField)
} else {
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
......@@ -687,7 +687,7 @@ module.exports = (function() {
if (Array.isArray(value)) {
result.push(this.arrayValue(value, key, _key, dao))
} else if ((value) && (typeof value == 'object') && !(value instanceof Date)) {
} else if ((value) && (typeof value == 'object') && !(value instanceof Date) && !Buffer.isBuffer(value)) {
if (!!value.join) {
//using as sentinel for join column => value
_value = this.quoteIdentifiers(value.join)
......
var Utils = require('../../utils')
, CustomEventEmitter = require("../../emitters/custom-event-emitter")
, Dot = require('dottie')
, _ = require('lodash')
module.exports = (function() {
var AbstractQuery = function(database, sequelize, callee, options) {}
......@@ -100,39 +101,6 @@ module.exports = (function() {
}
/**
Shortcut methods (success, ok) for listening for success events.
Params:
- fct: A function that gets executed once the *success* event was triggered.
Result:
The function returns the instance of the query.
*/
AbstractQuery.prototype.success =
AbstractQuery.prototype.ok =
function(fct) {
this.on('success', fct)
return this
}
/**
Shortcut methods (failure, fail, error) for listening for error events.
Params:
- fct: A function that gets executed once the *error* event was triggered.
Result:
The function returns the instance of the query.
*/
AbstractQuery.prototype.failure =
AbstractQuery.prototype.fail =
AbstractQuery.prototype.error =
function(fct) {
this.on('error', fct)
return this
}
/**
* This function is a wrapper for private methods.
*
* @param {String} fctName The name of the private method.
......@@ -167,10 +135,13 @@ module.exports = (function() {
if (!this.options.include) {
return null
}
var tableNames = this.options.include.map(function(include) {
if (!this.options.includeNames) {
this.options.includeNames = this.options.include.map(function(include) {
return include.as
}).filter(function(include) {
})
}
var tableNames = this.options.includeNames.filter(function(include) {
return attribute.indexOf(include + '.') === 0
})
......@@ -181,20 +152,6 @@ module.exports = (function() {
}
}
var queryResultHasJoin = function(results) {
if (!!results[0]) {
var keys = Object.keys(results[0])
for (var i = 0; i < keys.length; i++) {
if (!!findTableNameInAttribute.call(this, keys[i])) {
return true
}
}
}
return false
}
var isInsertQuery = function(results, metaData) {
var result = true
......@@ -244,6 +201,7 @@ module.exports = (function() {
var handleSelectQuery = function(results) {
var result = null
// Raw queries
if (this.options.raw) {
result = results.map(function(result) {
var o = {}
......@@ -258,8 +216,22 @@ module.exports = (function() {
})
result = result.map(Dot.transform)
// Queries with include
} else if (this.options.hasJoin === true) {
result = transformRowsWithEagerLoadingIntoDaos.call(this, results)
this.options.includeNames = this.options.include.map(function (include) {
return include.as
})
results = groupJoinData.call(this, results)
result = results.map(function(result) {
return this.callee.build(result, {
isNewRecord: false,
isDirty: false,
include:this.options.include,
includeNames: this.options.includeNames,
includeValidated: true
})
}.bind(this))
} else if (this.options.hasJoinTableModel === true) {
result = results.map(function(result) {
result = Dot.transform(result)
......@@ -275,6 +247,8 @@ module.exports = (function() {
return mainDao
}.bind(this))
// Regular queries
} else {
result = results.map(function(result) {
return this.callee.build(result, { isNewRecord: false, isDirty: false })
......@@ -289,75 +263,6 @@ module.exports = (function() {
return result
}
var transformRowsWithEagerLoadingIntoDaos = function(results) {
var result = []
result = prepareJoinData.call(this, results)
result = groupDataByCalleeFactory.call(this, result).map(function(result) {
return transformRowWithEagerLoadingIntoDao.call(this, result)
}.bind(this))
return result
}
var transformRowWithEagerLoadingIntoDao = function(result, dao) {
// let's build the actual dao instance first...
dao = dao || this.callee.build(result[this.callee.tableName], { isNewRecord: false, isDirty: false })
// ... and afterwards the prefetched associations
for (var tableName in result) {
if (result.hasOwnProperty(tableName) && (tableName !== this.callee.tableName)) {
buildAssociatedDaoInstances.call(this, tableName, result[tableName], dao)
}
}
return dao
}
var buildAssociatedDaoInstances = function(tableName, associationData, dao) {
var associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(tableName, { attribute: 'tableName' })
, association = null
, self = this
if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory)
} else {
associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(Utils.pluralize(tableName, this.sequelize.language), { attribute: 'tableName' })
if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory)
} else {
association = this.callee.getAssociationByAlias(tableName)
associatedDaoFactory = association.target
}
}
var accessor = Utils._.camelize(tableName)
// downcase the first char
accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1)
associationData.forEach(function(data) {
var daoInstance = associatedDaoFactory.build(data, { isNewRecord: false, isDirty: false })
, isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) {
accessor = Utils.singularize(accessor, self.sequelize.language)
dao[accessor] = isEmpty ? null : daoInstance
} else {
dao[accessor] = dao[accessor] || []
if (!isEmpty) {
dao[accessor].push(daoInstance)
}
}
// add the accessor to the eagerly loaded associations array
dao.__eagerlyLoadedAssociations = Utils._.uniq(dao.__eagerlyLoadedAssociations.concat([accessor]))
dao.__eagerlyLoadedOptions = (this.options && this.options.include) ? this.options.include : []
}.bind(this))
}
var isShowOrDescribeQuery = function() {
var result = false
......@@ -381,15 +286,18 @@ module.exports = (function() {
the associated data by the callee.
Example:
groupDataByCalleeFactory([
groupJoinData([
{
callee: { some: 'data', id: 1 },
some: 'data',
id: 1,
association: { foo: 'bar', id: 1 }
}, {
callee: { some: 'data', id: 1 },
some: 'data',
id: 1,
association: { foo: 'bar', id: 2 }
}, {
callee: { some: 'data', id: 1 },
some: 'data',
id: 1,
association: { foo: 'bar', id: 3 }
}
])
......@@ -399,7 +307,8 @@ module.exports = (function() {
[
{
callee: { some: 'data', id: 1 },
some: 'data',
id: 1,
association: [
{ foo: 'bar', id: 1 },
{ foo: 'bar', id: 2 },
......@@ -408,67 +317,38 @@ module.exports = (function() {
}
]
*/
var groupDataByCalleeFactory = function(data) {
var result = []
, calleeTableName = this.callee.tableName
data.forEach(function(row) {
var calleeData = row[calleeTableName]
, existingEntry = result.filter(function(groupedRow) {
return Utils._.isEqual(groupedRow[calleeTableName], calleeData)
})[0]
var groupJoinData = function(data) {
var self = this
, results = []
, existingResult
, calleeData
data.forEach(function (row) {
row = Dot.transform(row)
calleeData = _.omit(row, self.options.includeNames)
if (!existingEntry) {
existingEntry = {}
result.push(existingEntry)
existingEntry[calleeTableName] = calleeData
existingResult = _.find(results, function (result) {
return Utils._.isEqual(_.omit(result, self.options.includeNames), calleeData)
})
if (!existingResult) {
results.push(existingResult = calleeData)
}
for (var attrName in row) {
if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) {
existingEntry[attrName] = existingEntry[attrName] || []
var attrRowExists = existingEntry[attrName].some(function(attrRow) {
if (row.hasOwnProperty(attrName) && Object(row[attrName]) === row[attrName] && self.options.includeNames.indexOf(attrName) !== -1) {
existingResult[attrName] = existingResult[attrName] || []
var attrRowExists = existingResult[attrName].some(function(attrRow) {
return Utils._.isEqual(attrRow, row[attrName])
})
if (!attrRowExists) {
existingEntry[attrName].push(row[attrName])
existingResult[attrName].push(row[attrName])
}
}
}
})
return result
}
/**
* This function will prepare the result of select queries with joins.
*
* @param {Array} data This array contains objects.
* @return {Array} The array will have the needed format for groupDataByCalleeFactory.
*/
var prepareJoinData = function(data) {
var result = data.map(function(row) {
var nestedRow = {}
for (var key in row) {
if (row.hasOwnProperty(key)) {
var tableName = findTableNameInAttribute.call(this, key)
if (!!tableName) {
nestedRow[tableName] = nestedRow[tableName] || {}
nestedRow[tableName][key.replace(tableName + '.', '')] = row[key]
} else {
nestedRow[this.callee.tableName] = nestedRow[this.callee.tableName] || {}
nestedRow[this.callee.tableName][key] = row[key]
}
}
}
return nestedRow
}.bind(this))
return result
return results
}
return AbstractQuery
......
......@@ -88,6 +88,7 @@ module.exports = (function() {
.on('error', function(err) {
errorDetected = true
self.emit('sql', self.sql)
err.sql = sql
self.emit('error', err, self.callee)
})
.on('end', function(info) {
......
......@@ -50,7 +50,7 @@ module.exports = (function() {
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr]
var dataType = this.mysqlDataTypeMapping(tableName, attr, attributes[attr])
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr)
......@@ -117,7 +117,7 @@ module.exports = (function() {
attrString.push(Utils._.template('`<%= attrName %>` <%= definition %>')({
attrName: attrName,
definition: definition
definition: this.mysqlDataTypeMapping(tableName, attrName, definition)
}))
}
......@@ -496,6 +496,14 @@ module.exports = (function() {
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteIdentifier(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';'
},
mysqlDataTypeMapping: function(tableName, attr, dataType) {
if (Utils._.includes(dataType, 'UUID')) {
dataType = dataType.replace(/UUID/, 'CHAR(36) BINARY')
}
return dataType
}
}
......
......@@ -27,6 +27,7 @@ module.exports = (function() {
this.emit('sql', this.sql)
if (err) {
err.sql = sql
this.emit('error', err, this.callee)
} else {
this.emit('success', this.formatResults(results))
......
......@@ -27,6 +27,7 @@ module.exports = (function() {
this.disconnectTimeoutId = null
this.pendingQueries = 0
this.clientDrained = true
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
process.on('exit', function() {
......@@ -51,6 +52,7 @@ module.exports = (function() {
var self = this
self.pendingQueries++
self.clientDrained = false
return new Utils.CustomEventEmitter(function(emitter) {
self.connect()
......@@ -64,8 +66,6 @@ module.exports = (function() {
.complete(function(err) {
self.endQuery.call(self)
done && done(err) })
.success(function(results) { self.endQuery.call(self) })
.error(function(err) { self.endQuery.call(self) })
.proxy(emitter)
})
}).run()
......@@ -152,6 +152,12 @@ module.exports = (function() {
this.client.connect(function(err, client, done) {
connectCallback(err, client || self.client, done)
})
// Closes a client correctly even if we have backed up queries
// https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', function() {
self.clientDrained = true
})
}
}
......@@ -168,9 +174,9 @@ module.exports = (function() {
}
if (this.client) {
// Closes a client correctly even if we have backed up queries
// https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', this.client.end.bind(this.client))
if (this.clientDrained) {
this.client.end()
}
this.client = null
}
......
......@@ -256,9 +256,12 @@ module.exports = (function() {
},
insertQuery: function(tableName, attrValueHash, attributes) {
var query
, valueQuery = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;"
, emptyQuery = "INSERT INTO <%= table %> DEFAULT VALUES RETURNING *;"
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;"
// Remove serials that are null or undefined, which causes an error in PG
Utils._.forEach(attrValueHash, function(value, key, hash) {
if (tables[tableName]) {
......@@ -284,17 +287,32 @@ module.exports = (function() {
, values: rowValues.join(",")
}
query = replacements.attributes.length ? valueQuery : emptyQuery
return Utils._.template(query)(replacements)
},
bulkInsertQuery: function(tableName, attrValueHashes) {
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;"
, tuples = []
, serials = []
Utils._.forEach(attrValueHashes, function(attrValueHash, i) {
if (i === 0) {
Utils._.forEach(attrValueHash, function(value, key, hash) {
if (tables[tableName] && tables[tableName][key]) {
if (['bigserial', 'serial'].indexOf(tables[tableName][key]) !== -1) {
serials.push(key)
}
}
})
}
Utils._.forEach(attrValueHashes, function(attrValueHash) {
removeSerialsFromHash(tableName, attrValueHash)
tuples.push("(" +
Utils._.values(attrValueHash).map(function(value){
Utils._.map(attrValueHash, function(value, key){
if (serials.indexOf(key) !== -1) {
return 'DEFAULT';
}
return this.escape(value)
}.bind(this)).join(",") +
")")
......@@ -435,8 +453,8 @@ module.exports = (function() {
return Utils._.compact([
"CREATE", options.indicesType, "INDEX", this.quoteIdentifiers(options.indexName),
(options.indexType ? ('USING ' + options.indexType) : undefined),
"ON", this.quoteIdentifiers(tableName), '(' + transformedAttributes.join(', ') + ')'
"ON", this.quoteIdentifiers(tableName), (options.indexType ? ('USING ' + options.indexType) : undefined),
'(' + transformedAttributes.join(', ') + ')'
]).join(' ')
},
......
......@@ -36,6 +36,7 @@ module.exports = (function() {
query.on('error', function(err) {
receivedError = true
err.sql = sql
self.emit('error', err, self.callee)
})
......
......@@ -156,7 +156,9 @@ module.exports = (function() {
insertQuery: function(tableName, attrValueHash) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>);";
var query
, valueQuery = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>);"
, emptyQuery = "INSERT INTO <%= table %> DEFAULT VALUES;"
var replacements = {
table: this.quoteIdentifier(tableName),
......@@ -166,6 +168,7 @@ module.exports = (function() {
}.bind(this)).join(",")
}
query = replacements.attributes.length ? valueQuery : emptyQuery
return Utils._.template(query)(replacements)
},
......
......@@ -44,6 +44,7 @@ module.exports = (function() {
self.emit('sql', self.sql)
if (err) {
err.sql = self.sql
onFailure.call(self, err)
} else {
this.columnTypes = columnTypes
......
......@@ -28,6 +28,15 @@ module.exports = (function() {
return this
}
/**
Shortcut methods (success, ok) for listening for success events.
Params:
- fct: A function that gets executed once the *success* event was triggered.
Result:
The function returns the instance of the query.
*/
CustomEventEmitter.prototype.success =
CustomEventEmitter.prototype.ok =
function(fct) {
......@@ -35,6 +44,15 @@ module.exports = (function() {
return this
}
/**
Shortcut methods (failure, fail, error) for listening for error events.
Params:
- fct: A function that gets executed once the *error* event was triggered.
Result:
The function returns the instance of the query.
*/
CustomEventEmitter.prototype.failure =
CustomEventEmitter.prototype.fail =
CustomEventEmitter.prototype.error =
......
......@@ -660,6 +660,7 @@ module.exports = (function() {
var sql = this.QueryGenerator.selectQuery(tableName, options, factory)
queryOptions = Utils._.extend({}, queryOptions, { include: options.include })
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
}
......
......@@ -79,7 +79,7 @@ module.exports = (function() {
define: {},
query: {},
sync: {},
logging: console.log,
logging: false,
omitNull: false,
queue: true,
native: false,
......
Utils = require('./utils')
var Utils = require('./utils')
var TransactionManager = module.exports = function(sequelize) {
this.sequelize = sequelize
......
var cJSON = require('circular-json')
, _ = require('lodash') // Don't require Utils here, as it creates a circular dependency
var ParameterValidator = module.exports = {
check: function(value, expectation, options) {
options = Utils._.extend({
options = _.extend({
throwError: true,
deprecated: false,
deprecationWarning: generateDeprecationWarning(value, expectation, options),
......
......@@ -64,7 +64,8 @@
"chai-spies": "~0.5.1",
"lcov-result-merger": "0.0.2",
"istanbul": "~0.1.45",
"coveralls": "~2.5.0"
"coveralls": "~2.5.0",
"async": "~0.2.9"
},
"keywords": [
"mysql",
......
......@@ -16,7 +16,7 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' })
expect(Object.keys(Group.associations)).to.deep.equal(['Users', 'primaryUsers', 'secondaryUsers'])
expect(Object.keys(Group.associations)).to.deep.equal(['User', 'primaryUsers', 'secondaryUsers'])
})
})
......
......@@ -676,10 +676,8 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
{ tableName: 'tasks' }
)
this.User.hasMany(this.Task,
{ joinTableName: 'user_has_tasks' }
)
this.Task.hasMany(this.User)
this.User.hasMany(this.Task, { joinTableName: 'user_has_tasks' })
this.Task.hasMany(this.User, { joinTableName: 'user_has_tasks' })
this.User.sync({ force: true }).success(function() {
self.Task.sync({force: true}).success(function() {
......@@ -693,16 +691,18 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
expect(associationName).not.to.equal(this.User.tableName)
expect(associationName).not.to.equal(this.Task.tableName)
var joinTableName = this.User.associations[associationName].options.joinTableName
if (typeof joinTableName !== 'undefined') {
expect(joinTableName).to.equal(associationName)
var through = this.User.associations[associationName].through
if (typeof through !== 'undefined') {
expect(through.tableName).to.equal(associationName)
}
var tableName = this.User.associations[associationName].options.tableName
if (typeof tableName !== 'undefined') {
expect(tableName).to.equal(associationName)
}
}
setTimeout(function () {
done()
}, 50)
})
})
......@@ -766,8 +766,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
describe('inserting in join table', function () {
describe('add', function () {
it('should insert data provided on the object into the join table', function (done) {
var self = this
......@@ -800,6 +798,32 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
it('should be able to add twice (second call result in UPDATE call) without any attributes (and timestamps off) on the through model', function (done) {
var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
, Task = this.sequelize.define('Task', {}, {timestamps: false})
, WorkerTasks = this.sequelize.define('WorkerTasks', {}, {timestamps: false})
Worker.hasMany(Task, { through: WorkerTasks })
Task.hasMany(Worker, { through: WorkerTasks })
this.sequelize.sync().done(function(err) {
expect(err).not.to.be.ok
Worker.create().done(function (err, worker) {
expect(err).not.to.be.ok
Task.create().done(function (err, task) {
expect(err).not.to.be.ok
worker.addTask(task).done(function (err) {
expect(err).not.to.be.ok
worker.addTask(task).done(function (err) {
expect(err).not.to.be.ok
done()
})
})
})
})
})
})
})
describe('set', function () {
......@@ -832,6 +856,68 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
it('should be able to set twice (second call result in UPDATE calls) without any attributes (and timestamps off) on the through model', function (done) {
var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
, Task = this.sequelize.define('Task', {}, {timestamps: false})
, WorkerTasks = this.sequelize.define('WorkerTasks', {}, {timestamps: false})
Worker.hasMany(Task, { through: WorkerTasks })
Task.hasMany(Worker, { through: WorkerTasks })
this.sequelize.sync().done(function(err) {
expect(err).not.to.be.ok
Worker.create().done(function (err, worker) {
expect(err).not.to.be.ok
Task.bulkCreate([{}, {}]).done(function (err) {
expect(err).not.to.be.ok
Task.findAll().done(function (err, tasks) {
expect(err).not.to.be.ok
worker.setTasks(tasks).done(function (err) {
worker.setTasks(tasks).done(function (err) {
expect(err).not.to.be.ok
done()
})
})
})
})
})
})
})
})
})
describe('removing from the join table', function () {
it('should remove a single entry without any attributes (and timestamps off) on the through model', function (done) {
var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
, Task = this.sequelize.define('Task', {}, {timestamps: false})
, WorkerTasks = this.sequelize.define('WorkerTasks', {}, {timestamps: false})
Worker.hasMany(Task, { through: WorkerTasks })
Task.hasMany(Worker, { through: WorkerTasks })
this.sequelize.sync().done(function(err) {
expect(err).not.to.be.ok
Worker.create({}).done(function (err, worker) {
expect(err).not.to.be.ok
Task.bulkCreate([{}, {}]).done(function (err) {
expect(err).not.to.be.ok
Task.findAll().done(function (err, tasks) {
expect(err).not.to.be.ok
worker.setTasks(tasks).done(function (err) {
worker.removeTask(tasks[0]).done(function (err) {
expect(err).not.to.be.ok
worker.getTasks().done(function (err, tasks) {
expect(tasks.length).to.equal(1)
done()
})
})
})
})
})
})
})
})
})
})
......@@ -900,6 +986,77 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
describe('alias', function () {
it("creates the join table when through is a string", function (done) {
var self = this
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
User.hasMany(Group, { as: 'MyGroups', through: 'group_user'})
Group.hasMany(User, { as: 'MyUsers', through: 'group_user'})
this.sequelize.sync({force:true}).success(function () {
self.sequelize.getQueryInterface().showAllTables().success(function (result) {
expect(result.indexOf('group_user')).not.to.equal(-1)
done()
})
})
})
it("creates the join table when through is a model", function (done) {
var self = this
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
, UserGroup = this.sequelize.define('GroupUser', {}, {tableName: 'user_groups'})
User.hasMany(Group, { as: 'MyGroups', through: UserGroup})
Group.hasMany(User, { as: 'MyUsers', through: UserGroup})
this.sequelize.sync({force:true}).success(function () {
self.sequelize.getQueryInterface().showAllTables().success(function (result) {
expect(result.indexOf('user_groups')).not.to.equal(-1)
done()
})
})
})
it("correctly identifies its counterpart when through is a string", function (done) {
var self = this
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
User.hasMany(Group, { as: 'MyGroups', through: 'group_user'})
Group.hasMany(User, { as: 'MyUsers', through: 'group_user'})
expect(Group.associations.MyUsers.through === User.associations.MyGroups.through);
expect(Group.associations.MyUsers.through.rawAttributes.UserId).to.exist;
expect(Group.associations.MyUsers.through.rawAttributes.GroupId).to.exist;
setTimeout(function () {
done()
}, 50)
})
it("correctly identifies its counterpart when through is a model", function (done) {
var self = this
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
, UserGroup = this.sequelize.define('GroupUser', {}, {tableName: 'user_groups'})
User.hasMany(Group, { as: 'MyGroups', through: UserGroup})
Group.hasMany(User, { as: 'MyUsers', through: UserGroup})
expect(Group.associations.MyUsers.through === User.associations.MyGroups.through);
expect(Group.associations.MyUsers.through.rawAttributes.UserId).to.exist;
expect(Group.associations.MyUsers.through.rawAttributes.GroupId).to.exist;
setTimeout(function () {
done()
}, 50)
})
})
})
describe("Foreign key constraints", function() {
......
......@@ -16,7 +16,7 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
Group.hasOne(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.hasOne(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' })
expect(Object.keys(Group.associations)).to.deep.equal(['Users', 'primaryUsers', 'secondaryUsers'])
expect(Object.keys(Group.associations)).to.deep.equal(['User', 'primaryUsers', 'secondaryUsers'])
})
})
......
......@@ -11,6 +11,7 @@ var chai = require('chai')
, datetime = require('chai-datetime')
, _ = require('lodash')
, moment = require('moment')
, async = require('async')
chai.use(datetime)
chai.Assertion.includeStack = true
......@@ -227,6 +228,24 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
it('should work with both paranoid and underscored being true', function(done) {
var UserTable = this.sequelize.define('UserCol', {
aNumber: Sequelize.INTEGER
}, {
paranoid: true,
underscored: true
})
UserTable.sync({force: true}).success(function() {
UserTable.create({aNumber: 30}).success(function(user) {
UserTable.count().success(function(c) {
expect(c).to.equal(1)
done()
})
})
})
})
})
describe('build', function() {
......@@ -349,6 +368,101 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(p.price2).to.equal(20)
done()
})
describe('include', function () {
it('should support basic includes', function () {
var Product = this.sequelize.define('Product', {
title: Sequelize.STRING
})
var Tag = this.sequelize.define('Tag', {
name: Sequelize.STRING
})
var User = this.sequelize.define('User', {
first_name: Sequelize.STRING,
last_name: Sequelize.STRING
})
Product.hasMany(Tag)
Product.belongsTo(User)
var product = Product.build({
id: 1,
title: 'Chair',
tags: [
{id: 1, name: 'Alpha'},
{id: 2, name: 'Beta'}
],
user: {
id: 1,
first_name: 'Mick',
last_name: 'Hansen'
}
}, {
include: [
User,
Tag
]
})
expect(product.tags).to.be.ok
expect(product.tags.length).to.equal(2)
expect(product.tags[0].Model).to.equal(Tag)
expect(product.user).to.be.ok
expect(product.user.Model).to.equal(User)
})
it('should support includes with aliases', function () {
var Product = this.sequelize.define('Product', {
title: Sequelize.STRING
})
var Tag = this.sequelize.define('Tag', {
name: Sequelize.STRING
})
var User = this.sequelize.define('User', {
first_name: Sequelize.STRING,
last_name: Sequelize.STRING
})
Product.hasMany(Tag, {as: 'Categories'})
Product.hasMany(User, {as: 'Followers', through: 'product_followers'})
User.hasMany(Product, {as: 'Following', through: 'product_followers'})
var product = Product.build({
id: 1,
title: 'Chair',
categories: [
{id: 1, name: 'Alpha'},
{id: 2, name: 'Beta'},
{id: 3, name: 'Charlie'},
{id: 4, name: 'Delta'}
],
followers: [
{
id: 1,
first_name: 'Mick',
last_name: 'Hansen'
},
{
id: 2,
first_name: 'Jan',
last_name: 'Meier'
}
]
}, {
include: [
{model: User, as: 'Followers'},
{model: Tag, as: 'Categories'}
]
})
expect(product.categories).to.be.ok
expect(product.categories.length).to.equal(4)
expect(product.categories[0].Model).to.equal(Tag)
expect(product.followers).to.be.ok
expect(product.followers.length).to.equal(2)
expect(product.followers[0].Model).to.equal(User)
})
})
})
describe('findOrInitialize', function() {
......@@ -429,105 +543,20 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
describe('findOrCreate', function () {
it("supports transactions", function(done) {
var self = this
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('user_with_transaction', { username: Sequelize.STRING, data: Sequelize.STRING })
User
.sync({ force: true })
.success(function() {
sequelize.transaction(function(t) {
User.findOrCreate({ username: 'Username' }, { data: 'some data' }, { transaction: t }).complete(function(err) {
expect(err).to.be.null
User.count().success(function(count) {
expect(count).to.equal(0)
t.commit().success(function() {
User.count().success(function(count) {
expect(count).to.equal(1)
done()
})
})
})
})
})
})
})
})
it("returns instance if already existent. Single find field.", function(done) {
var self = this,
data = {
username: 'Username'
};
this.User.create(data).success(function (user) {
self.User.findOrCreate({
username: user.username
}).success(function (_user, created) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(created).to.be.false
done()
})
})
})
it("Returns instance if already existent. Multiple find fields.", function(done) {
var self = this,
data = {
username: 'Username',
data: 'ThisIsData'
};
this.User.create(data).success(function (user) {
self.User.findOrCreate(data).done(function (err, _user, created) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(_user.data).to.equal('ThisIsData')
expect(created).to.be.false
done()
})
})
})
it("creates new instance with default value.", function(done) {
var data = {
username: 'Username'
},
default_values = {
data: 'ThisIsData'
};
this.User.findOrCreate(data, default_values).success(function(user, created) {
expect(user.username).to.equal('Username')
expect(user.data).to.equal('ThisIsData')
expect(created).to.be.true
done()
})
})
})
describe('create', function() {
describe('update', function() {
it('supports transactions', function(done) {
var self = this
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('user_with_transaction', { username: Sequelize.STRING })
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'user' }, { transaction: t }).success(function() {
User.count().success(function(count) {
expect(count).to.equal(0)
t.commit().success(function() {
User.count().success(function(count) {
expect(count).to.equal(1)
done()
})
User.update({ username: 'bar' }, {}, { transaction: t }).success(function() {
User.all().success(function(users1) {
User.all({ transaction: t }).success(function(users2) {
expect(users1[0].username).to.equal('foo')
expect(users2[0].username).to.equal('bar')
t.rollback().success(function(){ done() })
})
})
})
......@@ -535,210 +564,67 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
it('is possible to use casting when creating an instance', function (done) {
var self = this
, type = Support.dialectIsMySQL() ? 'signed' : 'integer'
, _done = _.after(2, function() {
done()
})
this.User.create({
intVal: this.sequelize.cast('1', type)
}).on('sql', function (sql) {
expect(sql).to.match(new RegExp('CAST\\(1 AS ' + type.toUpperCase() + '\\)'))
_done()
})
.success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(1)
_done()
})
})
it('updates the attributes that we select only without updating createdAt', function(done) {
var User = this.sequelize.define('User1', {
username: Sequelize.STRING,
secretValue: Sequelize.STRING
}, {
paranoid:true
})
it('is possible to use casting multiple times mixed in with other utilities', function (done) {
var self = this
, type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer')
, _done = _.after(2, function() {
User.sync({ force: true }).success(function() {
User.create({username: 'Peter', secretValue: '42'}).success(function(user) {
user.updateAttributes({ secretValue: '43' }, ['secretValue']).on('sql', function(sql) {
expect(sql).to.match(/UPDATE\s+[`"]+User1s[`"]+\s+SET\s+[`"]+secretValue[`"]='43',[`"]+updatedAt[`"]+='[^`",]+'\s+WHERE [`"]+id[`"]+=1/)
done()
})
if (Support.dialectIsMySQL()) {
type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed')
}
this.User.create({
intVal: type
}).on('sql', function (sql) {
if (Support.dialectIsMySQL()) {
expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)')
} else {
expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)')
}
_done()
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
_done()
})
})
})
it('is possible to just use .literal() to bypass escaping', function (done) {
var self = this
this.User.create({
intVal: this.sequelize.literal('CAST(1-2 AS ' + (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER') + ')')
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
done()
})
})
it('allows sql logging of updated statements', function(done) {
var User = this.sequelize.define('User', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
}, {
paranoid:true
})
it('is possible for .literal() to contain other utility functions', function (done) {
var self = this
this.User.create({
intVal: this.sequelize.literal(this.sequelize.cast('1-2', (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER')))
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
User.sync({ force: true }).success(function() {
User.create({ name: 'meg', bio: 'none' }).success(function(u) {
expect(u).to.exist
expect(u).not.to.be.null
u.updateAttributes({name: 'brian'}).on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("UPDATE")).to.be.above(-1)
done()
})
})
})
it('is possible to use funtions when creating an instance', function (done) {
var self = this
this.User.create({
secretValue: this.sequelize.fn('upper', 'sequelize')
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.secretValue).to.equal('SEQUELIZE')
done()
})
})
})
it('is possible to use functions as default values', function (done) {
it('updates only values that match filter', function(done) {
var self = this
, userWithDefaults
if (dialect.indexOf('postgres') === 0) {
this.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"').success(function() {
userWithDefaults = self.sequelize.define('userWithDefaults', {
uuid: {
type: 'UUID',
defaultValue: self.sequelize.fn('uuid_generate_v4')
}
})
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
userWithDefaults.sync({force: true}).success(function () {
userWithDefaults.create({}).success(function (user) {
// uuid validation regex taken from http://stackoverflow.com/a/13653180/800016
expect(user.uuid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
done()
})
})
})
} else if (dialect === 'sqlite') {
// The definition here is a bit hacky. sqlite expects () around the expression for default values, so we call a function without a name
// to enclose the date function in (). http://www.sqlite.org/syntaxdiagrams.html#column-constraint
userWithDefaults = self.sequelize.define('userWithDefaults', {
year: {
type: Sequelize.STRING,
defaultValue: self.sequelize.fn('', self.sequelize.fn('date', 'now'))
}
})
this.User.bulkCreate(data).success(function() {
userWithDefaults.sync({force: true}).success(function () {
userWithDefaults.create({}).success(function (user) {
userWithDefaults.find(user.id).success(function (user) {
var now = new Date()
, pad = function (number) {
if (number > 9) {
return number
}
return '0' + number
}
self.User.update({username: 'Bill'}, {secretValue: '42'})
.success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(3)
expect(user.year).to.equal(now.getUTCFullYear() + '-' + pad(now.getUTCMonth() + 1) + '-' + pad(now.getUTCDate()))
done()
})
})
})
users.forEach(function (user) {
if (user.secretValue == '42') {
expect(user.username).to.equal("Bill")
} else {
// functions as default values are not supported in mysql, see http://stackoverflow.com/a/270338/800016
done()
}
})
it("casts empty arrays correctly for postgresql insert", function(done) {
if (dialect !== "postgres") {
expect('').to.equal('')
return done()
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [], mystr: []}).on('sql', function(sql){
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
done()
})
})
})
it("casts empty array correct for postgres update", function(done) {
if (dialect !== "postgres") {
expect('').to.equal('')
return done()
expect(user.username).to.equal("Bob")
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [1,2,3,4], mystr: ["One", "Two", "Three", "Four"]}).on('success', function(user){
user.myvals = []
user.mystr = []
user.save().on('sql', function(sql) {
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
done()
})
})
})
})
it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: true }
})
User.sync({ force: true }).success(function() {
User.create({ username:'foo' }).success(function() {
User.create({ username: 'foo' }).error(function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/.*Duplicate\ entry.*/)
} else {
expect(err.message).to.match(/.*duplicate\ key\ value.*/)
}
done()
})
......@@ -746,95 +632,52 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it("raises an error if created object breaks definition contraints", function(done) {
var UserNull = this.sequelize.define('UserWithNonNullSmth', {
username: { type: Sequelize.STRING, unique: true },
smth: { type: Sequelize.STRING, allowNull: false }
})
this.sequelize.options.omitNull = false
UserNull.sync({ force: true }).success(function() {
UserNull.create({ username: 'foo2', smth: null }).error(function(err) {
expect(err).to.exist
if (Support.dialectIsMySQL()) {
// We need to allow two different errors for MySQL, see:
// http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_trans_tables
expect(err.message).to.match(/(Column 'smth' cannot be null|Field 'smth' doesn't have a default value)/)
}
else if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
} else {
expect(err.message).to.match(/.*column "smth" violates not-null.*/)
}
UserNull.create({ username: 'foo', smth: 'foo' }).success(function() {
UserNull.create({ username: 'foo', smth: 'bar' }).error(function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/Duplicate entry 'foo' for key 'username'/)
} else {
expect(err.message).to.match(/.*duplicate key value violates unique constraint.*/)
}
it('updates with casting', function (done) {
var self = this
this.User.create({
username: 'John'
}).success(function(user) {
self.User.update({username: self.sequelize.cast('1', 'char')}, {username: 'John'}).success(function() {
self.User.all().success(function(users) {
expect(users[0].username).to.equal('1')
done()
})
})
})
})
})
it('raises an error if you mess up the datatype', function(done) {
it('updates with function and column value', function (done) {
var self = this
expect(function() {
self.sequelize.define('UserBadDataType', {
activity_date: Sequelize.DATe
})
}).to.throw(Error, 'Unrecognized data type for field activity_date')
expect(function() {
self.sequelize.define('UserBadDataType', {
activity_date: {type: Sequelize.DATe}
})
}).to.throw(Error, 'Unrecognized data type for field activity_date')
this.User.create({
username: 'John'
}).success(function(user) {
self.User.update({username: self.sequelize.fn('upper', self.sequelize.col('username'))}, {username: 'John'}).success(function () {
self.User.all().success(function(users) {
expect(users[0].username).to.equal('JOHN')
done()
})
it('sets a 64 bit int in bigint', function(done) {
var User = this.sequelize.define('UserWithBigIntFields', {
big: Sequelize.BIGINT
})
User.sync({ force: true }).success(function() {
User.create({ big: '9223372036854775807' }).on('success', function(user) {
expect(user.big).to.be.equal( '9223372036854775807' )
done()
})
})
})
it('sets auto increment fields', function(done) {
var User = this.sequelize.define('UserWithAutoIncrementField', {
userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false }
})
it('sets updatedAt to the current timestamp', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
User.sync({ force: true }).success(function() {
User.create({}).on('success', function(user) {
expect(user.userid).to.equal(1)
this.User.bulkCreate(data).success(function() {
self.User.update({username: 'Bill'}, {secretValue: '42'}).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(3)
User.create({}).on('success', function(user) {
expect(user.userid).to.equal(2)
done()
})
})
})
})
expect(users[0].username).to.equal("Bill")
expect(users[1].username).to.equal("Bill")
expect(users[2].username).to.equal("Bob")
<<<<<<< HEAD
it('allows the usage of options as attribute', function(done) {
var User = this.sequelize.define('UserWithNameAndOptions', {
name: Sequelize.STRING,
......@@ -3264,33 +3107,19 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
describe('findAndCountAll', function() {
beforeEach(function(done) {
var self = this
this.User.bulkCreate([
{username: 'user', data: 'foobar'},
{username: 'user2', data: 'bar'},
{username: 'bobby', data: 'foo'}
]).success(function() {
self.User.all().success(function(users){
self.users = users
done()
})
})
})
describe('destroy', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.findAndCountAll().success(function(info1) {
User.findAndCountAll({ transaction: t }).success(function(info2) {
expect(info1.count).to.equal(0)
expect(info2.count).to.equal(1)
User.destroy({}, { transaction: t }).success(function() {
User.count().success(function(count1) {
User.count({ transaction: t }).success(function(count2) {
expect(count1).to.equal(1)
expect(count2).to.equal(0)
t.rollback().success(function(){ done() })
})
})
......@@ -3299,86 +3128,134 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
})
it('deletes values that match filter', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
this.User.bulkCreate(data).success(function() {
self.User.destroy({secretValue: '42'})
.success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].username).to.equal("Bob")
done()
})
})
})
})
it('sets deletedAt to the current timestamp if paranoid is true', function(done) {
var self = this
, ident = self.sequelize.queryInterface.QueryGenerator.quoteIdentifier
, escape = self.sequelize.queryInterface.QueryGenerator.quote
, ParanoidUser = self.sequelize.define('ParanoidUser', {
username: Sequelize.STRING,
secretValue: Sequelize.STRING,
data: Sequelize.STRING,
intVal: { type: Sequelize.INTEGER, defaultValue: 1}
}, {
paranoid: true
})
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
ParanoidUser.sync({ force: true }).success(function() {
ParanoidUser.bulkCreate(data).success(function() {
// since we save in UTC, let's format to UTC time
var date = moment().utc().format('YYYY-MM-DD h:mm')
ParanoidUser.destroy({secretValue: '42'}).success(function() {
ParanoidUser.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].username).to.equal("Bob")
self.sequelize.query('SELECT * FROM ' + ident('ParanoidUsers') + ' WHERE ' + ident('deletedAt') + ' IS NOT NULL ORDER BY ' + ident('id'), null, {raw: true}).success(function(users) {
expect(users[0].username).to.equal("Peter")
expect(users[1].username).to.equal("Paul")
it("handles where clause [only]", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
if (dialect === "sqlite") {
expect(moment(users[0].deletedAt).format('YYYY-MM-DD h:mm')).to.equal(date)
expect(moment(users[1].deletedAt).format('YYYY-MM-DD h:mm')).to.equal(date)
} else {
expect(moment(users[0].deletedAt).utc().format('YYYY-MM-DD h:mm')).to.equal(date)
expect(moment(users[1].deletedAt).utc().format('YYYY-MM-DD h:mm')).to.equal(date)
}
done()
})
})
it("handles where clause with ordering [only]", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id, order: 'id ASC'}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
done()
})
})
it("handles offset", function(done) {
this.User.findAndCountAll({offset: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
done()
})
})
it("handles limit", function(done) {
this.User.findAndCountAll({limit: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(1)
describe("can't find records marked as deleted with paranoid being true", function() {
it('with the DAOFactory', function(done) {
var User = this.sequelize.define('UserCol', {
username: Sequelize.STRING
}, { paranoid: true })
User.sync({ force: true }).success(function() {
User.bulkCreate([
{username: 'Toni'},
{username: 'Tobi'},
{username: 'Max'}
]).success(function() {
User.find(1).success(function(user) {
user.destroy().success(function() {
User.find(1).success(function(user) {
expect(user).to.be.null
User.count().success(function(cnt) {
expect(cnt).to.equal(2)
User.all().success(function(users) {
expect(users).to.have.length(2)
done()
})
})
it("handles offset and limit", function(done) {
this.User.findAndCountAll({offset: 1, limit: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(1)
done()
})
})
it("handles attributes", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id, attributes: ['data']}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
expect(info.rows[0].selectedValues).to.not.have.property('username')
expect(info.rows[1].selectedValues).to.not.have.property('username')
done()
})
})
})
describe('all', function() {
beforeEach(function(done) {
this.User.bulkCreate([
{username: 'user', data: 'foobar'},
{username: 'user2', data: 'bar'}
]).complete(function() {
done()
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
it('should delete a paranoid record if I set force to true', function(done) {
var self = this
var User = this.sequelize.define('paranoiduser', {
username: Sequelize.STRING
}, { paranoid: true })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.all().success(function(users1) {
User.all({ transaction: t }).success(function(users2) {
expect(users1.length).to.equal(0)
expect(users2.length).to.equal(1)
t.rollback().success(function(){ done() })
User.bulkCreate([
{username: 'Bob'},
{username: 'Tobi'},
{username: 'Max'},
{username: 'Tony'}
]).success(function() {
User.find({where: {username: 'Bob'}}).success(function(user) {
user.destroy({force: true}).success(function() {
User.find({where: {username: 'Bob'}}).success(function(user) {
expect(user).to.be.null
User.find({where: {username: 'Tobi'}}).success(function(tobi) {
tobi.destroy().success(function() {
self.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tobi\'', null, {raw: true, plain: true}).success(function(result) {
expect(result.username).to.equal('Tobi')
User.destroy({username: 'Tony'}).success(function() {
self.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tony\'', null, {raw: true, plain: true}).success(function(result) {
expect(result.username).to.equal('Tony')
User.destroy({username: ['Tony', 'Max']}, {force: true}).success(function() {
self.sequelize.query('SELECT * FROM paranoidusers', null, {raw: true}).success(function(users) {
expect(users).to.have.length(1)
expect(users[0].username).to.equal('Tobi')
done()
})
})
})
})
})
})
})
......@@ -3386,11 +3263,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
it("should return all users", function(done) {
this.User.all().on('success', function(users) {
expect(users.length).to.equal(2)
done()
})
})
})
......@@ -3661,290 +3533,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
describe('scopes', function() {
beforeEach(function(done) {
this.ScopeMe = this.sequelize.define('ScopeMe', {
username: Sequelize.STRING,
email: Sequelize.STRING,
access_level: Sequelize.INTEGER,
other_value: Sequelize.INTEGER
}, {
defaultScope: {
where: {
access_level: {
gte: 5
}
}
},
scopes: {
orderScope: {
order: 'access_level DESC'
},
limitScope: {
limit: 2
},
sequelizeTeam: {
where: ['email LIKE \'%@sequelizejs.com\'']
},
fakeEmail: {
where: ['email LIKE \'%@fakeemail.com\'']
},
highValue: {
where: {
other_value: {
gte: 10
}
}
},
isTony: {
where: {
username: 'tony'
}
},
canBeTony: {
where: {
username: ['tony']
}
},
canBeDan: {
where: {
username: {
in: 'dan'
}
}
},
actualValue: function(value) {
return {
where: {
other_value: value
}
}
},
complexFunction: function(email, accessLevel) {
return {
where: ['email like ? AND access_level >= ?', email + '%', accessLevel]
}
},
lowAccess: {
where: {
access_level: {
lte: 5
}
}
},
escape: {
where: {
username: "escape'd"
}
}
}
})
this.sequelize.sync({force: true}).success(function() {
var records = [
{username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10},
{username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11},
{username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7}
];
this.ScopeMe.bulkCreate(records).success(function() {
done()
})
}.bind(this))
})
it("should have no problems with escaping SQL", function(done) {
var self = this
this.ScopeMe.create({username: 'escape\'d', email: 'fake@fakemail.com'}).success(function(){
self.ScopeMe.scope('escape').all().success(function(users){
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('escape\'d');
done()
})
})
})
it("should be able to use a defaultScope if declared", function(done) {
this.ScopeMe.all().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(2)
expect([10,5].indexOf(users[0].access_level) !== -1).to.be.true
expect([10,5].indexOf(users[1].access_level) !== -1).to.be.true
expect(['dan', 'tobi'].indexOf(users[0].username) !== -1).to.be.true
expect(['dan', 'tobi'].indexOf(users[1].username) !== -1).to.be.true
done()
})
})
it("should be able to amend the default scope with a find object", function(done) {
this.ScopeMe.findAll({where: {username: 'dan'}}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('dan')
done()
})
})
it("should be able to override the default scope", function(done) {
this.ScopeMe.scope('fakeEmail').findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('tobi')
done()
})
})
it("should be able to combine two scopes", function(done) {
this.ScopeMe.scope(['sequelizeTeam', 'highValue']).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('dan')
done()
})
})
it("should be able to call a scope that's a function", function(done) {
this.ScopeMe.scope({method: ['actualValue', 11]}).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('tobi')
done()
})
})
it("should be able to handle multiple function scopes", function(done) {
this.ScopeMe.scope([{method: ['actualValue', 10]}, {method: ['complexFunction', 'dan', '5']}]).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('dan')
done()
})
})
it("should be able to stack the same field in the where clause", function(done) {
this.ScopeMe.scope(['canBeDan', 'canBeTony']).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(2)
expect(['dan', 'tony'].indexOf(users[0].username) !== -1).to.be.true
expect(['dan', 'tony'].indexOf(users[1].username) !== -1).to.be.true
done()
})
})
it("should be able to merge scopes", function(done) {
this.ScopeMe.scope(['highValue', 'isTony', {merge: true, method: ['actualValue', 7]}]).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('tony')
done()
})
})
it("should give us the correct order if we declare an order in our scope", function(done) {
this.ScopeMe.scope('sequelizeTeam', 'orderScope').findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(2)
expect(users[0].username).to.equal('dan')
expect(users[1].username).to.equal('tony')
done()
})
})
it("should give us the correct order as well as a limit if we declare such in our scope", function(done) {
this.ScopeMe.scope(['orderScope', 'limitScope']).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(2)
expect(users[0].username).to.equal('tobi')
expect(users[1].username).to.equal('dan')
done()
})
})
it("should have no problems combining scopes and traditional where object", function(done) {
this.ScopeMe.scope('sequelizeTeam').findAll({where: {other_value: 10}}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('dan')
expect(users[0].access_level).to.equal(5)
expect(users[0].other_value).to.equal(10)
done()
})
})
it("should be able to remove all scopes", function(done) {
this.ScopeMe.scope(null).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(3)
done()
})
})
it("should have no problem performing findOrCreate", function(done) {
this.ScopeMe.findOrCreate({username: 'fake'}).success(function(user) {
expect(user.username).to.equal('fake')
done()
})
})
it("should be able to hold multiple scope objects", function(done) {
var sequelizeTeam = this.ScopeMe.scope('sequelizeTeam', 'orderScope')
, tobi = this.ScopeMe.scope({method: ['actualValue', 11]})
sequelizeTeam.all().success(function(team) {
tobi.all().success(function(t) {
expect(team).to.be.an.instanceof(Array)
expect(team.length).to.equal(2)
expect(team[0].username).to.equal('dan')
expect(team[1].username).to.equal('tony')
expect(t).to.be.an.instanceof(Array)
expect(t.length).to.equal(1)
expect(t[0].username).to.equal('tobi')
done()
})
})
})
it("should gracefully omit any scopes that don't exist", function(done) {
this.ScopeMe.scope('sequelizeTeam', 'orderScope', 'doesntexist').all().success(function(team) {
expect(team).to.be.an.instanceof(Array)
expect(team.length).to.equal(2)
expect(team[0].username).to.equal('dan')
expect(team[1].username).to.equal('tony')
done()
})
})
it("should gracefully omit any scopes that don't exist through an array", function(done) {
this.ScopeMe.scope(['sequelizeTeam', 'orderScope', 'doesntexist']).all().success(function(team) {
expect(team).to.be.an.instanceof(Array)
expect(team.length).to.equal(2)
expect(team[0].username).to.equal('dan')
expect(team[1].username).to.equal('tony')
done()
})
})
it("should gracefully omit any scopes that don't exist through an object", function(done) {
this.ScopeMe.scope('sequelizeTeam', 'orderScope', {method: 'doesntexist'}).all().success(function(team) {
expect(team).to.be.an.instanceof(Array)
expect(team.length).to.equal(2)
expect(team[0].username).to.equal('dan')
expect(team[1].username).to.equal('tony')
done()
})
})
it("should emit an error for scopes that don't exist with silent: false", function(done) {
try {
this.ScopeMe.scope('doesntexist', {silent: false})
} catch (err) {
expect(err.message).to.equal('Invalid scope doesntexist called.')
done()
}
})
})
describe('schematic support', function() {
beforeEach(function(done){
var self = this;
......
/* jshint camelcase: false */
/* jshint expr: true */
var chai = require('chai')
, Sequelize = require('../../index')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/../config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, _ = require('lodash')
, moment = require('moment')
, async = require('async')
chai.use(datetime)
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("DAOFactory"), function () {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN
})
this.User.sync({ force: true }).success(function() {
done()
})
})
describe('findOrCreate', function () {
it("supports transactions", function(done) {
var self = this
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('user_with_transaction', { username: Sequelize.STRING, data: Sequelize.STRING })
User
.sync({ force: true })
.success(function() {
sequelize.transaction(function(t) {
User.findOrCreate({ username: 'Username' }, { data: 'some data' }, { transaction: t }).complete(function(err) {
expect(err).to.be.null
User.count().success(function(count) {
expect(count).to.equal(0)
t.commit().success(function() {
User.count().success(function(count) {
expect(count).to.equal(1)
done()
})
})
})
})
})
})
})
})
it("returns instance if already existent. Single find field.", function(done) {
var self = this,
data = {
username: 'Username'
};
this.User.create(data).success(function (user) {
self.User.findOrCreate({
username: user.username
}).success(function (_user, created) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(created).to.be.false
done()
})
})
})
it("Returns instance if already existent. Multiple find fields.", function(done) {
var self = this,
data = {
username: 'Username',
data: 'ThisIsData'
};
this.User.create(data).success(function (user) {
self.User.findOrCreate(data).done(function (err, _user, created) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(_user.data).to.equal('ThisIsData')
expect(created).to.be.false
done()
})
})
})
it("creates new instance with default value.", function(done) {
var data = {
username: 'Username'
},
default_values = {
data: 'ThisIsData'
};
this.User.findOrCreate(data, default_values).success(function(user, created) {
expect(user.username).to.equal('Username')
expect(user.data).to.equal('ThisIsData')
expect(created).to.be.true
done()
})
})
})
describe('create', function() {
it('supports transactions', function(done) {
var self = this
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('user_with_transaction', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'user' }, { transaction: t }).success(function() {
User.count().success(function(count) {
expect(count).to.equal(0)
t.commit().success(function() {
User.count().success(function(count) {
expect(count).to.equal(1)
done()
})
})
})
})
})
})
})
})
it('is possible to use casting when creating an instance', function (done) {
var self = this
, type = Support.dialectIsMySQL() ? 'signed' : 'integer'
, _done = _.after(2, function() {
done()
})
this.User.create({
intVal: this.sequelize.cast('1', type)
}).on('sql', function (sql) {
expect(sql).to.match(new RegExp('CAST\\(1 AS ' + type.toUpperCase() + '\\)'))
_done()
})
.success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(1)
_done()
})
})
})
it('is possible to use casting multiple times mixed in with other utilities', function (done) {
var self = this
, type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer')
, _done = _.after(2, function() {
done()
})
if (Support.dialectIsMySQL()) {
type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed')
}
this.User.create({
intVal: type
}).on('sql', function (sql) {
if (Support.dialectIsMySQL()) {
expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)')
} else {
expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)')
}
_done()
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
_done()
})
})
})
it('is possible to just use .literal() to bypass escaping', function (done) {
var self = this
this.User.create({
intVal: this.sequelize.literal('CAST(1-2 AS ' + (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER') + ')')
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
done()
})
})
})
it('is possible for .literal() to contain other utility functions', function (done) {
var self = this
this.User.create({
intVal: this.sequelize.literal(this.sequelize.cast('1-2', (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER')))
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
done()
})
})
})
it('is possible to use funtions when creating an instance', function (done) {
var self = this
this.User.create({
secretValue: this.sequelize.fn('upper', 'sequelize')
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.secretValue).to.equal('SEQUELIZE')
done()
})
})
})
it('is possible to use functions as default values', function (done) {
var self = this
, userWithDefaults
if (dialect.indexOf('postgres') === 0) {
this.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"').success(function() {
userWithDefaults = self.sequelize.define('userWithDefaults', {
uuid: {
type: 'UUID',
defaultValue: self.sequelize.fn('uuid_generate_v4')
}
})
userWithDefaults.sync({force: true}).success(function () {
userWithDefaults.create({}).success(function (user) {
// uuid validation regex taken from http://stackoverflow.com/a/13653180/800016
expect(user.uuid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
done()
})
})
})
} else if (dialect === 'sqlite') {
// The definition here is a bit hacky. sqlite expects () around the expression for default values, so we call a function without a name
// to enclose the date function in (). http://www.sqlite.org/syntaxdiagrams.html#column-constraint
userWithDefaults = self.sequelize.define('userWithDefaults', {
year: {
type: Sequelize.STRING,
defaultValue: self.sequelize.fn('', self.sequelize.fn('date', 'now'))
}
})
userWithDefaults.sync({force: true}).success(function () {
userWithDefaults.create({}).success(function (user) {
userWithDefaults.find(user.id).success(function (user) {
var now = new Date()
, pad = function (number) {
if (number > 9) {
return number
}
return '0' + number
}
expect(user.year).to.equal(now.getUTCFullYear() + '-' + pad(now.getUTCMonth() + 1) + '-' + pad(now.getUTCDate()))
done()
})
})
})
} else {
// functions as default values are not supported in mysql, see http://stackoverflow.com/a/270338/800016
done()
}
})
it("casts empty arrays correctly for postgresql insert", function(done) {
if (dialect !== "postgres") {
expect('').to.equal('')
return done()
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [], mystr: []}).on('sql', function(sql){
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
done()
})
})
})
it("casts empty array correct for postgres update", function(done) {
if (dialect !== "postgres") {
expect('').to.equal('')
return done()
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [1,2,3,4], mystr: ["One", "Two", "Three", "Four"]}).on('success', function(user){
user.myvals = []
user.mystr = []
user.save().on('sql', function(sql) {
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
done()
})
})
})
})
it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: true }
})
User.sync({ force: true }).success(function() {
User.create({ username:'foo' }).success(function() {
User.create({ username: 'foo' }).error(function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/.*Duplicate\ entry.*/)
} else {
expect(err.message).to.match(/.*duplicate\ key\ value.*/)
}
done()
})
})
})
})
it("raises an error if created object breaks definition contraints", function(done) {
var UserNull = this.sequelize.define('UserWithNonNullSmth', {
username: { type: Sequelize.STRING, unique: true },
smth: { type: Sequelize.STRING, allowNull: false }
})
this.sequelize.options.omitNull = false
UserNull.sync({ force: true }).success(function() {
UserNull.create({ username: 'foo2', smth: null }).error(function(err) {
expect(err).to.exist
if (Support.dialectIsMySQL()) {
// We need to allow two different errors for MySQL, see:
// http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_trans_tables
expect(err.message).to.match(/(Column 'smth' cannot be null|Field 'smth' doesn't have a default value)/)
}
else if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
} else {
expect(err.message).to.match(/.*column "smth" violates not-null.*/)
}
UserNull.create({ username: 'foo', smth: 'foo' }).success(function() {
UserNull.create({ username: 'foo', smth: 'bar' }).error(function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/Duplicate entry 'foo' for key 'username'/)
} else {
expect(err.message).to.match(/.*duplicate key value violates unique constraint.*/)
}
done()
})
})
})
})
})
it('raises an error if you mess up the datatype', function(done) {
var self = this
expect(function() {
self.sequelize.define('UserBadDataType', {
activity_date: Sequelize.DATe
})
}).to.throw(Error, 'Unrecognized data type for field activity_date')
expect(function() {
self.sequelize.define('UserBadDataType', {
activity_date: {type: Sequelize.DATe}
})
}).to.throw(Error, 'Unrecognized data type for field activity_date')
done()
})
it('sets a 64 bit int in bigint', function(done) {
var User = this.sequelize.define('UserWithBigIntFields', {
big: Sequelize.BIGINT
})
User.sync({ force: true }).success(function() {
User.create({ big: '9223372036854775807' }).on('success', function(user) {
expect(user.big).to.be.equal( '9223372036854775807' )
done()
})
})
})
it('sets auto increment fields', function(done) {
var User = this.sequelize.define('UserWithAutoIncrementField', {
userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false }
})
User.sync({ force: true }).success(function() {
User.create({}).on('success', function(user) {
expect(user.userid).to.equal(1)
User.create({}).on('success', function(user) {
expect(user.userid).to.equal(2)
done()
})
})
})
})
it('allows the usage of options as attribute', function(done) {
var User = this.sequelize.define('UserWithNameAndOptions', {
name: Sequelize.STRING,
options: Sequelize.TEXT
})
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
User.sync({ force: true }).success(function() {
User
.create({ name: 'John Doe', options: options })
.success(function(user) {
expect(user.options).to.equal(options)
done()
})
})
})
it('allows sql logging', function(done) {
var User = this.sequelize.define('UserWithUniqueNameAndNonNullSmth', {
name: {type: Sequelize.STRING, unique: true},
smth: {type: Sequelize.STRING, allowNull: false}
})
User.sync({ force: true }).success(function() {
User
.create({ name: 'Fluffy Bunny', smth: 'else' })
.on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("INSERT")).to.be.above(-1)
done()
})
})
})
it('should only store the values passed in the whitelist', function(done) {
var self = this
, data = { username: 'Peter', secretValue: '42' }
this.User.create(data, { fields: ['username'] }).success(function(user) {
self.User.find(user.id).success(function(_user) {
expect(_user.username).to.equal(data.username)
expect(_user.secretValue).not.to.equal(data.secretValue)
expect(_user.secretValue).to.equal(null)
done()
})
})
})
it('should store all values if no whitelist is specified', function(done) {
var self = this
, data = { username: 'Peter', secretValue: '42' }
this.User.create(data).success(function(user) {
self.User.find(user.id).success(function(_user) {
expect(_user.username).to.equal(data.username)
expect(_user.secretValue).to.equal(data.secretValue)
done()
})
})
})
it('can omit autoincremental columns', function(done) {
var self = this
, data = { title: 'Iliad' }
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT]
, chain = new Sequelize.Utils.QueryChainer()
, chain2 = new Sequelize.Utils.QueryChainer()
, books = []
dataTypes.forEach(function(dataType, index) {
books[index] = self.sequelize.define('Book'+index, {
id: { type: dataType, primaryKey: true, autoIncrement: true },
title: Sequelize.TEXT
})
})
books.forEach(function(b) {
chain.add(b.sync({ force: true }))
})
chain.run().success(function() {
books.forEach(function(b) {
chain2.add(b.create(data))
})
chain2.run().success(function(results) {
results.forEach(function(book, index) {
expect(book.title).to.equal(data.title)
expect(book.author).to.equal(data.author)
expect(books[index].rawAttributes.id.type.toString())
.to.equal(dataTypes[index].toString())
})
done()
})
})
})
it('saves data with single quote', function(done) {
var quote = "single'quote"
, self = this
this.User.create({ data: quote }).success(function(user) {
expect(user.data).to.equal(quote)
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).to.equal(quote)
done()
})
})
})
it('saves data with double quote', function(done) {
var quote = 'double"quote'
, self = this
this.User.create({ data: quote }).success(function(user) {
expect(user.data).to.equal(quote)
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).to.equal(quote)
done()
})
})
})
it('saves stringified JSON data', function(done) {
var json = JSON.stringify({ key: 'value' })
, self = this
this.User.create({ data: json }).success(function(user) {
expect(user.data).to.equal(json)
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).to.equal(json)
done()
})
})
})
it('stores the current date in createdAt', function(done) {
this.User.create({ username: 'foo' }).success(function(user) {
expect(parseInt(+user.createdAt/5000, 10)).to.be.closeTo(parseInt(+new Date()/5000, 10), 1.5)
done()
})
})
it('allows setting custom IDs', function(done) {
var self = this
this.User.create({ id: 42 }).success(function (user) {
expect(user.id).to.equal(42)
self.User.find(42).success(function (user) {
expect(user).to.exist
done()
})
})
})
it('should allow blank creates (with timestamps: false)', function (done) {
var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
Worker.sync().done(function(err) {
Worker.create().done(function (err, worker) {
expect(err).not.to.be.ok
expect(worker).to.be.ok
done()
})
})
})
describe('enums', function() {
it('correctly restores enum values', function(done) {
var self = this
, Item = self.sequelize.define('Item', {
state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] }
})
Item.sync({ force: true }).success(function() {
Item.create({ state: 'available' }).success(function(_item) {
Item.find({ where: { state: 'available' }}).success(function(item) {
expect(item.id).to.equal(_item.id)
done()
})
})
})
})
it('allows null values', function(done) {
var Enum = this.sequelize.define('Enum', {
state: {
type: Sequelize.ENUM,
values: ['happy', 'sad'],
allowNull: true
}
})
Enum.sync({ force: true }).success(function() {
Enum.create({state: null}).success(function(_enum) {
expect(_enum.state).to.be.null
done()
})
})
})
describe('can safely sync multiple times', function(done) {
it('through the factory', function(done) {
var Enum = this.sequelize.define('Enum', {
state: {
type: Sequelize.ENUM,
values: ['happy', 'sad'],
allowNull: true
}
})
Enum.sync({ force: true }).success(function() {
Enum.sync().success(function() {
Enum.sync({ force: true }).complete(done)
})
})
})
it('through sequelize', function(done) {
var self = this
, Enum = this.sequelize.define('Enum', {
state: {
type: Sequelize.ENUM,
values: ['happy', 'sad'],
allowNull: true
}
})
this.sequelize.sync({ force: true }).success(function() {
self.sequelize.sync().success(function() {
self.sequelize.sync({ force: true }).complete(done)
})
})
})
})
})
})
describe('bulkCreate', function() {
it("supports transactions", function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User
.bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction: t })
.success(function() {
User.count().success(function(count1) {
User.count({ transaction: t }).success(function(count2) {
expect(count1).to.equal(0)
expect(count2).to.equal(2)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it('properly handles disparate field lists', function(done) {
var self = this
, data = [{username: 'Peter', secretValue: '42' },
{username: 'Paul'},
{username: 'Steve'}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({where: {username: 'Paul'}}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].username).to.equal("Paul")
expect(users[0].secretValue).to.be.null
done()
})
})
})
it('inserts multiple values respecting the white list', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '23'}]
this.User.bulkCreate(data, { fields: ['username'] }).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].secretValue).to.be.null;
expect(users[1].username).to.equal("Paul")
expect(users[1].secretValue).to.be.null;
done()
})
})
})
it('should store all values if no whitelist is specified', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '23'}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].secretValue).to.equal('42')
expect(users[1].username).to.equal("Paul")
expect(users[1].secretValue).to.equal('23')
done()
})
})
})
it('saves data with single quote', function(done) {
var self = this
, quote = "Single'Quote"
, data = [{ username: 'Peter', data: quote},
{ username: 'Paul', data: quote}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].data).to.equal(quote)
expect(users[1].username).to.equal("Paul")
expect(users[1].data).to.equal(quote)
done()
})
})
})
it('saves data with double quote', function(done) {
var self = this
, quote = 'Double"Quote'
, data = [{ username: 'Peter', data: quote},
{ username: 'Paul', data: quote}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].data).to.equal(quote)
expect(users[1].username).to.equal("Paul")
expect(users[1].data).to.equal(quote)
done()
})
})
})
it('saves stringified JSON data', function(done) {
var self = this
, json = JSON.stringify({ key: 'value' })
, data = [{ username: 'Peter', data: json},
{ username: 'Paul', data: json}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].data).to.equal(json)
expect(users[1].username).to.equal("Paul")
expect(users[1].data).to.equal(json)
done()
})
})
})
it('stores the current date in createdAt', function(done) {
var self = this
, data = [{ username: 'Peter'},
{ username: 'Paul'}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal('Peter')
expect(parseInt(+users[0].createdAt/5000, 10)).to.be.closeTo(parseInt(+new Date()/5000, 10), 1.5)
expect(users[1].username).to.equal('Paul')
expect(parseInt(+users[1].createdAt/5000, 10)).to.be.closeTo(parseInt(+new Date()/5000, 10), 1.5)
done()
})
})
})
it('emits an error when validate is set to true', function(done) {
var Tasks = this.sequelize.define('Task', {
name: {
type: Sequelize.STRING,
validate: {
notNull: { args: true, msg: 'name cannot be null' }
}
},
code: {
type: Sequelize.STRING,
validate: {
len: [3, 10]
}
}
})
Tasks.sync({ force: true }).success(function() {
Tasks.bulkCreate([
{name: 'foo', code: '123'},
{code: '1234'},
{name: 'bar', code: '1'}
], { validate: true }).error(function(errors) {
expect(errors).to.not.be.null
expect(errors).to.be.instanceof(Array)
expect(errors).to.have.length(2)
expect(errors[0].record.code).to.equal('1234')
expect(errors[0].errors.name[0]).to.equal('name cannot be null')
expect(errors[1].record.name).to.equal('bar')
expect(errors[1].record.code).to.equal('1')
expect(errors[1].errors.code[0]).to.equal('String is not in range: code')
done()
})
})
})
it("doesn't emit an error when validate is set to true but our selectedValues are fine", function(done) {
var Tasks = this.sequelize.define('Task', {
name: {
type: Sequelize.STRING,
validate: {
notNull: { args: true, msg: 'name cannot be null' }
}
},
code: {
type: Sequelize.STRING,
validate: {
len: [3, 10]
}
}
})
Tasks.sync({ force: true }).success(function() {
Tasks.bulkCreate([
{name: 'foo', code: '123'},
{code: '1234'}
], { fields: ['code'], validate: true }).success(function() {
// we passed!
done()
})
})
})
it('should allow blank creates (with timestamps: false)', function (done) {
var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
Worker.sync().done(function(err) {
Worker.bulkCreate([{}, {}]).done(function (err, workers) {
expect(err).not.to.be.ok
expect(workers).to.be.ok
done()
})
})
})
describe('enums', function() {
it('correctly restores enum values', function(done) {
var self = this
, Item = self.sequelize.define('Item', {
state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] },
name: Sequelize.STRING
})
Item.sync({ force: true }).success(function() {
Item.bulkCreate([{state: 'in_cart', name: 'A'}, { state: 'available', name: 'B'}]).success(function() {
Item.find({ where: { state: 'available' }}).success(function(item) {
expect(item.name).to.equal('B')
done()
})
})
})
})
})
})
})
\ No newline at end of file
/* jshint camelcase: false */
/* jshint expr: true */
var chai = require('chai')
, Sequelize = require('../../index')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/../config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, _ = require('lodash')
, moment = require('moment')
, async = require('async')
chai.use(datetime)
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("DAOFactory"), function () {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN
})
this.User.sync({ force: true }).success(function() {
done()
})
})
describe('find', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.find({ username: 'foo' }).success(function(user1) {
User.find({ username: 'foo' }, { transaction: t }).success(function(user2) {
expect(user1).to.be.null
expect(user2).to.not.be.null
t.rollback().success(function() {
done()
})
})
})
})
})
})
})
})
describe('general / basic function', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.UserPrimary = self.sequelize.define('UserPrimary', {
specialkey: {
type: DataTypes.STRING,
primaryKey: true
}
})
self.UserPrimary.sync({force: true}).success(function() {
self.UserPrimary.create({specialkey: 'a string'}).success(function() {
self.user = user
done()
})
})
})
})
it('does not modify the passed arguments', function (done) {
var options = { where: ['specialkey = ?', 'awesome']}
this.UserPrimary.find(options).success(function(user) {
expect(options).to.deep.equal({ where: ['specialkey = ?', 'awesome']})
done()
})
})
it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function(done) {
this.UserPrimary.find('a string').success(function(user) {
expect(user.specialkey).to.equal('a string')
done()
})
})
it('doesn\'t throw an error when entering in a non integer value', function(done) {
this.User.find('a string value').success(function(user) {
expect(user).to.be.null
done()
})
})
it('returns a single dao', function(done) {
var self = this
this.User.find(this.user.id).success(function(user) {
expect(Array.isArray(user)).to.not.be.ok
expect(user.id).to.equal(self.user.id)
expect(user.id).to.equal(1)
done()
})
})
it('returns a single dao given a string id', function(done) {
var self = this
this.User.find(this.user.id + '').success(function(user) {
expect(Array.isArray(user)).to.not.be.ok
expect(user.id).to.equal(self.user.id)
expect(user.id).to.equal(1)
done()
})
})
it("should make aliased attributes available", function(done) {
this.User.find({
where: { id: 1 },
attributes: ['id', ['username', 'name']]
}).success(function(user) {
expect(user.name).to.equal('barfooz')
done()
})
})
it("should not try to convert boolean values if they are not selected", function(done) {
var UserWithBoolean = this.sequelize.define('UserBoolean', {
active: Sequelize.BOOLEAN
})
UserWithBoolean.sync({force: true}).success(function () {
UserWithBoolean.create({ active: true }).success(function(user) {
UserWithBoolean.find({ where: { id: user.id }, attributes: [ 'id' ] }).success(function(user) {
expect(user.active).not.to.exist
done()
})
})
})
})
it('finds a specific user via where option', function(done) {
this.User.find({ where: { username: 'barfooz' } }).success(function(user) {
expect(user.username).to.equal('barfooz')
done()
})
})
it("doesn't find a user if conditions are not matching", function(done) {
this.User.find({ where: { username: 'foo' } }).success(function(user) {
expect(user).to.be.null
done()
})
})
it('allows sql logging', function(done) {
this.User.find({ where: { username: 'foo' } }).on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("SELECT")).to.be.above(-1)
done()
})
})
it('ignores passed limit option', function(done) {
this.User.find({ limit: 10 }).success(function(user) {
// it returns an object instead of an array
expect(Array.isArray(user)).to.not.be.ok
expect(user.dataValues.hasOwnProperty('username')).to.be.ok
done()
})
})
it('finds entries via primary keys', function(done) {
var self = this
, UserPrimary = self.sequelize.define('UserWithPrimaryKey', {
identifier: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING
})
UserPrimary.sync({ force: true }).success(function() {
UserPrimary.create({
identifier: 'an identifier',
name: 'John'
}).success(function(u) {
expect(u.id).not.to.exist
UserPrimary.find('an identifier').success(function(u2) {
expect(u2.identifier).to.equal('an identifier')
expect(u2.name).to.equal('John')
done()
})
})
})
})
it('finds entries via a string primary key called id', function(done) {
var self = this
, UserPrimary = self.sequelize.define('UserWithPrimaryKey', {
id: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING
})
UserPrimary.sync({ force: true }).success(function() {
UserPrimary.create({
id: 'a string based id',
name: 'Johnno'
}).success(function(u) {
UserPrimary.find('a string based id').success(function(u2) {
expect(u2.id).to.equal('a string based id')
expect(u2.name).to.equal('Johnno')
done()
})
})
})
})
it('returns the selected fields as instance.selectedValues', function(done) {
var self = this
this.User.create({
username: 'JohnXOXOXO'
}).success(function() {
self.User.find({
where: { username: 'JohnXOXOXO' },
attributes: ['username']
}).success(function(user) {
expect(user.selectedValues).to.have.property('username', 'JohnXOXOXO')
done()
})
})
})
it('returns the selected fields and all fields of the included table as instance.selectedValues', function(done) {
var self = this
self.Mission = self.sequelize.define('Mission', {
title: {type: Sequelize.STRING, defaultValue: 'a mission!!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
})
self.Mission.belongsTo(self.User)
self.User.hasMany(self.Mission)
self.Mission.sync({ force: true }).success(function() {
self.Mission.create().success(function(mission) {
self.User.create({username: 'John DOE'}).success(function(user) {
mission.setUser(user).success(function() {
self.User.find({
where: { username: 'John DOE' },
attributes: ['username'],
include: [self.Mission]
}).success(function(user) {
expect(user.selectedValues).to.deep.equal({ username: 'John DOE' })
done()
})
})
})
})
})
})
it('returns the selected fields for both the base table and the included table as instance.selectedValues', function(done) {
var self = this
self.Mission = self.sequelize.define('Mission', {
title: {type: Sequelize.STRING, defaultValue: 'another mission!!'},
foo: {type: Sequelize.INTEGER, defaultValue: 4},
})
self.Mission.belongsTo(self.User)
self.User.hasMany(self.Mission)
self.Mission.sync({ force: true }).success(function() {
self.Mission.create().success(function(mission) {
self.User.create({username: 'Brain Picker'}).success(function(user) {
mission.setUser(user).success(function() {
self.User.find({
where: { username: 'Brain Picker' },
attributes: ['username'],
include: [{model: self.Mission, as: self.Mission.tableName, attributes: ['title']}]
}).success(function(user) {
expect(user.selectedValues).to.deep.equal({ username: 'Brain Picker' })
expect(user.missions[0].selectedValues).to.deep.equal({ id: 1, title: 'another mission!!'})
expect(user.missions[0].foo).not.to.exist
done()
})
})
})
})
})
})
it('always honors ZERO as primary key', function(_done) {
var self = this
, permutations = [
0,
'0',
{where: {id: 0}},
{where: {id: '0'}}
]
, done = _.after(2 * permutations.length, _done)
this.User.bulkCreate([{username: 'jack'}, {username: 'jack'}]).success(function() {
permutations.forEach(function(perm) {
self.User.find(perm).done(function(err, user) {
expect(err).to.be.null
expect(user).to.be.null
done()
}).on('sql', function(s) {
expect(s.indexOf(0)).not.to.equal(-1)
done()
})
})
})
})
it('should allow us to find IDs using capital letters', function(done) {
var User = this.sequelize.define('User' + config.rand(), {
ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
Login: { type: Sequelize.STRING }
})
User.sync({ force: true }).success(function() {
User.create({Login: 'foo'}).success(function() {
User.find(1).success(function(user) {
expect(user).to.exist
expect(user.ID).to.equal(1)
done()
})
})
})
})
})
describe('eager loading', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('Task', { title: Sequelize.STRING })
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.Worker.create({ name: 'worker' }).success(function(worker) {
self.Task.create({ title: 'homework' }).success(function(task) {
self.worker = worker
self.task = task
callback()
})
})
})
})
}
done()
})
describe('belongsTo', function() {
describe('generic', function() {
it('throws an error about unexpected input if include contains a non-object', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ 1 ] })
}).to.throw(Error, 'Include unexpected. Element has to be either an instance of DAOFactory or an object.')
done()
})
it('throws an error about missing attributes if include contains an object with daoFactory', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Worker } ] })
}).to.throw(Error, 'Include malformed. Expected attributes: daoFactory, as!')
done()
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
it('returns the associated worker via task.worker', function(done) {
var self = this
this.Task.belongsTo(this.Worker)
this.init(function() {
self.task.setWorker(self.worker).success(function() {
self.Task.find({
where: { title: 'homework' },
include: [ self.Worker ]
}).complete(function(err, task) {
expect(err).to.be.null
expect(task).to.exist
expect(task.worker).to.exist
expect(task.worker.name).to.equal('worker')
done()
})
})
})
})
})
it('returns the private and public ip', function(done) {
var self = Object.create(this)
self.Domain = self.sequelize.define('Domain', { ip: Sequelize.STRING })
self.Environment = self.sequelize.define('Environment', { name: Sequelize.STRING })
self.Environment
.belongsTo(self.Domain, { as: 'PrivateDomain', foreignKey: 'privateDomainId' })
.belongsTo(self.Domain, { as: 'PublicDomain', foreignKey: 'publicDomainId' })
self.Domain.sync({ force: true }).success(function() {
self.Environment.sync({ force: true }).success(function() {
self.Domain.create({ ip: '192.168.0.1' }).success(function(privateIp) {
self.Domain.create({ ip: '91.65.189.19' }).success(function(publicIp) {
self.Environment.create({ name: 'environment' }).success(function(env) {
env.setPrivateDomain(privateIp).success(function() {
env.setPublicDomain(publicIp).success(function() {
self.Environment.find({
where: { name: 'environment' },
include: [
{ daoFactory: self.Domain, as: 'PrivateDomain' },
{ daoFactory: self.Domain, as: 'PublicDomain' }
]
}).complete(function(err, environment) {
expect(err).to.be.null
expect(environment).to.exist
expect(environment.privateDomain).to.exist
expect(environment.privateDomain.ip).to.equal('192.168.0.1')
expect(environment.publicDomain).to.exist
expect(environment.publicDomain.ip).to.equal('91.65.189.19')
done()
})
})
})
})
})
})
})
})
})
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerbelong', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group = self.sequelize.define('GroupPKeagerbelong', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.User.belongsTo(self.Group)
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone', GroupPKeagerbelongId: 'people' }).success(function() {
self.Group.create({ name: 'people' }).success(function() {
self.User.find({
where: {
username: 'someone'
},
include: [self.Group]
}).complete(function (err, someUser) {
expect(err).to.be.null
expect(someUser).to.exist
expect(someUser.username).to.equal('someone')
expect(someUser.groupPKeagerbelong.name).to.equal('people')
done()
})
})
})
})
})
it('getting parent data in many to one relationship', function(done) {
var self = this;
var User = self.sequelize.define('User', {
id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
username: {type: Sequelize.STRING}
})
var Message = self.sequelize.define('Message', {
id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
user_id: {type: Sequelize.INTEGER},
message: {type: Sequelize.STRING}
})
User.hasMany(Message)
Message.belongsTo(User, { foreignKey: 'user_id' })
Message.sync({ force: true }).success(function() {
User.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) {
Message.findAll({
where: {user_id: user.id},
attributes: [
'user_id',
'message'
],
include: [{ model: User, as: User.tableName, attributes: ['username'] }]
}).success(function(messages) {
expect(messages.length).to.equal(2);
expect(messages[0].message).to.equal('hi there!');
expect(messages[0].user.username).to.equal('test_testerson');
expect(messages[1].message).to.equal('a second message');
expect(messages[1].user.username).to.equal('test_testerson');
done()
})
})
})
})
})
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.belongsTo(this.Task, { as: 'ToDo' })
this.Worker.belongsTo(this.Task, { as: 'DoTo' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDo' },
{ model: self.Task, as: 'DoTo' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
})
describe('hasOne', function() {
beforeEach(function(done) {
var self = this
this.Worker.hasOne(this.Task)
this.init(function() {
self.worker.setTask(self.task).success(function() {
done()
})
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.find({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to Task!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.task).to.exist
expect(worker.task.title).to.equal('homework')
done()
})
})
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerone', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group = self.sequelize.define('GroupPKeagerone', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group.hasOne(self.User)
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone', GroupPKeageroneId: 'people' }).success(function() {
self.Group.create({ name: 'people' }).success(function() {
self.Group.find({
where: {
name: 'people'
},
include: [self.User]
}).complete(function (err, someGroup) {
expect(err).to.be.null
expect(someGroup).to.exist
expect(someGroup.name).to.equal('people')
expect(someGroup.userPKeagerone.username).to.equal('someone')
done()
})
})
})
})
})
})
describe('hasOne with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
describe('alias', function(done) {
beforeEach(function(done) {
var self = this
this.Worker.hasOne(this.Task, { as: 'ToDo' })
this.init(function() {
self.worker.setToDo(self.task).success(function() {
done()
})
})
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDo' } ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.toDo).to.exist
expect(worker.toDo.title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDo' } ]
}).complete(function(err, worker) {
expect(worker.toDo.title).to.equal('homework')
done()
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.hasOne(this.Task, { as: 'DoTo' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDo' },
{ model: self.Task, as: 'DoTo' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
})
})
describe('hasMany', function() {
beforeEach(function(done) {
var self = this
this.Worker.hasMany(this.Task)
this.init(function() {
self.worker.setTasks([ self.task ]).success(function() {
done()
})
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.find({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to Task!')
done()
})
it('returns the associated tasks via worker.tasks', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.tasks).to.exist
expect(worker.tasks[0].title).to.equal('homework')
done()
})
})
it('including two has many relations should not result in duplicate values', function(done) {
var self = this
self.Contact = self.sequelize.define('Contact', { name: DataTypes.TEXT })
self.Photo = self.sequelize.define('Photo', { img: DataTypes.TEXT })
self.PhoneNumber = self.sequelize.define('PhoneNumber', { phone: DataTypes.TEXT })
self.Contact.hasMany(self.Photo, { as: 'Photos' })
self.Contact.hasMany(self.PhoneNumber)
self.sequelize.sync({ force: true }).success(function() {
self.Contact.create({ name: 'Boris' }).success(function(someContact) {
self.Photo.create({ img: 'img.jpg' }).success(function(somePhoto) {
self.PhoneNumber.create({ phone: '000000' }).success(function(somePhone1) {
self.PhoneNumber.create({ phone: '111111' }).success(function(somePhone2) {
someContact.setPhotos([somePhoto]).complete(function (err, data) {
expect(err).to.be.null
someContact.setPhoneNumbers([somePhone1, somePhone2]).complete(function (err, data) {
self.Contact.find({
where: {
name: 'Boris'
},
include: [self.PhoneNumber, { daoFactory: self.Photo, as: 'Photos' }]
}).complete(function (err, fetchedContact) {
expect(err).to.be.null
expect(fetchedContact).to.exist
expect(fetchedContact.photos.length).to.equal(1)
expect(fetchedContact.phoneNumbers.length).to.equal(2)
done()
})
})
})
})
})
})
})
})
})
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerone', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group = self.sequelize.define('GroupPKeagerone', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group.hasMany(self.User)
self.User.hasMany(self.Group)
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone' }).success(function(someUser) {
self.Group.create({ name: 'people' }).success(function(someGroup) {
someUser.setGroupPKeagerones([someGroup]).complete(function (err, data) {
expect(err).to.be.null
self.User.find({
where: {
username: 'someone'
},
include: [self.Group]
}).complete(function (err, someUser) {
expect(err).to.be.null
expect(someUser).to.exist
expect(someUser.username).to.equal('someone')
expect(someUser.groupPKeagerones[0].name).to.equal('people')
done()
})
})
})
})
})
})
})
describe('hasMany with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
describe('alias', function() {
beforeEach(function(done) {
var self = this
this.Worker.hasMany(this.Task, { as: 'ToDos' })
this.init(function() {
self.worker.setToDos([ self.task ]).success(function() {
done()
})
})
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.toDos).to.exist
expect(worker.toDos[0].title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(worker.toDos[0].title).to.equal('homework')
done()
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.hasMany(this.Task, { as: 'DoTos' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDos' },
{ model: self.Task, as: 'DoTos' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
})
})
describe('hasMany (N:M) with alias', function () {
beforeEach(function (done) {
this.Product = this.sequelize.define('Product', { title: Sequelize.STRING })
this.Tag = this.sequelize.define('Tag', { name: Sequelize.STRING })
done();
})
it('returns the associated models when using through as string and alias', function (done) {
var self = this
this.Product.hasMany(this.Tag, {as: 'Tags', through: 'product_tag'})
this.Tag.hasMany(this.Product, {as: 'Products', through: 'product_tag'})
this.sequelize.sync().done(function (err) {
async.auto({
createProducts: function (callback) {
self.Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Handbag'},
{title: 'Dress'},
{title: 'Jan'}
]).done(callback)
},
// bulkCreate doesn't include id for some reason, not going to fix tis now
products: ['createProducts', function (callback) {
self.Product.findAll().done(callback)
}],
createTags: function (callback) {
self.Tag.bulkCreate([
{name: 'Furniture'},
{name: 'Clothing'},
{name: 'People'}
]).done(callback)
},
tags: ['createTags', function (callback) {
self.Tag.findAll().done(callback)
}],
}, function (err, results) {
expect(err).not.to.exist
var products = results.products
, tags = results.tags
async.parallel([
function (callback) {
products[0].setTags([tags[0], tags[1]]).done(callback)
},
function (callback) {
products[1].addTag(tags[0]).done(callback)
},
function (callback) {
products[2].addTag(tags[1]).done(callback)
},
function (callback) {
products[3].setTags([tags[1]]).done(callback)
},
function (callback) {
products[4].setTags([tags[2]]).done(callback)
}
], function (err) {
expect(err).not.to.exist
async.parallel([
function (callback) {
self.Tag.find({
where: {
id: tags[0].id
},
include: [
{model: self.Product, as: 'Products'}
]
}).done(function (err, tag) {
expect(tag).to.exist
expect(tag.products.length).to.equal(2)
callback()
})
},
function (callback) {
tags[1].getProducts().done(function (err, products) {
expect(products.length).to.equal(3)
callback()
})
},
function (callback) {
self.Product.find({
where: {
id: products[0].id
},
include: [
{model: self.Tag, as: 'Tags'}
]
}).done(function (err, product) {
expect(product).to.exist
expect(product.tags.length).to.equal(2)
callback()
})
},
function (callback) {
products[1].getTags().done(function (err, tags) {
expect(tags.length).to.equal(1)
callback()
})
},
], done)
})
})
})
})
it('returns the associated models when using through as model and alias')
})
})
describe('queryOptions', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.user = user
done()
})
})
it("should return a DAO when queryOptions are not set", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}).done(function(err, user) {
expect(user).to.be.instanceOf(self.User.DAO)
done()
})
})
it("should return a DAO when raw is false", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}, { raw: false }).done(function(err, user) {
expect(user).to.be.instanceOf(self.User.DAO)
done()
})
})
it("should return raw data when raw is true", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function(err, user) {
expect(user).to.not.be.instanceOf(self.User.DAO)
expect(user).to.be.instanceOf(Object)
done()
})
})
})
})
})
\ No newline at end of file
/* jshint camelcase: false */
/* jshint expr: true */
var chai = require('chai')
, Sequelize = require('../../index')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/../config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, _ = require('lodash')
, moment = require('moment')
, async = require('async')
chai.use(datetime)
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("DAOFactory"), function () {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN
})
this.User.sync({ force: true }).success(function() {
done()
})
})
describe('findAll', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.findAll({ username: 'foo' }).success(function(users1) {
User.findAll({ transaction: t }).success(function(users2) {
User.findAll({ username: 'foo' }, { transaction: t }).success(function(users3) {
expect(users1.length).to.equal(0)
expect(users2.length).to.equal(1)
expect(users3.length).to.equal(1)
t.rollback().success(function() {
done()
})
})
})
})
})
})
})
})
})
describe('special where conditions/smartWhere object', function() {
beforeEach(function(done) {
var self = this
this.User.bulkCreate([
{username: 'boo', intVal: 5, theDate: '2013-01-01 12:00'},
{username: 'boo2', intVal: 10, theDate: '2013-01-10 12:00'}
]).success(function(user2) {
done()
})
})
it('should be able to find rows where attribute is in a list of values', function (done) {
this.User.findAll({
where: {
username: ['boo', 'boo2']
}
}).success(function(users){
expect(users).to.have.length(2);
done()
});
})
it('should not break when trying to find rows using an array of primary keys', function (done) {
this.User.findAll({
where: {
id: [1, 2, 3]
}
}).success(function(users){
done();
});
})
it('should be able to find a row using like', function(done) {
this.User.findAll({
where: {
username: {
like: '%2'
}
}
}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row using not like', function(done) {
this.User.findAll({
where: {
username: {
nlike: '%2'
}
}
}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row between a certain date using the between shortcut', function(done) {
this.User.findAll({
where: {
theDate: {
'..': ['2013-01-02', '2013-01-11']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row not between a certain integer using the not between shortcut', function(done) {
this.User.findAll({
where: {
intVal: {
'!..': [8, 10]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to handle false/true values just fine...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres") ? '"' : '`'
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
User.all({where: [escapeChar + 'aBool' + escapeChar + ' = ?', false]}).success(function(users) {
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo5')
User.all({where: [escapeChar + 'aBool' + escapeChar + ' = ?', true]}).success(function(_users) {
expect(_users).to.have.length(1)
expect(_users[0].username).to.equal('boo6')
done()
})
})
})
})
it('should be able to handle false/true values through associations as well...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres") ? '"' : '`'
var Passports = this.sequelize.define('Passports', {
isActive: Sequelize.BOOLEAN
})
User.hasMany(Passports)
Passports.belongsTo(User)
User.sync({ force: true }).success(function() {
Passports.sync({ force: true }).success(function() {
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
Passports.bulkCreate([
{isActive: true},
{isActive: false}
]).success(function() {
User.find(1).success(function(user) {
Passports.find(1).success(function(passport) {
user.setPassports([passport]).success(function() {
User.find(2).success(function(_user) {
Passports.find(2).success(function(_passport) {
_user.setPassports([_passport]).success(function() {
_user.getPassports({where: [escapeChar + 'isActive' + escapeChar + ' = ?', false]}).success(function(theFalsePassport) {
user.getPassports({where: [escapeChar + 'isActive' + escapeChar + ' = ?', true]}).success(function(theTruePassport) {
expect(theFalsePassport).to.have.length(1)
expect(theFalsePassport[0].isActive).to.be.false
expect(theTruePassport).to.have.length(1)
expect(theTruePassport[0].isActive).to.be.true
done()
})
})
})
})
})
})
})
})
})
})
})
})
})
it('should be able to return a record with primaryKey being null for new inserts', function(done) {
var Session = this.sequelize.define('Session', {
token: { type: DataTypes.TEXT, allowNull: false },
lastUpdate: { type: DataTypes.DATE, allowNull: false }
}, {
charset: 'utf8',
collate: 'utf8_general_ci',
omitNull: true
})
, User = this.sequelize.define('User', {
name: { type: DataTypes.STRING, allowNull: false, unique: true },
password: { type: DataTypes.STRING, allowNull: false },
isAdmin: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
}, {
charset: 'utf8',
collate: 'utf8_general_ci'
})
User.hasMany(Session, { as: 'Sessions' })
Session.belongsTo(User)
Session.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
User.create({name: 'Name1', password: '123', isAdmin: false}).success(function(user) {
var sess = Session.build({
lastUpdate: new Date(),
token: '123'
})
user.addSession(sess).success(function(u) {
expect(u.token).to.equal('123')
done()
})
})
})
})
})
it('should be able to find a row between a certain date', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row between a certain date and an additional where clause', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
},
intVal: 10
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row not between a certain integer', function(done) {
this.User.findAll({
where: {
intVal: {
nbetween: [8, 10]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not between and between logic', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2012-12-10', '2013-01-02'],
nbetween: ['2013-01-04', '2013-01-20']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not between and between logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
between: [new Date('2012-12-10'), new Date('2013-01-02')],
nbetween: [new Date('2013-01-04'), new Date('2013-01-20')]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using greater than or equal to logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
gte: new Date('2013-01-09')
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row using greater than or equal to', function(done) {
this.User.find({
where: {
intVal: {
gte: 6
}
}
}).success(function(user) {
expect(user.username).to.equal('boo2')
expect(user.intVal).to.equal(10)
done()
})
})
it('should be able to find a row using greater than', function(done) {
this.User.find({
where: {
intVal: {
gt: 5
}
}
}).success(function(user) {
expect(user.username).to.equal('boo2')
expect(user.intVal).to.equal(10)
done()
})
})
it('should be able to find a row using lesser than or equal to', function(done) {
this.User.find({
where: {
intVal: {
lte: 5
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should be able to find a row using lesser than', function(done) {
this.User.find({
where: {
intVal: {
lt: 6
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should have no problem finding a row using lesser and greater than', function(done) {
this.User.findAll({
where: {
intVal: {
lt: 6,
gt: 4
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not equal to logic', function(done) {
this.User.find({
where: {
intVal: {
ne: 10
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should be able to find multiple users with any of the special where logic properties', function(done) {
this.User.findAll({
where: {
intVal: {
lte: 10
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
expect(users[1].username).to.equal('boo2')
expect(users[1].intVal).to.equal(10)
done()
})
})
})
describe('eager loading', function() {
describe('belongsTo', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('TaskBelongsTo', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Task.belongsTo(self.Worker)
self.Worker.sync({ force: true }).success(function() {
self.Task.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
self.task.setWorker(self.worker).success(function() {
done()
})
})
})
})
})
})
it('throws an error about unexpected input if include contains a non-object', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ 1 ] })
}).to.throw(Error, 'Include unexpected. Element has to be either an instance of DAOFactory or an object.')
done()
})
it('throws an error about missing attributes if include contains an object with daoFactory', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ { daoFactory: self.Worker } ] })
}).to.throw(Error, 'Include malformed. Expected attributes: daoFactory, as!')
done()
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ self.Task ] })
}).to.throw(Error, 'TaskBelongsTo is not associated to Worker!')
done()
})
it('returns the associated worker via task.worker', function(done) {
this.Task.all({
where: { title: 'homework' },
include: [ this.Worker ]
}).complete(function(err, tasks) {
expect(err).to.be.null
expect(tasks).to.exist
expect(tasks[0].worker).to.exist
expect(tasks[0].worker.name).to.equal('worker')
done()
})
})
})
describe('hasOne', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('TaskHasOne', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Worker.hasOne(self.Task)
self.Worker.sync({ force: true }).success(function() {
self.Task.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
self.worker.setTaskHasOne(self.task).success(function() {
done()
})
})
})
})
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.all({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to TaskHasOne!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.all({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, workers) {
expect(err).to.be.null
expect(workers).to.exist
expect(workers[0].taskHasOne).to.exist
expect(workers[0].taskHasOne.title).to.equal('homework')
done()
})
})
})
describe('hasOne with alias', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('Task', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Worker.hasOne(self.Task, { as: 'ToDo' })
self.Worker.sync({ force: true }).success(function() {
self.Task.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
self.worker.setToDo(self.task).success(function() {
done()
})
})
})
})
})
})
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.all({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDo' } ]
}).complete(function(err, workers) {
expect(err).to.be.null
expect(workers).to.exist
expect(workers[0].toDo).to.exist
expect(workers[0].toDo.title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.all({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDo' } ]
}).complete(function(err, workers) {
expect(workers[0].toDo.title).to.equal('homework')
done()
})
})
})
describe('hasMany', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('Task', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Worker.hasMany(self.Task)
self.Worker.sync({ force: true }).success(function() {
self.Task.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
self.worker.setTasks([ self.task ]).success(function() {
done()
})
})
})
})
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.findAll({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to Task!')
done()
})
it('returns the associated tasks via worker.tasks', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, workers) {
expect(err).to.be.null
expect(workers).to.exist
expect(workers[0].tasks).to.exist
expect(workers[0].tasks[0].title).to.equal('homework')
done()
})
})
})
describe('hasMany with alias', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('Task', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Worker.hasMany(self.Task, { as: 'ToDos' })
self.Worker.sync({ force: true }).success(function() {
self.Task.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
self.worker.setToDos([ self.task ]).success(function() {
done()
})
})
})
})
})
})
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.findAll({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.findAll({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, workers) {
expect(err).to.be.null
expect(workers).to.exist
expect(workers[0].toDos).to.exist
expect(workers[0].toDos[0].title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, workers) {
expect(workers[0].toDos[0].title).to.equal('homework')
done()
})
})
})
describe('queryOptions', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.user = user
done()
})
})
it("should return a DAO when queryOptions are not set", function(done) {
var self = this
this.User.findAll({ where: { username: 'barfooz'}}).done(function(err, users) {
users.forEach(function (user) {
expect(user).to.be.instanceOf(self.User.DAO)
})
done()
})
})
it("should return a DAO when raw is false", function(done) {
var self = this
this.User.findAll({ where: { username: 'barfooz'}}, { raw: false }).done(function(err, users) {
users.forEach(function (user) {
expect(user).to.be.instanceOf(self.User.DAO)
})
done()
})
})
it("should return raw data when raw is true", function(done) {
var self = this
this.User.findAll({ where: { username: 'barfooz'}}, { raw: true }).done(function(err, users) {
users.forEach(function(user) {
expect(user).to.not.be.instanceOf(self.User.DAO)
expect(users[0]).to.be.instanceOf(Object)
})
done()
})
})
})
})
describe('normal findAll', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'user', data: 'foobar', theDate: moment().toDate()}).success(function(user) {
self.User.create({username: 'user2', data: 'bar', theDate: moment().toDate()}).success(function(user2){
self.users = [user].concat(user2)
done()
})
})
})
it("finds all entries", function(done) {
this.User.all().on('success', function(users) {
expect(users.length).to.equal(2)
done()
})
})
it('does not modify the passed arguments', function (done) {
var options = { where: ['username = ?', 'awesome']}
this.User.findAll(options).success(function(user) {
expect(options).to.deep.equal({ where: ['username = ?', 'awesome']})
done()
})
})
it("finds all users matching the passed conditions", function(done) {
this.User.findAll({where: "id != " + this.users[1].id}).success(function(users) {
expect(users.length).to.equal(1)
done()
})
})
it("can also handle array notation", function(done) {
var self = this
this.User.findAll({where: ['id = ?', this.users[1].id]}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].id).to.equal(self.users[1].id)
done()
})
})
it("sorts the results via id in ascending order", function(done) {
this.User.findAll().success(function(users) {
expect(users.length).to.equal(2);
expect(users[0].id).to.be.below(users[1].id)
done()
})
})
it("sorts the results via id in descending order", function(done) {
this.User.findAll({ order: "id DESC" }).success(function(users) {
expect(users[0].id).to.be.above(users[1].id)
done()
})
})
it("sorts the results via a date column", function(done) {
var self = this
self.User.create({username: 'user3', data: 'bar', theDate: moment().add('hours', 2).toDate()}).success(function(){
self.User.findAll({ order: [['theDate', 'DESC']] }).success(function(users) {
expect(users[0].id).to.be.above(users[2].id)
done()
})
})
})
it("handles offset and limit", function(done) {
var self = this
this.User.bulkCreate([{username: 'bobby'}, {username: 'tables'}]).success(function() {
self.User.findAll({ limit: 2, offset: 2 }).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].id).to.equal(3)
done()
})
})
})
it('should allow us to find IDs using capital letters', function(done) {
var User = this.sequelize.define('User' + config.rand(), {
ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
Login: { type: Sequelize.STRING }
})
User.sync({ force: true }).success(function() {
User.create({Login: 'foo'}).success(function() {
User.findAll({ID: 1}).success(function(user) {
expect(user).to.be.instanceof(Array)
expect(user).to.have.length(1)
done()
})
})
})
})
})
})
describe('findAndCountAll', function() {
beforeEach(function(done) {
var self = this
this.User.bulkCreate([
{username: 'user', data: 'foobar'},
{username: 'user2', data: 'bar'},
{username: 'bobby', data: 'foo'}
]).success(function() {
self.User.all().success(function(users){
self.users = users
done()
})
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.findAndCountAll().success(function(info1) {
User.findAndCountAll({ transaction: t }).success(function(info2) {
expect(info1.count).to.equal(0)
expect(info2.count).to.equal(1)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it("handles where clause [only]", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
done()
})
})
it("handles where clause with ordering [only]", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id, order: 'id ASC'}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
done()
})
})
it("handles offset", function(done) {
this.User.findAndCountAll({offset: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
done()
})
})
it("handles limit", function(done) {
this.User.findAndCountAll({limit: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(1)
done()
})
})
it("handles offset and limit", function(done) {
this.User.findAndCountAll({offset: 1, limit: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(1)
done()
})
})
it("handles attributes", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id, attributes: ['data']}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
expect(info.rows[0].selectedValues).to.not.have.property('username')
expect(info.rows[1].selectedValues).to.not.have.property('username')
done()
})
})
})
describe('all', function() {
beforeEach(function(done) {
this.User.bulkCreate([
{username: 'user', data: 'foobar'},
{username: 'user2', data: 'bar'}
]).complete(function() {
done()
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.all().success(function(users1) {
User.all({ transaction: t }).success(function(users2) {
expect(users1.length).to.equal(0)
expect(users2.length).to.equal(1)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it("should return all users", function(done) {
this.User.all().on('success', function(users) {
expect(users.length).to.equal(2)
done()
})
})
})
})
\ No newline at end of file
/* jshint camelcase: false */
/* jshint expr: true */
var chai = require('chai')
, Sequelize = require('../../index')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/../config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, _ = require('lodash')
, moment = require('moment')
, async = require('async')
chai.use(datetime)
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("DAOFactory"), function () {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN
})
this.User.sync({ force: true }).success(function() {
done()
})
})
describe('scopes', function() {
beforeEach(function(done) {
this.ScopeMe = this.sequelize.define('ScopeMe', {
username: Sequelize.STRING,
email: Sequelize.STRING,
access_level: Sequelize.INTEGER,
other_value: Sequelize.INTEGER
}, {
defaultScope: {
where: {
access_level: {
gte: 5
}
}
},
scopes: {
orderScope: {
order: 'access_level DESC'
},
limitScope: {
limit: 2
},
sequelizeTeam: {
where: ['email LIKE \'%@sequelizejs.com\'']
},
fakeEmail: {
where: ['email LIKE \'%@fakeemail.com\'']
},
highValue: {
where: {
other_value: {
gte: 10
}
}
},
isTony: {
where: {
username: 'tony'
}
},
canBeTony: {
where: {
username: ['tony']
}
},
canBeDan: {
where: {
username: {
in: 'dan'
}
}
},
actualValue: function(value) {
return {
where: {
other_value: value
}
}
},
complexFunction: function(email, accessLevel) {
return {
where: ['email like ? AND access_level >= ?', email + '%', accessLevel]
}
},
lowAccess: {
where: {
access_level: {
lte: 5
}
}
},
escape: {
where: {
username: "escape'd"
}
}
}
})
this.sequelize.sync({force: true}).success(function() {
var records = [
{username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10},
{username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11},
{username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7}
];
this.ScopeMe.bulkCreate(records).success(function() {
done()
})
}.bind(this))
})
it("should have no problems with escaping SQL", function(done) {
var self = this
this.ScopeMe.create({username: 'escape\'d', email: 'fake@fakemail.com'}).success(function(){
self.ScopeMe.scope('escape').all().success(function(users){
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('escape\'d');
done()
})
})
})
it("should be able to use a defaultScope if declared", function(done) {
this.ScopeMe.all().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(2)
expect([10,5].indexOf(users[0].access_level) !== -1).to.be.true
expect([10,5].indexOf(users[1].access_level) !== -1).to.be.true
expect(['dan', 'tobi'].indexOf(users[0].username) !== -1).to.be.true
expect(['dan', 'tobi'].indexOf(users[1].username) !== -1).to.be.true
done()
})
})
it("should be able to amend the default scope with a find object", function(done) {
this.ScopeMe.findAll({where: {username: 'dan'}}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('dan')
done()
})
})
it("should be able to override the default scope", function(done) {
this.ScopeMe.scope('fakeEmail').findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('tobi')
done()
})
})
it("should be able to combine two scopes", function(done) {
this.ScopeMe.scope(['sequelizeTeam', 'highValue']).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('dan')
done()
})
})
it("should be able to call a scope that's a function", function(done) {
this.ScopeMe.scope({method: ['actualValue', 11]}).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('tobi')
done()
})
})
it("should be able to handle multiple function scopes", function(done) {
this.ScopeMe.scope([{method: ['actualValue', 10]}, {method: ['complexFunction', 'dan', '5']}]).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('dan')
done()
})
})
it("should be able to stack the same field in the where clause", function(done) {
this.ScopeMe.scope(['canBeDan', 'canBeTony']).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(2)
expect(['dan', 'tony'].indexOf(users[0].username) !== -1).to.be.true
expect(['dan', 'tony'].indexOf(users[1].username) !== -1).to.be.true
done()
})
})
it("should be able to merge scopes", function(done) {
this.ScopeMe.scope(['highValue', 'isTony', {merge: true, method: ['actualValue', 7]}]).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('tony')
done()
})
})
it("should give us the correct order if we declare an order in our scope", function(done) {
this.ScopeMe.scope('sequelizeTeam', 'orderScope').findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(2)
expect(users[0].username).to.equal('dan')
expect(users[1].username).to.equal('tony')
done()
})
})
it("should give us the correct order as well as a limit if we declare such in our scope", function(done) {
this.ScopeMe.scope(['orderScope', 'limitScope']).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(2)
expect(users[0].username).to.equal('tobi')
expect(users[1].username).to.equal('dan')
done()
})
})
it("should have no problems combining scopes and traditional where object", function(done) {
this.ScopeMe.scope('sequelizeTeam').findAll({where: {other_value: 10}}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(1)
expect(users[0].username).to.equal('dan')
expect(users[0].access_level).to.equal(5)
expect(users[0].other_value).to.equal(10)
done()
})
})
it("should be able to remove all scopes", function(done) {
this.ScopeMe.scope(null).findAll().success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users.length).to.equal(3)
done()
})
})
it("should have no problem performing findOrCreate", function(done) {
this.ScopeMe.findOrCreate({username: 'fake'}).success(function(user) {
expect(user.username).to.equal('fake')
done()
})
})
it("should be able to hold multiple scope objects", function(done) {
var sequelizeTeam = this.ScopeMe.scope('sequelizeTeam', 'orderScope')
, tobi = this.ScopeMe.scope({method: ['actualValue', 11]})
sequelizeTeam.all().success(function(team) {
tobi.all().success(function(t) {
expect(team).to.be.an.instanceof(Array)
expect(team.length).to.equal(2)
expect(team[0].username).to.equal('dan')
expect(team[1].username).to.equal('tony')
expect(t).to.be.an.instanceof(Array)
expect(t.length).to.equal(1)
expect(t[0].username).to.equal('tobi')
done()
})
})
})
it("should gracefully omit any scopes that don't exist", function(done) {
this.ScopeMe.scope('sequelizeTeam', 'orderScope', 'doesntexist').all().success(function(team) {
expect(team).to.be.an.instanceof(Array)
expect(team.length).to.equal(2)
expect(team[0].username).to.equal('dan')
expect(team[1].username).to.equal('tony')
done()
})
})
it("should gracefully omit any scopes that don't exist through an array", function(done) {
this.ScopeMe.scope(['sequelizeTeam', 'orderScope', 'doesntexist']).all().success(function(team) {
expect(team).to.be.an.instanceof(Array)
expect(team.length).to.equal(2)
expect(team[0].username).to.equal('dan')
expect(team[1].username).to.equal('tony')
done()
})
})
it("should gracefully omit any scopes that don't exist through an object", function(done) {
this.ScopeMe.scope('sequelizeTeam', 'orderScope', {method: 'doesntexist'}).all().success(function(team) {
expect(team).to.be.an.instanceof(Array)
expect(team.length).to.equal(2)
expect(team[0].username).to.equal('dan')
expect(team[1].username).to.equal('tony')
done()
})
})
it("should emit an error for scopes that don't exist with silent: false", function(done) {
try {
this.ScopeMe.scope('doesntexist', {silent: false})
} catch (err) {
expect(err.message).to.equal('Invalid scope doesntexist called.')
done()
}
})
})
})
\ No newline at end of file
......@@ -944,7 +944,9 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
user.age = user.age + 1 // happy birthday joe
user.save().success(function() {
user.save().done(function(err) {
expect(err).not.to.be.ok
expect(user.username).to.equal('joe')
expect(user.age).to.equal(2)
expect(user.projects).to.exist
......@@ -1078,7 +1080,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
it('returns a response that can be stringified', function(done) {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.stringify(user)).to.deep.equal('{"username":"test.user","age":99,"isAdmin":true,"id":null}')
expect(JSON.stringify(user)).to.deep.equal('{"id":null,"username":"test.user","age":99,"isAdmin":true}')
done()
})
......
......@@ -25,7 +25,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
[Sequelize.TEXT, 'TEXT', 'TEXT'],
[Sequelize.DATE, 'DATE', 'DATETIME'],
[Sequelize.NOW, 'NOW', 'NOW'],
[Sequelize.UUID, 'UUID', 'CHAR(36)'],
[Sequelize.UUID, 'UUID', 'UUID'],
[Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'],
[Sequelize.BLOB, 'BLOB', 'BLOB'],
......
......@@ -6,12 +6,12 @@ var chai = require('chai')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Language Util"), function() {
before(function(done) {
beforeEach(function(done) {
this.sequelize.options.language = 'es'
done()
})
after(function(done) {
afterEach(function(done) {
this.sequelize.options.language = 'en'
done()
})
......@@ -37,8 +37,8 @@ describe(Support.getTestDialectTeaser("Language Util"), function() {
table2.belongsTo(table)
table3.hasMany(table2)
expect(table.associations.androides.identifier).to.equal('arbolId')
expect(table2.associations.arboles).to.exist
expect(table.associations.androide.identifier).to.equal('arbolId')
expect(table2.associations.arbol).to.exist
expect(table3.associations.androideshombres).to.exist
done()
})
......
......@@ -268,6 +268,11 @@ if (Support.dialectIsMySQL()) {
arguments: ['myTable', {where: null}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'buffer as where argument',
arguments: ['myTable', {where: { field: new Buffer("Sequelize")}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field`=X'53657175656c697a65';",
context: QueryGenerator
}
],
......
......@@ -38,12 +38,16 @@ if (dialect.match(/^postgres/)) {
it("should not use a combined name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist
setTimeout(function () {
done()
}, 50)
})
it("should use the specified name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist
setTimeout(function () {
done()
}, 50)
})
})
})
......
......@@ -333,6 +333,11 @@ if (dialect.match(/^postgres/)) {
}, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"name\"='foo'';DROP TABLE mySchema.myTable;';"
}, {
title: 'buffer as where argument',
arguments: ['myTable', {where: { field: new Buffer("Sequelize")}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"field\"=E'\\\\x53657175656c697a65';",
context: QueryGenerator
},
// Variants when quoteIdentifiers is false
......@@ -410,6 +415,10 @@ if (dialect.match(/^postgres/)) {
insertQuery: [
{
arguments: ['myTable', {}],
expectation: "INSERT INTO \"myTable\" DEFAULT VALUES RETURNING *;"
},
{
arguments: ['myTable', {name: 'foo'}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;"
}, {
......
......@@ -255,6 +255,11 @@ if (dialect === 'sqlite') {
arguments: ['myTable', {where: null}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'buffer as where argument',
arguments: ['myTable', {where: { field: new Buffer("Sequelize")}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field`=X'53657175656c697a65';",
context: QueryGenerator
}
],
......
......@@ -48,6 +48,7 @@ var Support = {
options.pool = options.pool || config.pool
var sequelizeOptions = {
host: options.host || config.host,
logging: options.logging,
dialect: options.dialect,
port: options.port || process.env.SEQ_PORT || config.port,
......@@ -55,10 +56,6 @@ var Support = {
dialectOptions: options.dialectOptions || {}
}
if (!!options.host) {
sequelizeOptions.host = options.host
}
if (!!options.define) {
sequelizeOptions.define = options.define
}
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!