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

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") TESTS = $(shell find ./test/* -name "*.test.js")
DIALECT ?= mysql DIALECT ?= mysql
......
...@@ -10,6 +10,8 @@ There is a parallel "branch" of the project, released as `2.0.0-alphaX` in NPM. ...@@ -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 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 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 ### ### 1.6.0 ###
- We changed the way timestamps are handled. From v1.6.0 on timestamps are stored and loaded as UTC. - 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/ ...@@ -37,11 +39,11 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/
## Documentation and Updates ## ## 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). 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. 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) - [Twitter](http://twitter.com/sdepold)
- [IRC](http://webchat.freenode.net?channels=sequelizejs) - [IRC](http://webchat.freenode.net?channels=sequelizejs)
- [Google Groups](https://groups.google.com/forum/#!forum/sequelize) - [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 ...@@ -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 Once Node.JS is installed on your computer, you will also have access to the lovely
Node Package Manager (NPM). 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 First class citizen of Sequelize was MySQL. Over time, Sequelize began to
become compatible to SQLite and PostgreSQL. In order to provide a fully become compatible to SQLite and PostgreSQL. In order to provide a fully
...@@ -130,16 +141,32 @@ $ echo "CREATE DATABASE sequelize_test;" | mysql -uroot ...@@ -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 **AND ONE LAST THING:** Once `npm install` worked for you (see below), you'll
get SQLite tests for free :) 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 ```console
$ cd path/to/sequelize $ sudo docker stop $CONTAINER
$ npm install
``` ```
When running tests repeatedly, you only need to redo step 3 if you haven't stopped the container.
### 4. Run the tests ### ### 4. Run the tests ###
Right now, the test base is split into the `test` folder (which contains the Right now, the test base is split into the `test` folder (which contains the
......
...@@ -22,6 +22,15 @@ var relativeConfigFile = function() { ...@@ -22,6 +22,15 @@ var relativeConfigFile = function() {
return path.relative(process.cwd(), configuration.configFile) 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 writeDefaultConfig = function(config) {
var configPath = path.dirname(configuration.configFile) var configPath = path.dirname(configuration.configFile)
...@@ -109,7 +118,11 @@ program ...@@ -109,7 +118,11 @@ program
.parse(process.argv) .parse(process.argv)
if(typeof program.config === 'string') { if(typeof program.config === 'string') {
if (isRelativePath(program.config)) {
configuration.configFile = path.join(process.cwd(), program.config);
} else {
configuration.configFile = program.config configuration.configFile = program.config
}
} }
if(typeof program.env === 'string') { if(typeof program.env === 'string') {
......
...@@ -3,22 +3,32 @@ var Utils = require("./../utils") ...@@ -3,22 +3,32 @@ var Utils = require("./../utils")
, Helpers = require('./helpers') , Helpers = require('./helpers')
module.exports = (function() { module.exports = (function() {
var BelongsTo = function(srcDAO, targetDAO, options) { var BelongsTo = function(source, target, options) {
this.associationType = 'BelongsTo' this.associationType = 'BelongsTo'
this.source = srcDAO this.source = source
this.target = targetDAO this.target = target
this.options = options this.options = options
this.isSingleAssociation = true
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) { 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.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 this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as)
: this.options.as || this.target.tableName : 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 // the id is in the source table
...@@ -40,11 +50,10 @@ module.exports = (function() { ...@@ -40,11 +50,10 @@ module.exports = (function() {
BelongsTo.prototype.injectGetter = function(obj) { BelongsTo.prototype.injectGetter = function(obj) {
var self = this 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) , primaryKeys = Object.keys(self.target.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id' , primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
obj[accessor] = function(params) { obj[this.accessors.get] = function(params) {
var id = this[self.identifier] var id = this[self.identifier]
, where = {} , where = {}
, options = Utils._.pick(params || {}, 'transaction') , options = Utils._.pick(params || {}, 'transaction')
...@@ -69,9 +78,8 @@ module.exports = (function() { ...@@ -69,9 +78,8 @@ module.exports = (function() {
BelongsTo.prototype.injectSetter = function(obj) { BelongsTo.prototype.injectSetter = function(obj) {
var self = this 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) : [] var primaryKeys = !!associatedObject && !!associatedObject.daoFactory ? Object.keys(associatedObject.daoFactory.primaryKeys) : []
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id' , primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
......
...@@ -2,8 +2,8 @@ var Utils = require('./../utils') ...@@ -2,8 +2,8 @@ var Utils = require('./../utils')
, Transaction = require('./../transaction') , Transaction = require('./../transaction')
module.exports = (function() { module.exports = (function() {
var HasManyDoubleLinked = function(definition, instance) { var HasManyDoubleLinked = function(association, instance) {
this.__factory = definition this.association = association
this.instance = instance this.instance = instance
// Alias the quoting methods for code brevity // Alias the quoting methods for code brevity
...@@ -17,47 +17,47 @@ module.exports = (function() { ...@@ -17,47 +17,47 @@ module.exports = (function() {
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {} var where = {}
, connectorDAO = self.__factory.connectorDAO , through = self.association.through
, options = _options || {} , options = _options || {}
, queryOptions = {} , queryOptions = {}
, association = self.__factory.target.associations[self.__factory.associationAccessor] , targetAssociation = self.association.targetAssociation
//fully qualify //fully qualify
var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys) var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys)
, instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id' , instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id'
where[connectorDAO.tableName+"."+self.__factory.identifier] = self.instance[instancePrimaryKey] where[through.tableName+"."+self.association.identifier] = self.instance[instancePrimaryKey]
var primaryKeys = Object.keys(connectorDAO.primaryKeys) var primaryKeys = Object.keys(through.primaryKeys)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0] , foreignKey = primaryKeys.filter(function(pk) { return pk != self.association.identifier })[0]
, foreignPrimary = Object.keys(self.__factory.target.primaryKeys) , foreignPrimary = Object.keys(self.association.target.primaryKeys)
foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id' foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id'
where[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.hasJoinTableModel = true
queryOptions.joinTableModel = connectorDAO queryOptions.joinTableModel = through
if (!options.attributes) { if (!options.attributes) {
options.attributes = [ options.attributes = [
self.QueryInterface.quoteIdentifier(self.__factory.target.tableName)+".*" self.QueryInterface.quoteIdentifier(self.association.target.tableName)+".*"
] ]
} }
if (options.joinTableAttributes) { if (options.joinTableAttributes) {
options.joinTableAttributes.forEach(function (elem) { options.joinTableAttributes.forEach(function (elem) {
options.attributes.push( options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + elem) + ' as ' + self.QueryInterface.quoteIdentifiers(through.tableName + '.' + elem) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + elem, true) self.QueryInterface.quoteIdentifier(through.name + '.' + elem, true)
) )
}) })
} else { } else {
Utils._.forOwn(connectorDAO.rawAttributes, function (elem, key) { Utils._.forOwn(through.rawAttributes, function (elem, key) {
options.attributes.push( options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + key) + ' as ' + self.QueryInterface.quoteIdentifiers(through.tableName + '.' + key) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + key, true) self.QueryInterface.quoteIdentifier(through.name + '.' + key, true)
) )
}) })
} }
...@@ -65,14 +65,14 @@ module.exports = (function() { ...@@ -65,14 +65,14 @@ module.exports = (function() {
if (options.where) { if (options.where) {
if (Array.isArray(options.where)) { if (Array.isArray(options.where)) {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect) smart = Utils.smartWhere([where, options.where], self.association.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect) smart = Utils.compileSmartWhere.call(self.association.target, smart, self.association.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) { if (smart.length > 0) {
options.where = smart options.where = smart
} }
} else { } else {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect) smart = Utils.smartWhere([where, options.where], self.association.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect) smart = Utils.compileSmartWhere.call(self.association.target, smart, self.association.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) { if (smart.length > 0) {
options.where = smart options.where = smart
} }
...@@ -81,7 +81,7 @@ module.exports = (function() { ...@@ -81,7 +81,7 @@ module.exports = (function() {
options.where = where; options.where = where;
} }
self.__factory.target.findAllJoin(connectorDAO.tableName, options, queryOptions) self.association.target.findAllJoin(through.tableName, options, queryOptions)
.on('success', function(objects) { customEventEmitter.emit('success', objects) }) .on('success', function(objects) { customEventEmitter.emit('success', objects) })
.on('error', function(err){ customEventEmitter.emit('error', err) }) .on('error', function(err){ customEventEmitter.emit('error', err) })
.on('sql', function(sql) { customEventEmitter.emit('sql', sql)}) .on('sql', function(sql) { customEventEmitter.emit('sql', sql)})
...@@ -93,10 +93,10 @@ module.exports = (function() { ...@@ -93,10 +93,10 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) { HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) {
var self = this var self = this
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor] , targetAssociation = self.association.targetAssociation
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier , foreignIdentifier = targetAssociation.isSelfAssociation ? targetAssociation.foreignIdentifier : targetAssociation.identifier
, sourceKeys = Object.keys(self.__factory.source.primaryKeys) , sourceKeys = Object.keys(self.association.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys) , targetKeys = Object.keys(self.association.target.primaryKeys)
, obsoleteAssociations = [] , obsoleteAssociations = []
, changedAssociations = [] , changedAssociations = []
, options = {} , options = {}
...@@ -120,17 +120,25 @@ module.exports = (function() { ...@@ -120,17 +120,25 @@ module.exports = (function() {
if (!newObj) { if (!newObj) {
obsoleteAssociations.push(old) 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 = { var changedAssociation = {
where: {}, 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 changedAssociation.where[foreignIdentifier] = newObj[foreignIdentifier] || newObj.id
if (Object.keys(changedAssociation.attributes).length) {
changedAssociations.push(changedAssociation) changedAssociations.push(changedAssociation)
} }
}
}) })
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
...@@ -140,32 +148,32 @@ module.exports = (function() { ...@@ -140,32 +148,32 @@ module.exports = (function() {
var where = {} var where = {}
where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) where[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where, options)) chainer.add(self.association.through.destroy(where, options))
} }
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) { var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {} var attributes = {}
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) attributes[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
if (association.hasJoinTableModel) { if (Object(targetAssociation.through) === targetAssociation.through) {
attributes = Utils._.defaults(attributes, unassociatedObject[association.connectorDAO.name], defaultAttributes) attributes = Utils._.defaults(attributes, unassociatedObject[targetAssociation.through.name], defaultAttributes)
} }
return attributes return attributes
}) })
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk, options)) chainer.add(self.association.through.bulkCreate(bulk, options))
} }
if (changedAssociations.length > 0) { if (changedAssociations.length > 0) {
changedAssociations.forEach(function (assoc) { changedAssociations.forEach(function (assoc) {
chainer.add(self.__factory.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() { ...@@ -178,26 +186,28 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) { HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) {
var attributes = {} var attributes = {}
, association = this.__factory.target.associations[this.__factory.associationAccessor] , targetAssociation = this.association.targetAssociation
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier; , foreignIdentifier = targetAssociation.isSelfAssociation ? targetAssociation.foreignIdentifier : targetAssociation.identifier;
var sourceKeys = Object.keys(this.__factory.source.primaryKeys); var sourceKeys = Object.keys(this.association.source.primaryKeys);
var targetKeys = Object.keys(this.__factory.target.primaryKeys); var targetKeys = Object.keys(this.association.target.primaryKeys);
attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id) attributes[this.association.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id)
if (exists) { // implies hasJoinTableModel === true if (exists) {
var where = attributes 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 { } else {
if (association.hasJoinTableModel === true) { emitterProxy.emit('success')
attributes = Utils._.defaults(attributes, newAssociation[association.connectorDAO.name], additionalAttributes)
} }
} 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) }) .success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) }) .error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) }) .on('sql', function(sql) { emitterProxy.emit('sql', sql) })
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
, Helpers = require('./helpers') , Helpers = require('./helpers')
, _ = require('lodash')
var HasManySingleLinked = require("./has-many-single-linked") var HasManySingleLinked = require("./has-many-single-linked")
, HasManyMultiLinked = require("./has-many-double-linked") , HasManyMultiLinked = require("./has-many-double-linked")
module.exports = (function() { module.exports = (function() {
var HasMany = function(srcDAO, targetDAO, options) { var HasMany = function(source, target, options) {
var self = this
this.associationType = 'HasMany' this.associationType = 'HasMany'
this.source = srcDAO this.source = source
this.target = targetDAO this.target = target
this.targetAssociation = null
this.options = options this.options = options
this.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.isSelfAssociation = (this.source.tableName === this.target.tableName)
this.hasJoinTableModel = !!this.options.joinTableModel this.doubleLinked = false
this.combinedTableName = Utils.combineTableNames(
var combinedTableName;
if (this.hasJoinTableModel) {
combinedTableName = this.options.joinTableModel.tableName
} else if (this.options.joinTableName) {
combinedTableName = this.options.joinTableName
} else {
combinedTableName = Utils.combineTableNames(
this.source.tableName, this.source.tableName,
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.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 * If we are double linked, and through is either default or a string, we create the through model and set it on both associations
this.associationAccessor = this.options.as || this.combinedName */
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)) var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
...@@ -46,27 +116,27 @@ module.exports = (function() { ...@@ -46,27 +116,27 @@ module.exports = (function() {
// the id is in the target table // the id is in the target table
// or in an extra table which connects two tables // or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() { HasMany.prototype.injectAttributes = function() {
var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor) var doubleLinked = this.doubleLinked
, self = this , self = this
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
// is there already a single sided association between the source and the target? // is there already a single sided association between the source and the target?
// or is the association on the model itself? // or is the association on the model itself?
if ((this.isSelfAssociation && this.useJunctionTable) || multiAssociation) { if ((this.isSelfAssociation && this.through) || doubleLinked) {
// remove the obsolete association identifier from the source // remove the obsolete association identifier from the source
if (this.isSelfAssociation) { if (this.isSelfAssociation) {
this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored) this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored)
} else { } else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier this.foreignIdentifier = this.targetAssociation.identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier this.targetAssociation.foreignIdentifier = this.identifier
if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) { if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[this.foreignIdentifier] delete this.source.rawAttributes[this.foreignIdentifier]
} }
if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) { if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) {
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier] delete this.targetAssociation.source.rawAttributes[this.identifier]
} }
} }
...@@ -79,28 +149,18 @@ module.exports = (function() { ...@@ -79,28 +149,18 @@ module.exports = (function() {
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true} combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true} combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true}
if (this.hasJoinTableModel === true) {
this.connectorDAO = this.options.joinTableModel
// remove any previously defined PKs // 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) { 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.through.rawAttributes = Utils._.merge(this.through.rawAttributes, combinedTableAttributes)
this.connectorDAO.init(this.connectorDAO.daoFactoryManager) this.through.init(this.through.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
}
if (this.options.syncOnAssociation) { if (this.options.syncOnAssociation) {
this.connectorDAO.sync() this.through.sync()
} }
} else { } else {
var newAttributes = {} var newAttributes = {}
...@@ -120,7 +180,7 @@ module.exports = (function() { ...@@ -120,7 +180,7 @@ module.exports = (function() {
var self = this var self = this
obj[this.accessors.get] = function(options) { 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) return new Class(self, this).injectGetter(options)
} }
...@@ -178,15 +238,10 @@ module.exports = (function() { ...@@ -178,15 +238,10 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]() instance[self.accessors.get]()
.success(function(oldAssociatedObjects) { .success(function(oldAssociatedObjects) {
var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked var Class = self.doubleLinked ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectSetter(emitter, oldAssociatedObjects, newAssociatedObjects, defaultAttributes) new Class(self, instance).injectSetter(emitter, oldAssociatedObjects, newAssociatedObjects, defaultAttributes)
}) })
.error(function(err) { .proxy(emitter, {events: ['error', 'sql']})
emitter.emit('error', err)
})
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
}).run() }).run()
} }
...@@ -200,10 +255,10 @@ module.exports = (function() { ...@@ -200,10 +255,10 @@ module.exports = (function() {
where[newAssociatedObject.daoFactory.tableName+'.'+primaryKey] = newAssociatedObject[primaryKey] where[newAssociatedObject.daoFactory.tableName+'.'+primaryKey] = newAssociatedObject[primaryKey]
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]({ where: where }) instance[self.accessors.get]({ where: where })
.error(function(err){ emitter.emit('error', err)}) .proxy(emitter, {events: ['error', 'sql']})
.success(function(currentAssociatedObjects) { .success(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0 || self.hasJoinTableModel === true) { if (currentAssociatedObjects.length === 0 || Object(self.through) === self.through) {
var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked var Class = self.doubleLinked ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectAdder(emitter, newAssociatedObject, additionalAttributes, !!currentAssociatedObjects.length) new Class(self, instance).injectAdder(emitter, newAssociatedObject, additionalAttributes, !!currentAssociatedObjects.length)
} else { } else {
emitter.emit('success', newAssociatedObject); emitter.emit('success', newAssociatedObject);
...@@ -214,7 +269,7 @@ module.exports = (function() { ...@@ -214,7 +269,7 @@ module.exports = (function() {
obj[this.accessors.remove] = function(oldAssociatedObject) { obj[this.accessors.remove] = function(oldAssociatedObject) {
var instance = this var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]().success(function(currentAssociatedObjects) { instance[self.accessors.get]().success(function(currentAssociatedObjects) {
var newAssociations = [] var newAssociations = []
, oldAssociations = [] , oldAssociations = []
...@@ -242,12 +297,10 @@ module.exports = (function() { ...@@ -242,12 +297,10 @@ module.exports = (function() {
var run = function(err) { var run = function(err) {
if (!!err) { if (!!err) {
return customEventEmitter.emit('error', err) return emitter.emit('error', err)
} }
instance[self.accessors.set](newAssociations) instance[self.accessors.set](newAssociations).proxy(emitter)
.success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('error', err) })
} }
if (oldAssociations.length > 0) { if (oldAssociations.length > 0) {
...@@ -256,8 +309,7 @@ module.exports = (function() { ...@@ -256,8 +309,7 @@ module.exports = (function() {
run() run()
} }
}) })
}) }).run()
return customEventEmitter.run()
} }
return this return this
......
...@@ -8,21 +8,26 @@ module.exports = (function() { ...@@ -8,21 +8,26 @@ module.exports = (function() {
this.source = srcDAO this.source = srcDAO
this.target = targetDAO this.target = targetDAO
this.options = options this.options = options
this.isSingleAssociation = true
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) { 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) 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 this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as)
: this.options.as || this.target.tableName : this.options.as
this.options.useHooks = options.useHooks this.options.useHooks = options.useHooks
this.accessors = { this.accessors = {
get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))), get: Utils._.camelize('get_' + this.options.as),
set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))) set: Utils._.camelize('set_' + this.options.as)
} }
} }
......
...@@ -6,14 +6,14 @@ var Utils = require("./../utils") ...@@ -6,14 +6,14 @@ var Utils = require("./../utils")
/* Defines Mixin for all models. */ /* Defines Mixin for all models. */
var Mixin = module.exports = function(){} var Mixin = module.exports = function(){}
Mixin.hasOne = function(associatedDAO, options) { Mixin.hasOne = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option) // Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {} options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks) options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks options.useHooks = options.hooks
// the id is in the foreign table // the id is in the foreign table
var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasOne(this, associatedDAOFactory, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype); association.injectGetter(this.DAO.prototype);
...@@ -22,14 +22,14 @@ Mixin.hasOne = function(associatedDAO, options) { ...@@ -22,14 +22,14 @@ Mixin.hasOne = function(associatedDAO, options) {
return this return this
} }
Mixin.belongsTo = function(associatedDAO, options) { Mixin.belongsTo = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option) // Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {} options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks) options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks options.useHooks = options.hooks
// the id is in this table // the id is in this table
var association = new BelongsTo(this, associatedDAO, Utils._.extend(options, this.options)) var association = new BelongsTo(this, associatedDAOFactory, Utils._.extend(options, this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype) association.injectGetter(this.DAO.prototype)
...@@ -38,14 +38,14 @@ Mixin.belongsTo = function(associatedDAO, options) { ...@@ -38,14 +38,14 @@ Mixin.belongsTo = function(associatedDAO, options) {
return this return this
} }
Mixin.hasMany = function(associatedDAO, options) { Mixin.hasMany = function(associatedDAOFactory, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option) // Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {} options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks) options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks options.useHooks = options.hooks
// the id is in the foreign table or in a connecting table // the id is in the foreign table or in a connecting table
var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasMany(this, associatedDAOFactory, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype) association.injectGetter(this.DAO.prototype)
......
...@@ -168,7 +168,10 @@ module.exports = (function() { ...@@ -168,7 +168,10 @@ module.exports = (function() {
}) })
this.DAO.prototype.__factory = this 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.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues)
this.DAO.prototype.daoFactoryName = this.name
return this return this
} }
...@@ -555,13 +558,23 @@ module.exports = (function() { ...@@ -555,13 +558,23 @@ module.exports = (function() {
DAOFactory.prototype.build = function(values, options) { DAOFactory.prototype.build = function(values, options) {
options = options || { isNewRecord: true, isDirty: true } options = options || { isNewRecord: true, isDirty: true }
var self = this if (options.hasOwnProperty('include') && (!options.includeValidated || !options.includeNames)) {
, instance = new this.DAO(values, this.options, options.isNewRecord) 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 if (options.includeNames) {
instance.daoFactoryName = this.name options.includeNames = options.includeNames.concat(options.includeNames.map(function (key) {
instance.daoFactory = this return key.slice(0,1).toLowerCase() + key.slice(1)
instance.isDirty = options.isDirty }))
}
var self = this
, instance = new this.DAO(values, options)
return instance return instance
} }
...@@ -843,6 +856,9 @@ module.exports = (function() { ...@@ -843,6 +856,9 @@ module.exports = (function() {
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
*/ */
DAOFactory.prototype.destroy = function(where, options) { DAOFactory.prototype.destroy = function(where, options) {
options = options || {}
options.force = options.force === undefined ? false : Boolean(options.force)
var self = this var self = this
, query = null , query = null
, args = [] , args = []
...@@ -855,7 +871,7 @@ module.exports = (function() { ...@@ -855,7 +871,7 @@ module.exports = (function() {
where = newWhere || where 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 attr = Utils._.underscoredIf(self.options.deletedAt, self.options.underscored)
var attrValueHash = {} var attrValueHash = {}
attrValueHash[attr] = Utils.now() attrValueHash[attr] = Utils.now()
...@@ -1118,18 +1134,20 @@ module.exports = (function() { ...@@ -1118,18 +1134,20 @@ module.exports = (function() {
options = options || {} options = options || {}
options.where = options.where || {} 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 // Don't overwrite our explicit deletedAt search value if we provide one
if (!!options.where[this.options.deletedAt]) { if (!!options.where[deletedAtCol]) {
return options return options
} }
if (typeof options.where === "string") { 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)) { 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 { } else {
options.where[this.options.deletedAt] = null options.where[deletedAtCol] = null
} }
} }
...@@ -1215,6 +1233,11 @@ module.exports = (function() { ...@@ -1215,6 +1233,11 @@ module.exports = (function() {
var usesAlias = (include.as !== include.daoFactory.tableName) var usesAlias = (include.as !== include.daoFactory.tableName)
, association = (usesAlias ? this.getAssociationByAlias(include.as) : this.getAssociation(include.daoFactory)) , 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 // check if the current daoFactory is actually associated with the passed daoFactory
if (!!association && (!association.options.as || (association.options.as === include.as))) { if (!!association && (!association.options.as || (association.options.as === include.as))) {
include.association = association include.association = association
......
...@@ -3,16 +3,22 @@ var Utils = require("./utils") ...@@ -3,16 +3,22 @@ var Utils = require("./utils")
, DaoValidator = require("./dao-validator") , DaoValidator = require("./dao-validator")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, hstore = require('./dialects/postgres/hstore') , hstore = require('./dialects/postgres/hstore')
, _ = require('lodash')
module.exports = (function() { module.exports = (function() {
var DAO = function(values, options, isNewRecord) { var DAO = function(values, options) {
this.dataValues = {} this.dataValues = {}
this.__options = options this.__options = this.__factory.options
this.hasPrimaryKeys = options.hasPrimaryKeys this.options = options
this.selectedValues = values 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.__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) Utils._.extend(DAO.prototype, Mixin.prototype)
...@@ -36,17 +42,7 @@ module.exports = (function() { ...@@ -36,17 +42,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'values', { Object.defineProperty(DAO.prototype, 'values', {
get: function() { get: function() {
var result = {} return this.dataValues
, self = this
this.attributes.concat(this.__eagerlyLoadedAssociations).forEach(function(attr) {
result[attr] = self.dataValues.hasOwnProperty(attr)
? self.dataValues[attr]
: self[attr]
;
})
return result
} }
}) })
...@@ -104,7 +100,7 @@ module.exports = (function() { ...@@ -104,7 +100,7 @@ module.exports = (function() {
options = Utils._.extend({}, options, fieldsOrOptions) options = Utils._.extend({}, options, fieldsOrOptions)
var self = this 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) , updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored) , createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
...@@ -215,7 +211,7 @@ module.exports = (function() { ...@@ -215,7 +211,7 @@ module.exports = (function() {
return emitter.emit('error', err) return emitter.emit('error', err)
} }
result.dataValues = newValues result.dataValues = _.extend(result.dataValues, newValues)
emitter.emit('success', result) emitter.emit('success', result)
}) })
}) })
...@@ -241,7 +237,7 @@ module.exports = (function() { ...@@ -241,7 +237,7 @@ module.exports = (function() {
this.__factory.find({ this.__factory.find({
where: where, where: where,
limit: 1, limit: 1,
include: this.__eagerlyLoadedOptions || [] include: this.options.include || []
}, options) }, options)
.on('sql', function(sql) { emitter.emit('sql', sql) }) .on('sql', function(sql) { emitter.emit('sql', sql) })
.on('error', function(error) { emitter.emit('error', error) }) .on('error', function(error) { emitter.emit('error', error) })
...@@ -327,6 +323,9 @@ module.exports = (function() { ...@@ -327,6 +323,9 @@ module.exports = (function() {
} }
DAO.prototype.destroy = function(options) { DAO.prototype.destroy = function(options) {
options = options || {}
options.force = options.force === undefined ? false : Boolean(options.force)
var self = this var self = this
, query = null , query = null
...@@ -336,7 +335,7 @@ module.exports = (function() { ...@@ -336,7 +335,7 @@ module.exports = (function() {
return emitter.emit('error', err) 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) var attr = Utils._.underscoredIf(self.__options.deletedAt, self.__options.underscored)
self.dataValues[attr] = new Date() self.dataValues[attr] = new Date()
query = self.save(options) query = self.save(options)
...@@ -469,25 +468,20 @@ module.exports = (function() { ...@@ -469,25 +468,20 @@ module.exports = (function() {
} }
DAO.prototype.toJSON = function() { DAO.prototype.toJSON = function() {
return this.values; return this.dataValues;
} }
// private // 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 // set id to null if not passed as value, a newly created dao has no id
var defaults = this.hasPrimaryKeys ? {} : { id: null }, var defaults = this.hasPrimaryKeys ? {} : { id: null },
attrs = {}, attrs = {},
key; key;
// add all passed values to the dao and store the attribute names in this.attributes // 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) { if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function(valueFn, key) { Utils._.each(this.defaultValues, function(valueFn, key) {
if (!defaults.hasOwnProperty(key)) { if (!defaults.hasOwnProperty(key)) {
...@@ -532,22 +526,43 @@ module.exports = (function() { ...@@ -532,22 +526,43 @@ module.exports = (function() {
} }
for (key in attrs) { 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 var include = _.find(options.include, function (include) {
// so now we have to rebuild for bulkInserts, bulkUpdates, etc. return include.as === key || (include.as.slice(0,1).toLowerCase() + include.as.slice(1)) === key
var rebuild = {} })
var association = include.association
, self = this
// Get the correct map.... var accessor = Utils._.camelize(key)
Utils._.each(this.attributes, function(key) {
if (this.dataValues.hasOwnProperty(key)) { // downcase the first char
rebuild[key] = this.dataValues[key] 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. if (!isEmpty) {
this.dataValues = Utils._.extend(rebuild, this.dataValues) self.dataValues[accessor].push(daoInstance)
}
}
}.bind(this))
} else {
this.addAttribute(key, attrs[key])
}
}
} }
return DAO return DAO
......
...@@ -188,7 +188,7 @@ module.exports = { ...@@ -188,7 +188,7 @@ module.exports = {
FLOAT: FLOAT, FLOAT: FLOAT,
NOW: 'NOW', NOW: 'NOW',
BLOB: BLOB, BLOB: BLOB,
UUID: 'CHAR(36)', UUID: 'UUID',
UUIDV1: 'UUIDV1', UUIDV1: 'UUIDV1',
UUIDV4: 'UUIDV4', UUIDV4: 'UUIDV4',
......
...@@ -388,7 +388,7 @@ module.exports = (function() { ...@@ -388,7 +388,7 @@ module.exports = (function() {
var table = include.daoFactory.tableName var table = include.daoFactory.tableName
, as = include.as , 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)) 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) , tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0]) , attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
...@@ -407,7 +407,7 @@ module.exports = (function() { ...@@ -407,7 +407,7 @@ module.exports = (function() {
, identTarget = include.association.foreignIdentifier , identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0]) , 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(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) 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() { ...@@ -635,14 +635,14 @@ module.exports = (function() {
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName) joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.identifier) joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.identifier)
joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField) joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField)
} else if (association.connectorDAO){ } else if (Object(association.through) === association.through) {
joinedTables[association.connectorDAO.tableName] = true; joinedTables[association.through.tableName] = true;
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.connectorDAO.tableName) joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.through.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.source.autoIncrementField) 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 += ' 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) joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField)
} else { } else {
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName) joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
...@@ -687,7 +687,7 @@ module.exports = (function() { ...@@ -687,7 +687,7 @@ module.exports = (function() {
if (Array.isArray(value)) { if (Array.isArray(value)) {
result.push(this.arrayValue(value, key, _key, dao)) 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) { if (!!value.join) {
//using as sentinel for join column => value //using as sentinel for join column => value
_value = this.quoteIdentifiers(value.join) _value = this.quoteIdentifiers(value.join)
......
var Utils = require('../../utils') var Utils = require('../../utils')
, CustomEventEmitter = require("../../emitters/custom-event-emitter") , CustomEventEmitter = require("../../emitters/custom-event-emitter")
, Dot = require('dottie') , Dot = require('dottie')
, _ = require('lodash')
module.exports = (function() { module.exports = (function() {
var AbstractQuery = function(database, sequelize, callee, options) {} var AbstractQuery = function(database, sequelize, callee, options) {}
...@@ -100,39 +101,6 @@ module.exports = (function() { ...@@ -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. * This function is a wrapper for private methods.
* *
* @param {String} fctName The name of the private method. * @param {String} fctName The name of the private method.
...@@ -167,10 +135,13 @@ module.exports = (function() { ...@@ -167,10 +135,13 @@ module.exports = (function() {
if (!this.options.include) { if (!this.options.include) {
return null return null
} }
if (!this.options.includeNames) {
var tableNames = this.options.include.map(function(include) { this.options.includeNames = this.options.include.map(function(include) {
return include.as return include.as
}).filter(function(include) { })
}
var tableNames = this.options.includeNames.filter(function(include) {
return attribute.indexOf(include + '.') === 0 return attribute.indexOf(include + '.') === 0
}) })
...@@ -181,20 +152,6 @@ module.exports = (function() { ...@@ -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 isInsertQuery = function(results, metaData) {
var result = true var result = true
...@@ -244,6 +201,7 @@ module.exports = (function() { ...@@ -244,6 +201,7 @@ module.exports = (function() {
var handleSelectQuery = function(results) { var handleSelectQuery = function(results) {
var result = null var result = null
// Raw queries
if (this.options.raw) { if (this.options.raw) {
result = results.map(function(result) { result = results.map(function(result) {
var o = {} var o = {}
...@@ -258,8 +216,22 @@ module.exports = (function() { ...@@ -258,8 +216,22 @@ module.exports = (function() {
}) })
result = result.map(Dot.transform) result = result.map(Dot.transform)
// Queries with include
} else if (this.options.hasJoin === true) { } 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) { } else if (this.options.hasJoinTableModel === true) {
result = results.map(function(result) { result = results.map(function(result) {
result = Dot.transform(result) result = Dot.transform(result)
...@@ -275,6 +247,8 @@ module.exports = (function() { ...@@ -275,6 +247,8 @@ module.exports = (function() {
return mainDao return mainDao
}.bind(this)) }.bind(this))
// Regular queries
} else { } else {
result = results.map(function(result) { result = results.map(function(result) {
return this.callee.build(result, { isNewRecord: false, isDirty: false }) return this.callee.build(result, { isNewRecord: false, isDirty: false })
...@@ -289,75 +263,6 @@ module.exports = (function() { ...@@ -289,75 +263,6 @@ module.exports = (function() {
return result 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 isShowOrDescribeQuery = function() {
var result = false var result = false
...@@ -381,15 +286,18 @@ module.exports = (function() { ...@@ -381,15 +286,18 @@ module.exports = (function() {
the associated data by the callee. the associated data by the callee.
Example: Example:
groupDataByCalleeFactory([ groupJoinData([
{ {
callee: { some: 'data', id: 1 }, some: 'data',
id: 1,
association: { foo: 'bar', id: 1 } association: { foo: 'bar', id: 1 }
}, { }, {
callee: { some: 'data', id: 1 }, some: 'data',
id: 1,
association: { foo: 'bar', id: 2 } association: { foo: 'bar', id: 2 }
}, { }, {
callee: { some: 'data', id: 1 }, some: 'data',
id: 1,
association: { foo: 'bar', id: 3 } association: { foo: 'bar', id: 3 }
} }
]) ])
...@@ -399,7 +307,8 @@ module.exports = (function() { ...@@ -399,7 +307,8 @@ module.exports = (function() {
[ [
{ {
callee: { some: 'data', id: 1 }, some: 'data',
id: 1,
association: [ association: [
{ foo: 'bar', id: 1 }, { foo: 'bar', id: 1 },
{ foo: 'bar', id: 2 }, { foo: 'bar', id: 2 },
...@@ -408,67 +317,38 @@ module.exports = (function() { ...@@ -408,67 +317,38 @@ module.exports = (function() {
} }
] ]
*/ */
var groupDataByCalleeFactory = function(data) {
var result = []
, calleeTableName = this.callee.tableName
data.forEach(function(row) { var groupJoinData = function(data) {
var calleeData = row[calleeTableName] var self = this
, existingEntry = result.filter(function(groupedRow) { , results = []
return Utils._.isEqual(groupedRow[calleeTableName], calleeData) , existingResult
})[0] , calleeData
data.forEach(function (row) {
row = Dot.transform(row)
calleeData = _.omit(row, self.options.includeNames)
if (!existingEntry) { existingResult = _.find(results, function (result) {
existingEntry = {} return Utils._.isEqual(_.omit(result, self.options.includeNames), calleeData)
result.push(existingEntry) })
existingEntry[calleeTableName] = calleeData
if (!existingResult) {
results.push(existingResult = calleeData)
} }
for (var attrName in row) { for (var attrName in row) {
if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) { if (row.hasOwnProperty(attrName) && Object(row[attrName]) === row[attrName] && self.options.includeNames.indexOf(attrName) !== -1) {
existingEntry[attrName] = existingEntry[attrName] || [] existingResult[attrName] = existingResult[attrName] || []
var attrRowExists = existingEntry[attrName].some(function(attrRow) { var attrRowExists = existingResult[attrName].some(function(attrRow) {
return Utils._.isEqual(attrRow, row[attrName]) return Utils._.isEqual(attrRow, row[attrName])
}) })
if (!attrRowExists) { if (!attrRowExists) {
existingEntry[attrName].push(row[attrName]) existingResult[attrName].push(row[attrName])
} }
} }
} }
}) })
return results
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 AbstractQuery return AbstractQuery
......
...@@ -88,6 +88,7 @@ module.exports = (function() { ...@@ -88,6 +88,7 @@ module.exports = (function() {
.on('error', function(err) { .on('error', function(err) {
errorDetected = true errorDetected = true
self.emit('sql', self.sql) self.emit('sql', self.sql)
err.sql = sql
self.emit('error', err, self.callee) self.emit('error', err, self.callee)
}) })
.on('end', function(info) { .on('end', function(info) {
......
...@@ -50,7 +50,7 @@ module.exports = (function() { ...@@ -50,7 +50,7 @@ module.exports = (function() {
for (var attr in attributes) { for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) { if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr] var dataType = this.mysqlDataTypeMapping(tableName, attr, attributes[attr])
if (Utils._.includes(dataType, 'PRIMARY KEY')) { if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr) primaryKeys.push(attr)
...@@ -117,7 +117,7 @@ module.exports = (function() { ...@@ -117,7 +117,7 @@ module.exports = (function() {
attrString.push(Utils._.template('`<%= attrName %>` <%= definition %>')({ attrString.push(Utils._.template('`<%= attrName %>` <%= definition %>')({
attrName: attrName, attrName: attrName,
definition: definition definition: this.mysqlDataTypeMapping(tableName, attrName, definition)
})) }))
} }
...@@ -496,6 +496,14 @@ module.exports = (function() { ...@@ -496,6 +496,14 @@ module.exports = (function() {
*/ */
dropForeignKeyQuery: function(tableName, foreignKey) { dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteIdentifier(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(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() { ...@@ -27,6 +27,7 @@ module.exports = (function() {
this.emit('sql', this.sql) this.emit('sql', this.sql)
if (err) { if (err) {
err.sql = sql
this.emit('error', err, this.callee) this.emit('error', err, this.callee)
} else { } else {
this.emit('success', this.formatResults(results)) this.emit('success', this.formatResults(results))
......
...@@ -27,6 +27,7 @@ module.exports = (function() { ...@@ -27,6 +27,7 @@ module.exports = (function() {
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
this.pendingQueries = 0 this.pendingQueries = 0
this.clientDrained = true
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50) this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
process.on('exit', function() { process.on('exit', function() {
...@@ -51,6 +52,7 @@ module.exports = (function() { ...@@ -51,6 +52,7 @@ module.exports = (function() {
var self = this var self = this
self.pendingQueries++ self.pendingQueries++
self.clientDrained = false
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
self.connect() self.connect()
...@@ -64,8 +66,6 @@ module.exports = (function() { ...@@ -64,8 +66,6 @@ module.exports = (function() {
.complete(function(err) { .complete(function(err) {
self.endQuery.call(self) self.endQuery.call(self)
done && done(err) }) done && done(err) })
.success(function(results) { self.endQuery.call(self) })
.error(function(err) { self.endQuery.call(self) })
.proxy(emitter) .proxy(emitter)
}) })
}).run() }).run()
...@@ -152,6 +152,12 @@ module.exports = (function() { ...@@ -152,6 +152,12 @@ module.exports = (function() {
this.client.connect(function(err, client, done) { this.client.connect(function(err, client, done) {
connectCallback(err, client || self.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() { ...@@ -168,9 +174,9 @@ module.exports = (function() {
} }
if (this.client) { if (this.client) {
// Closes a client correctly even if we have backed up queries if (this.clientDrained) {
// https://github.com/brianc/node-postgres/pull/346 this.client.end()
this.client.on('drain', this.client.end.bind(this.client)) }
this.client = null this.client = null
} }
......
...@@ -256,9 +256,12 @@ module.exports = (function() { ...@@ -256,9 +256,12 @@ module.exports = (function() {
}, },
insertQuery: function(tableName, attrValueHash, attributes) { 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) 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 // Remove serials that are null or undefined, which causes an error in PG
Utils._.forEach(attrValueHash, function(value, key, hash) { Utils._.forEach(attrValueHash, function(value, key, hash) {
if (tables[tableName]) { if (tables[tableName]) {
...@@ -284,17 +287,32 @@ module.exports = (function() { ...@@ -284,17 +287,32 @@ module.exports = (function() {
, values: rowValues.join(",") , values: rowValues.join(",")
} }
query = replacements.attributes.length ? valueQuery : emptyQuery
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
bulkInsertQuery: function(tableName, attrValueHashes) { bulkInsertQuery: function(tableName, attrValueHashes) {
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;" var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;"
, tuples = [] , 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("(" + 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) return this.escape(value)
}.bind(this)).join(",") + }.bind(this)).join(",") +
")") ")")
...@@ -435,8 +453,8 @@ module.exports = (function() { ...@@ -435,8 +453,8 @@ module.exports = (function() {
return Utils._.compact([ return Utils._.compact([
"CREATE", options.indicesType, "INDEX", this.quoteIdentifiers(options.indexName), "CREATE", options.indicesType, "INDEX", this.quoteIdentifiers(options.indexName),
(options.indexType ? ('USING ' + options.indexType) : undefined), "ON", this.quoteIdentifiers(tableName), (options.indexType ? ('USING ' + options.indexType) : undefined),
"ON", this.quoteIdentifiers(tableName), '(' + transformedAttributes.join(', ') + ')' '(' + transformedAttributes.join(', ') + ')'
]).join(' ') ]).join(' ')
}, },
......
...@@ -36,6 +36,7 @@ module.exports = (function() { ...@@ -36,6 +36,7 @@ module.exports = (function() {
query.on('error', function(err) { query.on('error', function(err) {
receivedError = true receivedError = true
err.sql = sql
self.emit('error', err, self.callee) self.emit('error', err, self.callee)
}) })
......
...@@ -156,7 +156,9 @@ module.exports = (function() { ...@@ -156,7 +156,9 @@ module.exports = (function() {
insertQuery: function(tableName, attrValueHash) { insertQuery: function(tableName, attrValueHash) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) 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 = { var replacements = {
table: this.quoteIdentifier(tableName), table: this.quoteIdentifier(tableName),
...@@ -166,6 +168,7 @@ module.exports = (function() { ...@@ -166,6 +168,7 @@ module.exports = (function() {
}.bind(this)).join(",") }.bind(this)).join(",")
} }
query = replacements.attributes.length ? valueQuery : emptyQuery
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
......
...@@ -44,6 +44,7 @@ module.exports = (function() { ...@@ -44,6 +44,7 @@ module.exports = (function() {
self.emit('sql', self.sql) self.emit('sql', self.sql)
if (err) { if (err) {
err.sql = self.sql
onFailure.call(self, err) onFailure.call(self, err)
} else { } else {
this.columnTypes = columnTypes this.columnTypes = columnTypes
......
...@@ -28,6 +28,15 @@ module.exports = (function() { ...@@ -28,6 +28,15 @@ module.exports = (function() {
return this 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.success =
CustomEventEmitter.prototype.ok = CustomEventEmitter.prototype.ok =
function(fct) { function(fct) {
...@@ -35,6 +44,15 @@ module.exports = (function() { ...@@ -35,6 +44,15 @@ module.exports = (function() {
return this 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.failure =
CustomEventEmitter.prototype.fail = CustomEventEmitter.prototype.fail =
CustomEventEmitter.prototype.error = CustomEventEmitter.prototype.error =
......
...@@ -660,6 +660,7 @@ module.exports = (function() { ...@@ -660,6 +660,7 @@ module.exports = (function() {
var sql = this.QueryGenerator.selectQuery(tableName, options, factory) var sql = this.QueryGenerator.selectQuery(tableName, options, factory)
queryOptions = Utils._.extend({}, queryOptions, { include: options.include }) queryOptions = Utils._.extend({}, queryOptions, { include: options.include })
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select') return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
} }
......
...@@ -79,7 +79,7 @@ module.exports = (function() { ...@@ -79,7 +79,7 @@ module.exports = (function() {
define: {}, define: {},
query: {}, query: {},
sync: {}, sync: {},
logging: console.log, logging: false,
omitNull: false, omitNull: false,
queue: true, queue: true,
native: false, native: false,
......
Utils = require('./utils') var Utils = require('./utils')
var TransactionManager = module.exports = function(sequelize) { var TransactionManager = module.exports = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
......
var cJSON = require('circular-json') var cJSON = require('circular-json')
, _ = require('lodash') // Don't require Utils here, as it creates a circular dependency
var ParameterValidator = module.exports = { var ParameterValidator = module.exports = {
check: function(value, expectation, options) { check: function(value, expectation, options) {
options = Utils._.extend({ options = _.extend({
throwError: true, throwError: true,
deprecated: false, deprecated: false,
deprecationWarning: generateDeprecationWarning(value, expectation, options), deprecationWarning: generateDeprecationWarning(value, expectation, options),
......
...@@ -64,7 +64,8 @@ ...@@ -64,7 +64,8 @@
"chai-spies": "~0.5.1", "chai-spies": "~0.5.1",
"lcov-result-merger": "0.0.2", "lcov-result-merger": "0.0.2",
"istanbul": "~0.1.45", "istanbul": "~0.1.45",
"coveralls": "~2.5.0" "coveralls": "~2.5.0",
"async": "~0.2.9"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
......
...@@ -16,7 +16,7 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() { ...@@ -16,7 +16,7 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' }) Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' }) 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() { ...@@ -676,10 +676,8 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
{ tableName: 'tasks' } { tableName: 'tasks' }
) )
this.User.hasMany(this.Task, this.User.hasMany(this.Task, { joinTableName: 'user_has_tasks' })
{ joinTableName: 'user_has_tasks' } this.Task.hasMany(this.User, { joinTableName: 'user_has_tasks' })
)
this.Task.hasMany(this.User)
this.User.sync({ force: true }).success(function() { this.User.sync({ force: true }).success(function() {
self.Task.sync({force: true}).success(function() { self.Task.sync({force: true}).success(function() {
...@@ -693,16 +691,18 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -693,16 +691,18 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
expect(associationName).not.to.equal(this.User.tableName) expect(associationName).not.to.equal(this.User.tableName)
expect(associationName).not.to.equal(this.Task.tableName) expect(associationName).not.to.equal(this.Task.tableName)
var joinTableName = this.User.associations[associationName].options.joinTableName var through = this.User.associations[associationName].through
if (typeof joinTableName !== 'undefined') { if (typeof through !== 'undefined') {
expect(joinTableName).to.equal(associationName) expect(through.tableName).to.equal(associationName)
} }
var tableName = this.User.associations[associationName].options.tableName var tableName = this.User.associations[associationName].options.tableName
if (typeof tableName !== 'undefined') { if (typeof tableName !== 'undefined') {
expect(tableName).to.equal(associationName) expect(tableName).to.equal(associationName)
} }
} }
setTimeout(function () {
done() done()
}, 50)
}) })
}) })
...@@ -766,8 +766,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -766,8 +766,6 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
describe('inserting in join table', function () { describe('inserting in join table', function () {
describe('add', function () { describe('add', function () {
it('should insert data provided on the object into the join table', function (done) { it('should insert data provided on the object into the join table', function (done) {
var self = this var self = this
...@@ -800,6 +798,32 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -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 () { describe('set', function () {
...@@ -832,6 +856,68 @@ describe(Support.getTestDialectTeaser("HasMany"), 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() { ...@@ -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() { describe("Foreign key constraints", function() {
......
...@@ -16,7 +16,7 @@ describe(Support.getTestDialectTeaser("HasOne"), function() { ...@@ -16,7 +16,7 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
Group.hasOne(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' }) Group.hasOne(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.hasOne(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' }) 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') ...@@ -11,6 +11,7 @@ var chai = require('chai')
, datetime = require('chai-datetime') , datetime = require('chai-datetime')
, _ = require('lodash') , _ = require('lodash')
, moment = require('moment') , moment = require('moment')
, async = require('async')
chai.use(datetime) chai.use(datetime)
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
...@@ -227,6 +228,24 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -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() { describe('build', function() {
...@@ -349,6 +368,101 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -349,6 +368,101 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(p.price2).to.equal(20) expect(p.price2).to.equal(20)
done() 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() { describe('findOrInitialize', function() {
...@@ -429,105 +543,20 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -429,105 +543,20 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
describe('findOrCreate', 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, 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) { it('supports transactions', function(done) {
var self = this
Support.prepareTransactionTest(this.sequelize, function(sequelize) { 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.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function() {
sequelize.transaction(function(t) { sequelize.transaction(function(t) {
User.create({ username: 'user' }, { transaction: t }).success(function() { User.update({ username: 'bar' }, {}, { transaction: t }).success(function() {
User.count().success(function(count) { User.all().success(function(users1) {
expect(count).to.equal(0) User.all({ transaction: t }).success(function(users2) {
t.commit().success(function() { expect(users1[0].username).to.equal('foo')
User.count().success(function(count) { expect(users2[0].username).to.equal('bar')
expect(count).to.equal(1) t.rollback().success(function(){ done() })
done()
})
}) })
}) })
}) })
...@@ -535,210 +564,67 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -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({ it('updates the attributes that we select only without updating createdAt', function(done) {
intVal: this.sequelize.cast('1', type) var User = this.sequelize.define('User1', {
}).on('sql', function (sql) { username: Sequelize.STRING,
expect(sql).to.match(new RegExp('CAST\\(1 AS ' + type.toUpperCase() + '\\)')) secretValue: Sequelize.STRING
_done() }, {
}) paranoid:true
.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) { User.sync({ force: true }).success(function() {
var self = this User.create({username: 'Peter', secretValue: '42'}).success(function(user) {
, type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer') user.updateAttributes({ secretValue: '43' }, ['secretValue']).on('sql', function(sql) {
, _done = _.after(2, function() { expect(sql).to.match(/UPDATE\s+[`"]+User1s[`"]+\s+SET\s+[`"]+secretValue[`"]='43',[`"]+updatedAt[`"]+='[^`",]+'\s+WHERE [`"]+id[`"]+=1/)
done() 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) { it('allows sql logging of updated statements', function(done) {
var self = this var User = this.sequelize.define('User', {
name: Sequelize.STRING,
this.User.create({ bio: Sequelize.TEXT
intVal: this.sequelize.literal('CAST(1-2 AS ' + (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER') + ')') }, {
}).success(function (user) { paranoid:true
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) { User.sync({ force: true }).success(function() {
var self = this User.create({ name: 'meg', bio: 'none' }).success(function(u) {
expect(u).to.exist
this.User.create({ expect(u).not.to.be.null
intVal: this.sequelize.literal(this.sequelize.cast('1-2', (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER'))) u.updateAttributes({name: 'brian'}).on('sql', function(sql) {
}).success(function (user) { expect(sql).to.exist
self.User.find(user.id).success(function (user) { expect(sql.toUpperCase().indexOf("UPDATE")).to.be.above(-1)
expect(user.intVal).to.equal(-1)
done() 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 var self = this
, userWithDefaults , data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
if (dialect.indexOf('postgres') === 0) { { username: 'Bob', secretValue: '43' }]
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 () { this.User.bulkCreate(data).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 () { self.User.update({username: 'Bill'}, {secretValue: '42'})
userWithDefaults.create({}).success(function (user) { .success(function() {
userWithDefaults.find(user.id).success(function (user) { self.User.findAll({order: 'id'}).success(function(users) {
var now = new Date() expect(users.length).to.equal(3)
, 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())) users.forEach(function (user) {
done() if (user.secretValue == '42') {
}) expect(user.username).to.equal("Bill")
})
})
} else { } else {
// functions as default values are not supported in mysql, see http://stackoverflow.com/a/270338/800016 expect(user.username).to.equal("Bob")
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() done()
}) })
...@@ -746,95 +632,52 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -746,95 +632,52 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it("raises an error if created object breaks definition contraints", function(done) { it('updates with casting', function (done) {
var UserNull = this.sequelize.define('UserWithNonNullSmth', { var self = this
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.*/)
}
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() 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 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() { this.User.create({
self.sequelize.define('UserBadDataType', { username: 'John'
activity_date: {type: Sequelize.DATe} }).success(function(user) {
}) self.User.update({username: self.sequelize.fn('upper', self.sequelize.col('username'))}, {username: 'John'}).success(function () {
}).to.throw(Error, 'Unrecognized data type for field activity_date') self.User.all().success(function(users) {
expect(users[0].username).to.equal('JOHN')
done() 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) { it('sets updatedAt to the current timestamp', function(done) {
var User = this.sequelize.define('UserWithAutoIncrementField', { var self = this
userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false } , data = [{ username: 'Peter', secretValue: '42' },
}) { username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
User.sync({ force: true }).success(function() { this.User.bulkCreate(data).success(function() {
User.create({}).on('success', function(user) { self.User.update({username: 'Bill'}, {secretValue: '42'}).success(function() {
expect(user.userid).to.equal(1) self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(3)
User.create({}).on('success', function(user) { expect(users[0].username).to.equal("Bill")
expect(user.userid).to.equal(2) expect(users[1].username).to.equal("Bill")
done() expect(users[2].username).to.equal("Bob")
})
})
})
})
<<<<<<< HEAD
it('allows the usage of options as attribute', function(done) { it('allows the usage of options as attribute', function(done) {
var User = this.sequelize.define('UserWithNameAndOptions', { var User = this.sequelize.define('UserWithNameAndOptions', {
name: Sequelize.STRING, name: Sequelize.STRING,
...@@ -3264,33 +3107,19 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -3264,33 +3107,19 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
describe('findAndCountAll', function() { describe('destroy', 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) { it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) { Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING }) var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function() {
sequelize.transaction(function(t) { sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() { User.destroy({}, { transaction: t }).success(function() {
User.count().success(function(count1) {
User.findAndCountAll().success(function(info1) { User.count({ transaction: t }).success(function(count2) {
User.findAndCountAll({ transaction: t }).success(function(info2) { expect(count1).to.equal(1)
expect(info1.count).to.equal(0) expect(count2).to.equal(0)
expect(info2.count).to.equal(1)
t.rollback().success(function(){ done() }) t.rollback().success(function(){ done() })
}) })
}) })
...@@ -3299,86 +3128,134 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -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) { if (dialect === "sqlite") {
this.User.findAndCountAll({where: "id != " + this.users[0].id}).success(function(info) { expect(moment(users[0].deletedAt).format('YYYY-MM-DD h:mm')).to.equal(date)
expect(info.count).to.equal(2) expect(moment(users[1].deletedAt).format('YYYY-MM-DD h:mm')).to.equal(date)
expect(Array.isArray(info.rows)).to.be.ok } else {
expect(info.rows.length).to.equal(2) 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() 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) { describe("can't find records marked as deleted with paranoid being true", function() {
this.User.findAndCountAll({limit: 1}).success(function(info) { it('with the DAOFactory', function(done) {
expect(info.count).to.equal(3) var User = this.sequelize.define('UserCol', {
expect(Array.isArray(info.rows)).to.be.ok username: Sequelize.STRING
expect(info.rows.length).to.equal(1) }, { 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() 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) { it('should delete a paranoid record if I set force to true', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) { var self = this
var User = sequelize.define('User', { username: Sequelize.STRING }) var User = this.sequelize.define('paranoiduser', {
username: Sequelize.STRING
}, { paranoid: true })
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) { User.bulkCreate([
User.create({ username: 'foo' }, { transaction: t }).success(function() { {username: 'Bob'},
User.all().success(function(users1) { {username: 'Tobi'},
User.all({ transaction: t }).success(function(users2) { {username: 'Max'},
expect(users1.length).to.equal(0) {username: 'Tony'}
expect(users2.length).to.equal(1) ]).success(function() {
t.rollback().success(function(){ done() }) 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 () { ...@@ -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 () { ...@@ -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() { describe('schematic support', function() {
beforeEach(function(done){ beforeEach(function(done){
var self = this; 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 () { ...@@ -944,7 +944,9 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
user.age = user.age + 1 // happy birthday joe 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.username).to.equal('joe')
expect(user.age).to.equal(2) expect(user.age).to.equal(2)
expect(user.projects).to.exist expect(user.projects).to.exist
...@@ -1078,7 +1080,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -1078,7 +1080,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
it('returns a response that can be stringified', function(done) { it('returns a response that can be stringified', function(done) {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true }) 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() done()
}) })
......
...@@ -25,7 +25,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -25,7 +25,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
[Sequelize.TEXT, 'TEXT', 'TEXT'], [Sequelize.TEXT, 'TEXT', 'TEXT'],
[Sequelize.DATE, 'DATE', 'DATETIME'], [Sequelize.DATE, 'DATE', 'DATETIME'],
[Sequelize.NOW, 'NOW', 'NOW'], [Sequelize.NOW, 'NOW', 'NOW'],
[Sequelize.UUID, 'UUID', 'CHAR(36)'], [Sequelize.UUID, 'UUID', 'UUID'],
[Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'], [Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'],
[Sequelize.BLOB, 'BLOB', 'BLOB'], [Sequelize.BLOB, 'BLOB', 'BLOB'],
......
...@@ -6,12 +6,12 @@ var chai = require('chai') ...@@ -6,12 +6,12 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Language Util"), function() { describe(Support.getTestDialectTeaser("Language Util"), function() {
before(function(done) { beforeEach(function(done) {
this.sequelize.options.language = 'es' this.sequelize.options.language = 'es'
done() done()
}) })
after(function(done) { afterEach(function(done) {
this.sequelize.options.language = 'en' this.sequelize.options.language = 'en'
done() done()
}) })
...@@ -37,8 +37,8 @@ describe(Support.getTestDialectTeaser("Language Util"), function() { ...@@ -37,8 +37,8 @@ describe(Support.getTestDialectTeaser("Language Util"), function() {
table2.belongsTo(table) table2.belongsTo(table)
table3.hasMany(table2) table3.hasMany(table2)
expect(table.associations.androides.identifier).to.equal('arbolId') expect(table.associations.androide.identifier).to.equal('arbolId')
expect(table2.associations.arboles).to.exist expect(table2.associations.arbol).to.exist
expect(table3.associations.androideshombres).to.exist expect(table3.associations.androideshombres).to.exist
done() done()
}) })
......
...@@ -268,6 +268,11 @@ if (Support.dialectIsMySQL()) { ...@@ -268,6 +268,11 @@ if (Support.dialectIsMySQL()) {
arguments: ['myTable', {where: null}], arguments: ['myTable', {where: null}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;", expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator 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/)) { ...@@ -38,12 +38,16 @@ if (dialect.match(/^postgres/)) {
it("should not use a combined name", function(done) { it("should not use a combined name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist
setTimeout(function () {
done() done()
}, 50)
}) })
it("should use the specified name", function(done) { it("should use the specified name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist
setTimeout(function () {
done() done()
}, 50)
}) })
}) })
}) })
......
...@@ -333,6 +333,11 @@ if (dialect.match(/^postgres/)) { ...@@ -333,6 +333,11 @@ if (dialect.match(/^postgres/)) {
}, { }, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}], arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"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 // Variants when quoteIdentifiers is false
...@@ -410,6 +415,10 @@ if (dialect.match(/^postgres/)) { ...@@ -410,6 +415,10 @@ if (dialect.match(/^postgres/)) {
insertQuery: [ insertQuery: [
{ {
arguments: ['myTable', {}],
expectation: "INSERT INTO \"myTable\" DEFAULT VALUES RETURNING *;"
},
{
arguments: ['myTable', {name: 'foo'}], arguments: ['myTable', {name: 'foo'}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;"
}, { }, {
......
...@@ -255,6 +255,11 @@ if (dialect === 'sqlite') { ...@@ -255,6 +255,11 @@ if (dialect === 'sqlite') {
arguments: ['myTable', {where: null}], arguments: ['myTable', {where: null}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;", expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator 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 = { ...@@ -48,6 +48,7 @@ var Support = {
options.pool = options.pool || config.pool options.pool = options.pool || config.pool
var sequelizeOptions = { var sequelizeOptions = {
host: options.host || config.host,
logging: options.logging, logging: options.logging,
dialect: options.dialect, dialect: options.dialect,
port: options.port || process.env.SEQ_PORT || config.port, port: options.port || process.env.SEQ_PORT || config.port,
...@@ -55,10 +56,6 @@ var Support = { ...@@ -55,10 +56,6 @@ var Support = {
dialectOptions: options.dialectOptions || {} dialectOptions: options.dialectOptions || {}
} }
if (!!options.host) {
sequelizeOptions.host = options.host
}
if (!!options.define) { if (!!options.define) {
sequelizeOptions.define = 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!