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

Commit 5703c754 by Meg Sharkey

merged

2 parents d4d1b11f 370c22d7
Showing with 5844 additions and 3028 deletions
test*.js test*.js
.idea .idea
.DS_STORE .DS_STORE
node_modules node_modules
\ No newline at end of file config
...@@ -4,10 +4,17 @@ before_script: ...@@ -4,10 +4,17 @@ before_script:
script: script:
- "node_modules/.bin/jasmine-node spec" - "node_modules/.bin/jasmine-node spec"
- "node_modules/.bin/expresso -s test/**/*"
notifications: notifications:
- sascha@depold.com email:
- sascha@depold.com
env: env:
- DB=mysql - DB=mysql
language: node_js
node_js:
- 0.4
- 0.5
- 0.6
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
The Sequelize library provides easy access to a MySQL database by mapping database entries to objects and vice versa. To put it in a nutshell... it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment. The Sequelize library provides easy access to a MySQL database by mapping database entries to objects and vice versa. To put it in a nutshell... it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment.
## Blogposts/Changes ##
- [v1.3.0](http://blog.depold.com/post/15283366633/changes-in-sequelize-1-3-0): migrations, cross-database, validations, new listener notation, ...
- [v1.2.1](http://blog.depold.com/post/12319530694/changes-in-sequelize-1-2-1): changes some defaults and some interfaces
- [v1.0.0](http://blog.depold.com/post/5936116582/changes-in-sequelize-1-0-0): complete rewrite
## Features ## ## Features ##
- Schema definition - Schema definition
...@@ -16,7 +21,7 @@ The Sequelize library provides easy access to a MySQL database by mapping databa ...@@ -16,7 +21,7 @@ The Sequelize library provides easy access to a MySQL database by mapping databa
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://www.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. For a (more or less) complete overview of changes in 1.0.0. take a look at [this blogpost](http://blog.depold.com/post/5936116582/changes-in-sequelize-1-0-0). Also make sure to take a look at the examples in the repository. The website will contain them soon, as well.
## Collaboration ## ## Collaboration ##
...@@ -24,10 +29,8 @@ I'm glad to get pull request if any functionality is missing or something is bug ...@@ -24,10 +29,8 @@ I'm glad to get pull request if any functionality is missing or something is bug
## Tests ## ## Tests ##
In order to run the tests, just do ```npm install```, which will install expresso and jasmine. I am switching from In order to run the tests, just do ```npm install```, which will install jasmine. Please add tests to your pull requests. This is how you start the tests:
expresso to jasmine, so please add according tests to your pull requests. This is how you start the tests:
node_modules/.bin/expresso -s test/**/*
node_modules/.bin/jasmine-node spec/ node_modules/.bin/jasmine-node spec/
Current build status on travis-ci: [![Build Status](https://secure.travis-ci.org/sdepold/sequelize.png)](http://travis-ci.org/sdepold/sequelize) Current build status on travis-ci: [![Build Status](https://secure.travis-ci.org/sdepold/sequelize.png)](http://travis-ci.org/sdepold/sequelize)
#!/usr/bin/env node
const path = require("path")
, fs = require("fs")
, program = require("commander")
, Sequelize = require(__dirname + '/../index')
, _ = Sequelize.Utils._
var configPath = process.cwd() + '/config'
, migrationsPath = process.cwd() + '/migrations'
, configFile = configPath + '/config.json'
, configPathExists = path.existsSync(configPath)
, configFileExists = path.existsSync(configFile)
var writeConfig = function(config) {
!configPathExists && fs.mkdirSync(configPath)
config = JSON.stringify(config)
config = config.replace('{', '{\n ')
config = config.replace(/,/g, ",\n ")
config = config.replace('}', "\n}")
fs.writeFileSync(configFile, config)
}
var createMigrationsFolder = function(force) {
if(force) {
console.log('Deleting the migrations folder.')
try {
fs.readdirSync(migrationsPath).forEach(function(filename) {
fs.unlinkSync(migrationsPath + '/' + filename)
})
} catch(e) {}
try {
fs.rmdirSync(migrationsPath)
console.log('Successfully deleted the migrations folder.')
} catch(e) {}
}
console.log('Creating migrations folder.')
try {
fs.mkdirSync(migrationsPath)
console.log('Successfully create migrations folder.')
} catch(e) {
console.log('Migrations folder already exist.')
}
}
var readConfig = function() {
try {
return JSON.parse(fs.readFileSync(configFile))
} catch(e) {
throw new Error('The config.json is not available or contains invalid JSON.')
}
}
program
.version('1.3.0')
.option('-i, --init', 'Initializes the project. Creates a config/config.json')
.option('-m, --migrate', 'Runs undone migrations')
.option('-u, --undo', 'Redo the last migration.')
.option('-f, --force', 'Forces the action to be done.')
.parse(process.argv)
if(program.migrate) {
if(configFileExists) {
var config = readConfig()
, options = {}
_.each(config, function(value, key) {
if(['database', 'username', 'password'].indexOf(key) == -1) {
options[key] = value
}
})
options = _.extend(options, { logging: false })
var sequelize = new Sequelize(config.database, config.username, config.password, options)
, migratorOptions = { path: __dirname + '/../migrations' }
, migrator = sequelize.getMigrator(migratorOptions)
if(program.undo) {
sequelize.migrator.findOrCreateSequelizeMetaModel().success(function(Meta) {
Meta.find({ order: 'id DESC' }).success(function(meta) {
if(meta) {
migrator = sequelize.getMigrator(_.extend(migratorOptions, meta), true)
}
migrator.migrate({ method: 'down' })
})
})
} else {
sequelize.migrate()
}
} else {
throw new Error('Please add a configuration file under config/config.json. You might run "sequelize --init".')
}
} else if(program.init) {
if(!configFileExists || !!program.force) {
writeConfig({
username: "root",
password: null,
database: 'database',
host: '127.0.0.1'
})
console.log('Successfully created config.json')
} else {
console.log('A config.json already exists. Run "sequelize --init --force" to overwrite it.')
}
createMigrationsFolder(program.force)
} else {
console.log('Please define any params!')
}
# v0.1.0 # # v1.3.1 #
- first stable version - [REFACTORING] renamed ModelManager to ModelFactoryManager
- implemented all basic functions
- associations are working # v1.3.0 #
- [REFACTORING] Model#all is now a function and not a getter.
- [REFACTORING] Renamed ModelDefinition to ModelFactory
- [REFACTORING] Private method scoping; Attributes are still public
- [REFACTORING] Use the new util module for node 0.6.2
- [FEATURE] QueryChainer can now run serially
- [FEATURE] Association definition is chainable: Person.hasOne(House).hasMany(Address)
- [FEATURE] Validations (Thanks to [hiddentao](https://github.com/hiddentao))
- [FEATURE] jQuery-like event listeners: .success(callback) and .error(callback)
- [FEATURE] aliasing for select queries: Model.find({ where: 'id = 1', attributes: ['id', ['name', 'username']] }) ==> will return the user's name as username
- [FEATURE] cross-database support. currently supported: mysql, sqlite
- [FEATURE] migrations
- [TEST] removed all expresso tests and converted them to jasmine
# v0.2.0 # # v1.2.1 #
- added methods for setting associations - [REFACTORING] renamed the global options for sync, query and define on sequelize; before: options.queryOptions; now: options.query
- added method for chaining an arbitraty amount of queries - [FEATURE] allow definition of charset via global define option in sequelize or via charset option in sequelize.define
- [FEATURE] allow definition of mysql engine via global define option in sequelize or via engine option in sequelize.define; default is InnoDB now
- [FEATURE] find and findAll will now search in a list of values via: Model.findAll({where: { id: [1,2,3] }}); will return all models with id 1, 2 and 3
- [TEST] force latin1 charset for travis
# v0.2.1 # # v1.2.0 #
- fixed date bug - [FEATURE] min/max function for models, which return the min/max value in a column
- [FEATURE] getModel for modelManager for getting a model without storing it in a variable; use it via sequelize.modelManager.getModel('User')
- [TEST] test suite refactoring for jasmine
# v0.2.2 # # v1.1.4 #
- released project as npm package - [BUG] tables with identical prefix (e.g. wp_) can now be used in many-to-many associations
# v0.2.3 # # v1.1.3 #
- added latest mysql connection library - [BUG] scoped options in model => a model can now have the attribute options
- fixed id handling on save - [FEATURE] added drop method for sequelize, that drops all currently registered tables
- fixed text handling (varchar > 255; text)
- using the inflection library for naming tables more convenient
- Sequelize.TEXT is now using MySQL datatype TEXT instead of varchar(4000)
# v0.2.4 # # v1.1.2 #
- fixed bug when using cross associated tables (many to many associations) - [BUG] prevent malfunction after being idle
# v0.2.5 # # v1.1.1 #
- added BOOLEAN type - [BUG] fixed memory leaks
- added FLOAT type - [FEATURE] added query queueing (adjustable via maxConcurrentQueries in config; default: 50)
- fixed DATE type issue
- fixed npm package
# v0.2.6 # # v1.1.0 #
- refactored Sequelize to fit CommonJS module conventions - [BUG] defaultValue 0 is now working
- [REMOVED] mysql-pool usage (will give it a new try later)
- [CHORE] updated node-mysql to 0.9.4
# v0.3.0 # # v1.0.2 #
- added possibility to define class and instance methods for models - [BUG] Fixed where clause generation for models with explicit primary keys (allanca)
- added import method for loading model definition from a file - [BUG] Set insertId for non-default auto increment fields (allanca)
# v0.4.0 # # v1.0.1 #
- added error handling when defining invalid database credentials - [FEATURE] Added Model.count(callback), which returns the number of elements saved in the database
- Sequelize#sync, Sequelize#drop, model#sync, model#drop returns errors via callback - [BUG] Fixed self associations
- code is now located under lib/sequelize to use it with nDistro
- added possibility to use non default mysql database (host/port) # v1.0.0 #
- added error handling when defining invalid database port/host - complete rewrite
- schema definitions can now contain default values and null allowance - added new emitter syntax
- database credentials can now also contain an empty / no password - sql injection protection
- select now supports hash usage of where
- select now supports array usage of where
- added a lot of options to find/findAll
- Wrapped queries correctly using `foo`
- using expresso 0.7.2
- moved config for test database into seperated config file
- Added method for adding and deleting single associations
# v0.4.3 #
- renamed loadAssociatedData to fetchAssociations
- renamed Model#associatedData to fetchedAssociations
- added fetchAssociations to finder methods
- store data found by finder method in the associatedData hash + grep them from there if reload is not forced
- added option to sequelize constructor for disabling the pluralization of tablenames: disableTableNameModification
- allow array as value for chainQueries => Sequelize.chainQueries([save: [a,b,c]], callback)
- remove the usage of an array => Sequelize.chainQueries({save: a}, {destroy: b}, callback)
# v0.4.2 #
- fixed bugs from 0.4.1
- added the model instance method loadAssociatedData which adds the hash Model#associatedData to an instance which contains all associated data
# v0.4.1 # # v0.4.1 #
- THIS UPDATE CHANGES TABLE STRUCTURES MASSIVELY! - THIS UPDATE CHANGES TABLE STRUCTURES MASSIVELY!
...@@ -56,66 +90,50 @@ ...@@ -56,66 +90,50 @@
- added hasOneAndBelongsTo - added hasOneAndBelongsTo
- nodejs-mysql-native 0.4.2 - nodejs-mysql-native 0.4.2
# v0.4.2 # # v0.4.0 #
- fixed bugs from 0.4.1 - added error handling when defining invalid database credentials
- added the model instance method loadAssociatedData which adds the hash Model#associatedData to an instance which contains all associated data - Sequelize#sync, Sequelize#drop, model#sync, model#drop returns errors via callback
- code is now located under lib/sequelize to use it with nDistro
# v0.4.3 # - added possibility to use non default mysql database (host/port)
- renamed loadAssociatedData to fetchAssociations - added error handling when defining invalid database port/host
- renamed Model#associatedData to fetchedAssociations - schema definitions can now contain default values and null allowance
- added fetchAssociations to finder methods - database credentials can now also contain an empty / no password
- store data found by finder method in the associatedData hash + grep them from there if reload is not forced
- added option to sequelize constructor for disabling the pluralization of tablenames: disableTableNameModification
- allow array as value for chainQueries => Sequelize.chainQueries([save: [a,b,c]], callback)
- remove the usage of an array => Sequelize.chainQueries({save: a}, {destroy: b}, callback)
# v1.0.0 # # v0.3.0 #
- complete rewrite - added possibility to define class and instance methods for models
- added new emitter syntax - added import method for loading model definition from a file
- sql injection protection
- select now supports hash usage of where
- select now supports array usage of where
- added a lot of options to find/findAll
- Wrapped queries correctly using `foo`
- using expresso 0.7.2
- moved config for test database into seperated config file
- Added method for adding and deleting single associations
# v1.0.1 # # v0.2.6 #
- Added Model.count(callback), which returns the number of elements saved in the database - refactored Sequelize to fit CommonJS module conventions
- Fixed self associations
# v1.0.2 # # v0.2.5 #
- Fixed where clause generation for models with explicit primary keys (allanca) - added BOOLEAN type
- Set insertId for non-default auto increment fields (allanca) - added FLOAT type
- fixed DATE type issue
- fixed npm package
# v1.1.0 # # v0.2.4 #
- defaultValue 0 is now working - fixed bug when using cross associated tables (many to many associations)
- REVERTED mysql-pool usage (will give it a new try later)
- updated node-mysql to 0.9.4
# v1.1.1 # # v0.2.3 #
- fixed memory leaks - added latest mysql connection library
- added query queueing (adjustable via maxConcurrentQueries in config; default: 50) - fixed id handling on save
- fixed text handling (varchar > 255; text)
- using the inflection library for naming tables more convenient
- Sequelize.TEXT is now using MySQL datatype TEXT instead of varchar(4000)
# v1.1.2 # # v0.2.2 #
- prevent malfunction after being idle - released project as npm package
# v1.1.3 # # v0.2.1 #
- [BUG] scoped options in model => a model can now have the attribute options - fixed date bug
- [FEATURE] added drop method for sequelize, that drops all currently registered tables
# v1.1.4 # # v0.2.0 #
- [BUG] tables with identical prefix (e.g. wp_) can now be used in many-to-many associations - added methods for setting associations
- added method for chaining an arbitraty amount of queries
# v1.2.0 # # v0.1.0 #
- [FEATURE] min/max function for models, which return the min/max value in a column - first stable version
- [FEATURE] getModel for modelManager for getting a model without storing it in a variable; use it via sequelize.modelManager.getModel('User') - implemented all basic functions
- [TEST] test suite refactoring for jasmine - associations are working
# v1.2.1 #
- [REFACTORING] renamed the global options for sync, query and define on sequelize; before: options.queryOptions; now: options.query
- [FEATURE] allow definition of charset via global define option in sequelize or via charset option in sequelize.define
- [FEATURE] allow definition of mysql engine via global define option in sequelize or via engine option in sequelize.define; default is InnoDB now
- [FEATURE] find and findAll will now search in a list of values via: Model.findAll({where: { id: [1,2,3] }}); will return all models with id 1, 2 and 3
- [TEST] force latin1 charset for travis
var Sequelize = require(__dirname + "/../../lib/sequelize/Sequelize").Sequelize,
sequelize = new Sequelize("sequelize_test", "root", null, {disableLogging: false})
var Person = sequelize.define('person', {
name: Sequelize.STRING
})
var Pet = sequelize.define('pet', {
name: Sequelize.STRING
})
Person.hasManyAndBelongsTo('pets', Pet, 'owner')
Sequelize.chainQueries([{drop: sequelize}, {sync: sequelize}], function() {
var person = new Person({ name: 'Luke' }),
pet1 = new Pet({ name: 'Bob' }),
pet2 = new Pet({ name: 'Aaron' })
Sequelize.chainQueries([{save: person}, {save: pet1}, {save: pet2}], function() {
person.setPets([pet1], function(pets) {
Sequelize.Helper.log('my pet: ' + pets[0].name )
Sequelize.Helper.log("Now let's get the same data with fetchData!")
person.fetchAssociations(function(data) {
Sequelize.Helper.log("And here we are: " + data.pets[0].name)
Sequelize.Helper.log("The object should now also contain the data: " + person.fetchedAssociations.pets[0].name)
Sequelize.Helper.log('This won\'t do a database request!')
person.getPets(function(pets) {
Sequelize.Helper.log("Pets: " + pets.map(function(pet) { return pet.name }).join(", "))
Sequelize.Helper.log("Let's associate with another pet...")
person.setPets([pet1, pet2], function() {
Sequelize.Helper.log("The set call has stored the pets as associated data!")
Sequelize.Helper.log("And now let's find the pets again! This will make no new database request but serve the already stored pets Bob and Aaron!")
person.getPets(function(pets) {
Sequelize.Helper.log("Pets: " + pets.map(function(pet) { return pet.name }).join(", "))
Sequelize.Helper.log("Now let's force the reloading of pets!")
person.getPets({refetchAssociations: true}, function(pets) {
Sequelize.Helper.log("Pets: " + pets.map(function(pet) { return pet.name }).join(", "))
Person.find(person.id, { fetchAssociations: true }, function(p) {
var petNames = p.fetchedAssociations.pets.map(function(pet) { return pet.name }).join(", ")
Sequelize.Helper.log('Works with find as well: ' + petNames)
})
Person.findAll({ fetchAssociations: true }, function(people) {
var petNames = people[0].fetchedAssociations.pets.map(function(pet) { return pet.name }).join(", ")
Sequelize.Helper.log('And also with findAll: ' + petNames)
})
})
})
})
})
})
})
})
})
\ No newline at end of file
module.exports = require("./lib/sequelize/sequelize") module.exports = require("./lib/sequelize")
\ No newline at end of file
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
var BelongsTo = module.exports = function(srcModel, targetModel, options) { module.exports = (function() {
this.source = srcModel var BelongsTo = function(srcModel, targetModel, options) {
this.target = targetModel this.source = srcModel
this.options = options this.target = targetModel
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.options = options
this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if(this.isSelfAssociation && !this.options.foreignKey && !!this.options.as)
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.source.options.underscored) if(this.isSelfAssociation && !this.options.foreignKey && !!this.options.as)
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.source.options.underscored)
this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) this.associationAccessor = this.isSelfAssociation
: this.target.tableName ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
} : this.target.tableName
// the id is in the source table
BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {}
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName) + "Id", this.source.options.underscored)
newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.source.attributes, Utils.simplifyAttributes(newAttributes))
return this
}
BelongsTo.prototype.injectGetter = function(obj) {
var self = this
, accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName)))
obj[accessor] = function() {
var id = obj[self.identifier]
return self.target.find(id)
} }
return this // the id is in the source table
} BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {}
BelongsTo.prototype.injectSetter = function(obj) {
var self = this this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName) + "Id", this.source.options.underscored)
, accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName))) newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.source.rawAttributes, newAttributes)
obj[accessor] = function(associatedObject) { return this
obj[self.identifier] = associatedObject ? associatedObject.id : null
return obj.save()
} }
return this BelongsTo.prototype.injectGetter = function(obj) {
} var self = this
\ No newline at end of file , accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName)))
obj[accessor] = function() {
var id = obj[self.identifier]
return self.target.find(id)
}
return this
}
BelongsTo.prototype.injectSetter = function(obj) {
var self = this
, accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName)))
obj[accessor] = function(associatedObject) {
obj[self.identifier] = associatedObject ? associatedObject.id : null
return obj.save()
}
return this
}
return BelongsTo
})()
var Utils = require('./../utils') var Utils = require('./../utils')
var HasManyDoubleLinked = module.exports = function(definition, instance) { module.exports = (function() {
this.__definition = definition var HasManyDoubleLinked = function(definition, instance) {
this.instance = instance this.__factory = definition
} this.instance = instance
}
HasManyDoubleLinked.prototype.injectGetter = function() {
var self = this HasManyDoubleLinked.prototype.injectGetter = function() {
var self = this
var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {} var customEventEmitter = new Utils.CustomEventEmitter(function() {
//connectorModel = join table var where = {}
//fully qualify //fully qualify
where[self.__definition.connectorModel.tableName+"."+self.__definition.identifier] = self.instance.id where[self.__factory.connectorModel.tableName+"."+self.__factory.identifier] = self.instance.id
var primaryKeys = Utils._.keys(self.__definition.connectorModel.rawAttributes) var primaryKeys = Utils._.keys(self.__factory.connectorModel.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__definition.identifier })[0] , foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
where[self.__definition.connectorModel.tableName+"."+foreignKey] = {join: self.__definition.target.tableName+".id"} where[self.__factory.connectorModel.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+".id"}
self.__factory.target.findAllJoin(self.__factory.connectorModel.tableName, {where: where})
self.__definition.target.findAllJoin(self.__definition.connectorModel.tableName, {where: where}) .on('success', function(objects) { customEventEmitter.emit('success', objects) })
.on('success', function(objects) { customEventEmitter.emit('success', objects) }) .on('failure', function(err){ customEventEmitter.emit('failure', err) })
.on('failure', function(err){ customEventEmitter.emit('failure', err) })
})
return customEventEmitter.run()
}
HasManyDoubleLinked.prototype.destroyObsoleteAssociations = function(oldAssociations, newAssociations) {
var self = this
var emitter = new Utils.CustomEventEmitter(function() {
var chainer = new Utils.QueryChainer
var foreignIdentifier = self.__definition.target.associations[self.__definition.associationAccessor].identifier
var obsoleteAssociations = oldAssociations.filter(function(obj) { return !obj.equalsOneOf(newAssociations) })
if(obsoleteAssociations.length == 0)
return emitter.emit('success', null)
obsoleteAssociations.forEach(function(associatedObject) {
var where = {}
, primaryKeys = Utils._.keys(self.__definition.connectorModel.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__definition.identifier })[0]
where[self.__definition.identifier] = self.instance.id
where[foreignKey] = associatedObject.id
self.__definition.connectorModel.find({where: where}).on('success', function(connector) {
chainer.add(connector.destroy())
if(chainer.emitters.length == obsoleteAssociations.length) {
// found all obsolete connectors and will delete them now
chainer
.run()
.on('success', function() { emitter.emit('success', null) })
.on('failure', function(err) { emitter.emit('failure', err) })
}
})
}) })
})
return emitter.run() return customEventEmitter.run()
} }
HasManyDoubleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManyDoubleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this var self = this
this.destroyObsoleteAssociations(oldAssociations, newAssociations) destroyObsoleteAssociations.call(this, oldAssociations, newAssociations)
.on('failure', function(err) { emitter.emit('failure', err) }) .error(function(err) { emitter.emit('failure', err) })
.on('success', function() { .success(function() {
var chainer = new Utils.QueryChainer var chainer = new Utils.QueryChainer
, association = self.__definition.target.associations[self.__definition.associationAccessor] , association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, unassociatedObjects = newAssociations.filter(function(obj) { return !obj.equalsOneOf(oldAssociations) }) , unassociatedObjects = newAssociations.filter(function(obj) { return !obj.equalsOneOf(oldAssociations) })
unassociatedObjects.forEach(function(unassociatedObject) { unassociatedObjects.forEach(function(unassociatedObject) {
var attributes = {} var attributes = {}
attributes[self.__definition.identifier] = self.instance.id attributes[self.__factory.identifier] = self.instance.id
attributes[foreignIdentifier] = unassociatedObject.id attributes[foreignIdentifier] = unassociatedObject.id
chainer.add(self.__definition.connectorModel.create(attributes)) chainer.add(self.__factory.connectorModel.create(attributes))
})
chainer
.run()
.success(function() { emitter.emit('success', newAssociations) })
.error(function(err) { emitter.emit('failure', err) })
}) })
}
// private
chainer var destroyObsoleteAssociations = function(oldAssociations, newAssociations) {
.run() var self = this
.on('success', function() { emitter.emit('success', newAssociations) })
.on('failure', function(err) { emitter.emit('failure', err) }) var emitter = new Utils.CustomEventEmitter(function() {
var chainer = new Utils.QueryChainer
var foreignIdentifier = self.__factory.target.associations[self.__factory.associationAccessor].identifier
var obsoleteAssociations = oldAssociations.filter(function(obj) { return !obj.equalsOneOf(newAssociations) })
if(obsoleteAssociations.length == 0)
return emitter.emit('success', null)
obsoleteAssociations.forEach(function(associatedObject) {
var where = {}
, primaryKeys = Utils._.keys(self.__factory.connectorModel.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
where[self.__factory.identifier] = self.instance.id
where[foreignKey] = associatedObject.id
self.__factory.connectorModel.find({where: where}).success(function(connector) {
chainer.add(connector.destroy())
if(chainer.emitters.length == obsoleteAssociations.length) {
// found all obsolete connectors and will delete them now
chainer
.run()
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('failure', err) })
}
})
})
}) })
} return emitter.run()
}
return HasManyDoubleLinked
})()
var Utils = require('./../utils') var Utils = require('./../utils')
var HasManySingleLinked = module.exports = function(definition, instance) { module.exports = (function() {
this.__definition = definition var HasManySingleLinked = function(definition, instance) {
this.instance = instance this.__factory = definition
} this.instance = instance
}
HasManySingleLinked.prototype.injectGetter = function() { HasManySingleLinked.prototype.injectGetter = function() {
var where = {} var where = {}
where[this.__definition.identifier] = this.instance.id where[this.__factory.identifier] = this.instance.id
return this.__definition.target.findAll({where: where}) return this.__factory.target.findAll({where: where})
} }
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this var self = this
// clear the old associations // clear the old associations
oldAssociations.forEach(function(associatedObject) { oldAssociations.forEach(function(associatedObject) {
associatedObject[self.__definition.identifier] = null associatedObject[self.__factory.identifier] = null
associatedObject.save() associatedObject.save()
}) })
// set the new one // set the new one
var chainer = new Utils.QueryChainer var chainer = new Utils.QueryChainer
newAssociations.forEach(function(associatedObject) { newAssociations.forEach(function(associatedObject) {
associatedObject[self.__definition.identifier] = self.instance.id associatedObject[self.__factory.identifier] = self.instance.id
chainer.add(associatedObject.save()) chainer.add(associatedObject.save())
}) })
chainer chainer
.run() .run()
.on('success', function() { emitter.emit('success', newAssociations) }) .success(function() { emitter.emit('success', newAssociations) })
.on('failure', function(err) { emitter.emit('failure', err) }) .error(function(err) { emitter.emit('failure', err) })
} }
return HasManySingleLinked
})()
...@@ -4,124 +4,128 @@ var Utils = require("./../utils") ...@@ -4,124 +4,128 @@ var Utils = require("./../utils")
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")
var HasMany = module.exports = function(srcModel, targetModel, options) { module.exports = (function() {
this.source = srcModel var HasMany = function(srcModel, targetModel, options) {
this.target = targetModel this.source = srcModel
this.options = options this.target = targetModel
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.options = options
this.isSelfAssociation = (this.source.tableName == this.target.tableName)
this.associationAccessor = this.combinedName = this.options.joinTableName || Utils.combineTableNames(
this.source.tableName, this.associationAccessor = this.combinedName = this.options.joinTableName || Utils.combineTableNames(
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName this.source.tableName,
) this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
)
var as = (this.options.as || Utils.pluralize(this.target.tableName))
var as = (this.options.as || Utils.pluralize(this.target.tableName))
this.accessors = {
get: Utils._.camelize('get_' + as), this.accessors = {
set: Utils._.camelize('set_' + as), get: Utils._.camelize('get_' + as),
add: Utils._.camelize(Utils.singularize('add_' + as)), set: Utils._.camelize('set_' + as),
remove: Utils._.camelize(Utils.singularize('remove_' + as)) add: Utils._.camelize(Utils.singularize('add_' + as)),
remove: Utils._.camelize(Utils.singularize('remove_' + as))
}
} }
}
// 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 multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored)
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "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 || multiAssociation) {
if (this.isSelfAssociation || multiAssociation) { // 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 {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
delete this.source.attributes[this.foreignIdentifier]
}
// define a new model, which connects the models
var combinedTableAttributes = {}
combinedTableAttributes[this.identifier] = {type:DataTypes.INTEGER, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type:DataTypes.INTEGER, primaryKey: true}
this.connectorModel = this.source.modelFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
if(!this.isSelfAssociation) this.target.associations[this.associationAccessor].connectorModel = this.connectorModel
this.connectorModel.sync()
} else { } else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier var newAttributes = {}
delete this.source.attributes[this.foreignIdentifier] newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.target.rawAttributes, newAttributes)
} }
// define a new model, which connects the models return this
var combinedTableAttributes = {}
combinedTableAttributes[this.identifier] = {type:DataTypes.INTEGER, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type:DataTypes.INTEGER, primaryKey: true}
this.connectorModel = this.source.modelManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
if(!this.isSelfAssociation) this.target.associations[this.associationAccessor].connectorModel = this.connectorModel
this.connectorModel.sync()
} else {
var newAttributes = {}
newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.target.attributes, Utils.simplifyAttributes(newAttributes))
} }
return this HasMany.prototype.injectGetter = function(obj) {
} var self = this
HasMany.prototype.injectGetter = function(obj) { obj[this.accessors.get] = function() {
var self = this var Class = self.connectorModel ? HasManyMultiLinked : HasManySingleLinked
return new Class(self, this).injectGetter()
obj[this.accessors.get] = function() { }
var Class = self.connectorModel ? HasManyMultiLinked : HasManySingleLinked
return new Class(self, this).injectGetter() return this
} }
return this
}
HasMany.prototype.injectSetter = function(obj) { HasMany.prototype.injectSetter = function(obj) {
var self = this var self = this
obj[this.accessors.set] = function(newAssociatedObjects) { obj[this.accessors.set] = function(newAssociatedObjects) {
var instance = this var instance = this
// define the returned customEventEmitter, which will emit the success event once everything is done // define the returned customEventEmitter, which will emit the success event once everything is done
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]().on('success', function(oldAssociatedObjects) { instance[self.accessors.get]().success(function(oldAssociatedObjects) {
var Class = self.connectorModel ? HasManyMultiLinked : HasManySingleLinked var Class = self.connectorModel ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectSetter(customEventEmitter, oldAssociatedObjects, newAssociatedObjects) new Class(self, instance).injectSetter(customEventEmitter, oldAssociatedObjects, newAssociatedObjects)
})
})
return customEventEmitter.run()
}
obj[this.accessors.add] = function(newAssociatedObject) {
var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]()
.on('failure', function(err){ customEventEmitter.emit('failure', err)})
.on('success', function(currentAssociatedObjects) {
if(!newAssociatedObject.equalsOneOf(currentAssociatedObjects))
currentAssociatedObjects.push(newAssociatedObject)
instance[self.accessors.set](currentAssociatedObjects)
.on('success', function(instances) { customEventEmitter.emit('success', instances) })
.on('failure', function(err) { customEventEmitter.emit('failure', err) })
})
})
return customEventEmitter.run()
}
obj[this.accessors.remove] = function(oldAssociatedObject) {
var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]().on('success', function(currentAssociatedObjects) {
var newAssociations = []
currentAssociatedObjects.forEach(function(association) {
if(!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers))
newAssociations.push(association)
}) })
})
return customEventEmitter.run()
}
obj[this.accessors.add] = function(newAssociatedObject) {
var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]()
.error(function(err){ customEventEmitter.emit('failure', err)})
.success(function(currentAssociatedObjects) {
if(!newAssociatedObject.equalsOneOf(currentAssociatedObjects))
currentAssociatedObjects.push(newAssociatedObject)
instance[self.accessors.set](currentAssociatedObjects)
.success(function(instances) { customEventEmitter.emit('success', instances) })
.error(function(err) { customEventEmitter.emit('failure', err) })
})
})
return customEventEmitter.run()
}
obj[this.accessors.remove] = function(oldAssociatedObject) {
var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]().success(function(currentAssociatedObjects) {
var newAssociations = []
instance[self.accessors.set](newAssociations) currentAssociatedObjects.forEach(function(association) {
.on('success', function() { customEventEmitter.emit('success', null) }) if(!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers))
.on('failure', function(err) { customEventEmitter.emit('failure', err) }) newAssociations.push(association)
})
instance[self.accessors.set](newAssociations)
.success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('failure', err) })
})
}) })
}) return customEventEmitter.run()
return customEventEmitter.run() }
return this
} }
return this return HasMany
} })()
\ No newline at end of file
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
var HasOne = module.exports = function(srcModel, targetModel, options) { module.exports = (function() {
this.source = srcModel var HasOne = function(srcModel, targetModel, options) {
this.target = targetModel this.source = srcModel
this.options = options this.target = targetModel
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.options = options
this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if(this.isSelfAssociation && !this.options.foreignKey && !!this.options.as)
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.options.underscored) if(this.isSelfAssociation && !this.options.foreignKey && !!this.options.as)
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.options.underscored)
this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) this.associationAccessor = this.isSelfAssociation
: this.target.tableName ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.target.tableName
this.accessors = {
get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName))), this.accessors = {
set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName))) get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName))),
set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName)))
}
}
// the id is in the target table
HasOne.prototype.injectAttributes = function() {
var newAttributes = {}
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored)
newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.target.rawAttributes, newAttributes)
return this
} }
}
HasOne.prototype.injectGetter = function(obj) {
// the id is in the target table var self = this
HasOne.prototype.injectAttributes = function() {
var newAttributes = {} obj[this.accessors.get] = function() {
var id = obj.id
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored) , where = {}
newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.target.attributes, Utils.simplifyAttributes(newAttributes)) where[self.identifier] = id
return self.target.find({where: where})
return this }
}
return this
HasOne.prototype.injectGetter = function(obj) {
var self = this
obj[this.accessors.get] = function() {
var id = obj.id
, where = {}
where[self.identifier] = id
return self.target.find({where: where})
} }
return this HasOne.prototype.injectSetter = function(obj) {
} var self = this
HasOne.prototype.injectSetter = function(obj) { obj[this.accessors.set] = function(associatedObject) {
var self = this var customEventEmitter = new Utils.CustomEventEmitter(function() {
obj[self.accessors.get]().success(function(oldObj) {
obj[this.accessors.set] = function(associatedObject) { if(oldObj) {
var customEventEmitter = new Utils.CustomEventEmitter(function() { oldObj[self.identifier] = null
obj[self.accessors.get]().on('success', function(oldObj) { oldObj.save()
if(oldObj) { }
oldObj[self.identifier] = null
oldObj.save() associatedObject[self.identifier] = obj.id
} associatedObject.save()
.success(function() { customEventEmitter.emit('success', associatedObject) })
associatedObject[self.identifier] = obj.id .error(function(err) { customEventEmitter.emit('failure', err) })
associatedObject.save() })
.on('success', function() { customEventEmitter.emit('success', associatedObject) })
.on('failure', function(err) { customEventEmitter.emit('failure', err) })
}) })
}) return customEventEmitter.run()
return customEventEmitter.run() }
return this
} }
return this return HasOne
} })()
\ No newline at end of file
var Utils = require("./../utils") var Utils = require("./../utils")
, HasOne = require('./has-one')
, HasMany = require("./has-many")
, BelongsTo = require("./belongs-to")
/* /* Defines Mixin for all models. */
Defines Mixin for all models. var Mixin = module.exports = function(){}
*/
var Associations = module.exports = { Mixin.hasOne = function(associatedModel, options) {
classMethods: { // the id is in the foreign table
hasOne: function(associatedModel, options) { var association = new HasOne(this, associatedModel, Utils._.extend((options||{}), this.options))
// the id is in the foreign table this.associations[association.associationAccessor] = association.injectAttributes()
var HasOne = require('./has-one') return this
var association = new HasOne(this, associatedModel, Utils._.extend((options||{}), this.options)) }
this.associations[association.associationAccessor] = association.injectAttributes() Mixin.belongsTo = function(associatedModel, options) {
}, // the id is in this table
belongsTo: function(associatedModel, options) { var association = new BelongsTo(this, associatedModel, Utils._.extend((options||{}), this.options))
// the id is in this table this.associations[association.associationAccessor] = association.injectAttributes()
var BelongsTo = require("./belongs-to") return this
var association = new BelongsTo(this, associatedModel, Utils._.extend((options||{}), this.options)) }
this.associations[association.associationAccessor] = association.injectAttributes() Mixin.hasMany = function(associatedModel, options) {
}, // the id is in the foreign table or in a connecting table
hasMany: function(associatedModel, options) { var association = new HasMany(this, associatedModel, Utils._.extend((options||{}), this.options))
// the id is in the foreign table or in a connecting table this.associations[association.associationAccessor] = association.injectAttributes()
var HasMany = require("./has-many") return this
var association = new HasMany(this, associatedModel, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes()
}
},
instanceMethods: {
}
} }
/* example for instance methods:
Mixin.prototype.test = function() {
console.log('asd')
}
*/
module.exports = { module.exports = {
STRING: 'VARCHAR(255)', STRING: 'VARCHAR(255)',
TEXT: 'TEXT', TEXT: 'TEXT',
INTEGER: 'INT', INTEGER: 'INTEGER',
DATE: 'DATETIME', DATE: 'DATETIME',
BOOLEAN: 'TINYINT(1)', BOOLEAN: 'TINYINT(1)',
FLOAT: 'FLOAT' FLOAT: 'FLOAT'
} }
\ No newline at end of file
module.exports = (function(){
var ConnectorManager = function(sequelize, config) {
throw new Error('Define the constructor!')
}
ConnectorManager.prototype.query = function(sql, callee, options) {
throw new Error('Define the query method!')
}
ConnectorManager.prototype.connect = function() {
throw new Error('Define the connect method!')
}
ConnectorManager.prototype.disconnect = function() {
throw new Error('Define the disconnect method!')
}
ConnectorManager.prototype.reconnect = function() {
this.disconnect()
this.connect()
}
return ConnectorManager
})()
var Query = require("./query") var Query = require("./query")
, Utils = require("../../utils")
, without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) } , without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) }
var ConnectorManager = module.exports = function(config) { module.exports = (function() {
this.client = null var ConnectorManager = function(sequelize, config) {
this.config = config this.sequelize = sequelize
this.disconnectTimeoutId = null this.client = null
this.queue = [] this.config = config || {}
this.activeQueue = [] this.disconnectTimeoutId = null
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50) this.queue = []
} this.activeQueue = []
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
ConnectorManager.prototype.connect = function() { }
var self = this Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
this.client = require("mysql").createClient({
user: this.config.username,
password: this.config.password,
host: this.config.host,
port: this.config.port,
database: this.config.database
})
this.client.setMaxListeners(this.maxConcurrentQueries) ConnectorManager.prototype.query = function(sql, callee, options) {
} if(!this.isConnected) this.connect()
ConnectorManager.prototype.query = function(sql, callee, options) { var queueItem = {
if(!this.isConnected) this.connect() query: new Query(this.client, callee, options || {}),
sql: sql
}
var queueItem = { enqueue.call(this, queueItem)
query: new Query(this.client, callee, options || {}),
sql: sql return queueItem.query
} }
this._enqueue(queueItem) ConnectorManager.prototype.connect = function() {
var self = this
return queueItem.query this.client = require("mysql").createClient({
} user: this.config.username,
password: this.config.password,
host: this.config.host,
port: this.config.port,
database: this.config.database
})
ConnectorManager.prototype.disconnect = function() { this.client.setMaxListeners(this.maxConcurrentQueries)
var self = this }
this.client.end(function() { self.client = null })
}
ConnectorManager.prototype.reconnect = function() { ConnectorManager.prototype.disconnect = function() {
this.disconnect() var self = this
this.connect() this.client.end(function() { self.client = null })
} }
// private
// private var enqueue = function(queueItem) {
if(this.activeQueue.length < this.maxConcurrentQueries) {
this.activeQueue.push(queueItem)
execQueueItem.call(this, queueItem)
} else {
this.queue.push(queueItem)
}
}
ConnectorManager.prototype._enqueue = function(queueItem) { var dequeue = function(queueItem) {
if(this.activeQueue.length < this.maxConcurrentQueries) { this.activeQueue = without(this.activeQueue, queueItem)
this.activeQueue.push(queueItem)
this._execQueueItem(queueItem)
} else {
this.queue.push(queueItem)
} }
}
var transferQueuedItems = function(count) {
ConnectorManager.prototype._dequeue = function(queueItem) { for(var i = 0; i < count; i++) {
this.activeQueue = without(this.activeQueue, queueItem) var queueItem = this.queue[0]
} if(queueItem) {
enqueue.call(this, queueItem)
ConnectorManager.prototype._transferQueuedItems = function(count) { this.queue = without(this.queue, queueItem)
for(var i = 0; i < count; i++) { }
var queueItem = this.queue[0]
if(queueItem) {
this._enqueue(queueItem)
this.queue = without(this.queue, queueItem)
} }
} }
}
ConnectorManager.prototype._afterQuery = function(queueItem) { var afterQuery = function(queueItem) {
var self = this var self = this
dequeue.call(this, queueItem)
transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length)
disconnectIfNoConnections.call(this)
}
this._dequeue(queueItem) var execQueueItem = function(queueItem) {
this._transferQueuedItems(this.maxConcurrentQueries - this.activeQueue.length) var self = this
this._disconnectIfNoConnections()
}
queueItem.query
.success(function(){ afterQuery.call(self, queueItem) })
.error(function(){ afterQuery.call(self, queueItem) })
ConnectorManager.prototype._execQueueItem = function(queueItem) { queueItem.query.run(queueItem.sql)
var self = this }
queueItem.query ConnectorManager.prototype.__defineGetter__('hasNoConnections', function() {
.on('success', function(){ self._afterQuery(queueItem) }) return (this.queue.length == 0) && (this.activeQueue.length == 0) && this.client._queue && (this.client._queue.length == 0)
.on('failure', function(){ self._afterQuery(queueItem) }) })
queueItem.query.run(queueItem.sql) ConnectorManager.prototype.__defineGetter__('isConnected', function() {
} return this.client != null
})
ConnectorManager.prototype.__defineGetter__('hasNoConnections', function() { var disconnectIfNoConnections = function() {
return (this.queue.length == 0) && (this.activeQueue.length == 0) && this.client._queue && (this.client._queue.length == 0) var self = this
})
ConnectorManager.prototype.__defineGetter__('isConnected', function() { this.disconnectTimeoutId && clearTimeout(this.disconnectTimeoutId)
return this.client != null this.disconnectTimeoutId = setTimeout(function() {
}) self.isConnected && self.hasNoConnections && self.disconnect()
}, 100)
}
ConnectorManager.prototype._disconnectIfNoConnections = function() { return ConnectorManager
var self = this })()
this.disconnectTimeoutId && clearTimeout(this.disconnectTimeoutId)
this.disconnectTimeoutId = setTimeout(function() {
self.isConnected && self.hasNoConnections && self.disconnect()
}, 100)
}
var Utils = require("../../utils")
, util = require("util")
module.exports = (function() {
var QueryGenerator = {
createTableQuery: function(tableName, attributes, options) {
options = Utils._.extend({
engine: 'InnoDB',
charset: null
}, options || {})
var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>) ENGINE=<%= engine %> <%= charset %>"
, primaryKeys = []
, attrStr = Utils._.map(attributes, function(dataType, attr) {
var dt = dataType
if (Utils._.includes(dt, 'PRIMARY KEY')) {
primaryKeys.push(attr)
return Utils.addTicks(attr) + " " + dt.replace(/PRIMARY KEY/, '')
} else {
return Utils.addTicks(attr) + " " + dt
}
}).join(", ")
, values = {
table: Utils.addTicks(tableName),
attributes: attrStr,
engine: options.engine,
charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : "")
}
, pkString = primaryKeys.map(function(pk) {return Utils.addTicks(pk)}).join(", ")
if(pkString.length > 0) values.attributes += ", PRIMARY KEY (" + pkString + ")"
return Utils._.template(query)(values).trim() + ";"
},
dropTableQuery: function(tableName, options) {
options = options || {}
var query = "DROP TABLE IF EXISTS <%= table %>;"
return Utils._.template(query)({table: Utils.addTicks(tableName)})
},
renameTableQuery: function(before, after) {
var query = "RENAME TABLE `<%= before %>` TO `<%= after %>`;"
return Utils._.template(query)({ before: before, after: after })
},
showTablesQuery: function() {
return 'SHOW TABLES;'
},
addColumnQuery: function(tableName, attributes) {
var query = "ALTER TABLE `<%= tableName %>` ADD <%= attributes %>;"
, attrString = Utils._.map(attributes, function(definition, attributeName) {
return Utils._.template('`<%= attributeName %>` <%= definition %>')({
attributeName: attributeName,
definition: definition
})
}).join(', ')
return Utils._.template(query)({ tableName: tableName, attributes: attrString })
},
removeColumnQuery: function(tableName, attributeName) {
var query = "ALTER TABLE `<%= tableName %>` DROP `<%= attributeName %>`;"
return Utils._.template(query)({ tableName: tableName, attributeName: attributeName })
},
changeColumnQuery: function(tableName, attributes) {
var query = "ALTER TABLE `<%= tableName %>` CHANGE <%= attributes %>;"
var attrString = Utils._.map(attributes, function(definition, attributeName) {
return Utils._.template('`<%= attributeName %>` `<%= attributeName %>` <%= definition %>')({
attributeName: attributeName,
definition: definition
})
}).join(', ')
return Utils._.template(query)({ tableName: tableName, attributes: attrString })
},
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = "ALTER TABLE `<%= tableName %>` CHANGE <%= attributes %>;"
var attrString = Utils._.map(attributes, function(definition, attributeName) {
return Utils._.template('`<%= before %>` `<%= after %>` <%= definition %>')({
before: attrBefore,
after: attributeName,
definition: definition
})
}).join(', ')
return Utils._.template(query)({ tableName: tableName, attributes: attrString })
},
selectQuery: function(tableName, options) {
options = options || {}
options.table = Array.isArray(tableName) ? tableName.map(function(tbl){return Utils.addTicks(tbl)}).join(", ") : Utils.addTicks(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr){
if(Array.isArray(attr) && attr.length == 2)
return [attr[0], Utils.addTicks(attr[1])].join(' as ')
else
return attr.indexOf(Utils.TICK_CHAR)<0 ? Utils.addTicks(attr) : attr
}).join(", ")
options.attributes = options.attributes || '*'
var query = "SELECT <%= attributes %> FROM <%= table %>"
if(options.where) {
options.where = QueryGenerator.getWhereConditions(options.where)
query += " WHERE <%= where %>"
}
if(options.order) query += " ORDER BY <%= order %>"
if(options.group) {
options.group = Utils.addTicks(options.group)
query += " GROUP BY <%= group %>"
}
if(options.limit) {
if(options.offset) query += " LIMIT <%= offset %>, <%= limit %>"
else query += " LIMIT <%= limit %>"
}
query += ";"
return Utils._.template(query)(options)
},
insertQuery: function(tableName, attrValueHash) {
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>);"
var replacements = {
table: Utils.addTicks(tableName),
attributes: Utils._.keys(attrValueHash).map(function(attr){return Utils.addTicks(attr)}).join(","),
values: Utils._.values(attrValueHash).map(function(value){
return Utils.escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).join(",")
}
return Utils._.template(query)(replacements)
},
updateQuery: function(tableName, values, where) {
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
var replacements = {
table: Utils.addTicks(tableName),
values: Utils._.map(values, function(value, key){
return Utils.addTicks(key) + "=" + Utils.escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).join(","),
where: QueryGenerator.getWhereConditions(where)
}
return Utils._.template(query)(replacements)
},
deleteQuery: function(tableName, where, options) {
options = options || {}
options.limit = options.limit || 1
var query = "DELETE FROM <%= table %> WHERE <%= where %> LIMIT <%= limit %>"
var replacements = {
table: Utils.addTicks(tableName),
where: QueryGenerator.getWhereConditions(where),
limit: Utils.escape(options.limit)
}
return Utils._.template(query)(replacements)
},
addIndexQuery: function(tableName, attributes, options) {
var transformedAttributes = attributes.map(function(attribute) {
if(typeof attribute == 'string')
return attribute
else {
var result = ""
if(!attribute.attribute)
throw new Error('The following index attribute has no attribute: ' + util.inspect(attribute))
result += attribute.attribute
if(attribute.length)
result += '(' + attribute.length + ')'
if(attribute.order)
result += ' ' + attribute.order
return result
}
})
var onlyAttributeNames = attributes.map(function(attribute) {
return (typeof attribute == 'string') ? attribute : attribute.attribute
})
options = Utils._.extend({
indicesType: null,
indexName: Utils._.underscored(tableName + '_' + onlyAttributeNames.join('_')),
parser: null
}, options || {})
return Utils._.compact([
"CREATE", options.indicesType, "INDEX", options.indexName,
(options.indexType ? ('USING ' + options.indexType) : undefined),
"ON", tableName, '(' + transformedAttributes.join(', ') + ')',
(options.parser ? "WITH PARSER " + options.parser : undefined)
]).join(' ')
},
showIndexQuery: function(tableName, options) {
var sql = "SHOW INDEX FROM <%= tableName %><%= options %>"
return Utils._.template(sql)({
tableName: tableName,
options: (options || {}).database ? ' FROM ' + options.database : ''
})
},
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = "DROP INDEX <%= indexName %> ON <%= tableName %>"
, indexName = indexNameOrAttributes
if(typeof indexName != 'string')
indexName = Utils._.underscored(tableName + '_' + indexNameOrAttributes.join('_'))
return Utils._.template(sql)({ tableName: tableName, indexName: indexName })
},
getWhereConditions: function(smth) {
var result = null
if(Utils.isHash(smth))
result = QueryGenerator.hashToWhereConditions(smth)
else if(typeof smth == 'number')
result = Utils.addTicks('id') + "=" + Utils.escape(smth)
else if(typeof smth == "string")
result = smth
else if(Array.isArray(smth))
result = Utils.format(smth)
return result
},
hashToWhereConditions: function(hash) {
return Utils._.map(hash, function(value, key) {
var _key = Utils.addTicks(key)
, _value = null
if(Array.isArray(value)) {
_value = "(" + Utils._.map(value, function(subvalue) {
return Utils.escape(subvalue);
}).join(',') + ")"
return [_key, _value].join(" IN ")
}
else if ((value) && (typeof value == 'object')) {
//using as sentinel for join column => value
_value = value.join.split('.').map(function(col){return Utils.addTicks(col)}).join(".")
return [_key, _value].join("=")
} else {
_value = Utils.escape(value)
return (_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("=")
}
}).join(" AND ")
},
attributesToSQL: function(attributes) {
var result = {}
Utils._.map(attributes, function(dataType, name) {
if(Utils.isHash(dataType)) {
var template = "<%= type %>"
, replacements = { type: dataType.type }
if(dataType.hasOwnProperty('allowNull') && (!dataType.allowNull)) template += " NOT NULL"
if(dataType.autoIncrement) template +=" auto_increment"
if(dataType.defaultValue != undefined) {
template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = Utils.escape(dataType.defaultValue)
}
if(dataType.unique) template += " UNIQUE"
if(dataType.primaryKey) template += " PRIMARY KEY"
result[name] = Utils._.template(template)(replacements)
} else {
result[name] = dataType
}
})
return result
},
findAutoIncrementField: function(factory) {
var fields = Utils._.map(factory.attributes, function(definition, name) {
var isAutoIncrementField = (definition && (definition.indexOf('auto_increment') > -1))
return isAutoIncrementField ? name : null
})
return Utils._.compact(fields)
}
}
return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator)
})()
var Utils = require("../../utils")
module.exports = (function() {
var Query = function(client, callee, options) {
var self = this
this.client = client
this.callee = callee
this.options = Utils._.extend({
logging: true,
plain: false,
raw: false
}, options || {})
this.bindClientFunction = function(err) { onFailure.call(self, err) }
}
Utils._.extend(Query.prototype, require("../query").prototype)
Query.prototype.run = function(sql) {
var self = this
this.sql = sql
bindClient.call(this)
if(this.options.logging)
console.log('Executing: ' + this.sql)
this.client.query(this.sql, function(err, results, fields) {
//allow clients to listen to sql to do their own logging or whatnot
self.emit('sql', self.sql)
err ? onFailure.call(self, err) : onSuccess.call(self, results, fields)
}).setMaxListeners(100)
return this
}
//private
var bindClient = function() {
this.client.on('error', this.bindClientFunction)
}
var unbindClient = function() {
this.client.removeListener('error', this.bindClientFunction)
}
var onSuccess = function(results, fields) {
var result = this.callee
, self = this
// add the inserted row id to the instance
if (this.callee && (this.sql.indexOf('INSERT INTO') == 0) && (results.hasOwnProperty('insertId')))
this.callee[this.callee.__factory.autoIncrementField] = results.insertId
if (this.sql.indexOf('SELECT') == 0) {
// transform results into real model instances
// return the first real model instance if options.plain is set (e.g. Model.find)
if(this.options.raw) {
result = results
} else {
result = results.map(function(result) {
return self.callee.build(result, { isNewRecord: false })
})
}
if(this.options.plain)
result = (result.length == 0) ? null : result[0]
} else if(this.sql.indexOf('SHOW TABLES') == 0) {
result = Utils._.flatten(results.map(function(resultSet) {
return Utils._.values(resultSet)
}))
} else if((this.sql.indexOf('SHOW') == 0) || (this.sql.indexOf('DESCRIBE') == 0)) {
result = results
}
unbindClient.call(this)
this.emit('success', result)
}
var onFailure = function(err) {
unbindClient.call(this)
this.emit('failure', err, this.callee)
}
return Query
})()
module.exports = (function() {
var QueryGenerator = {
/*
Returns a query for creating a table.
Parameters:
- tableName: Name of the new table.
- attributes: An object with containing attribute-attributeType-pairs.
Attributes should have the format:
{attributeName: type, attr2: type2}
--> e.g. {title: 'VARCHAR(255)'}
- options: An object with options.
Defaults: { engine: 'InnoDB', charset: null }
*/
createTableQuery: function(tableName, attributes, options) {
throwMethodUndefined('createTableQuery')
},
/*
Returns a query for dropping a table.
*/
dropTableQuery: function(tableName, options) {
throwMethodUndefined('dropTableQuery')
},
/*
Returns a rename table query.
Parameters:
- originalTableName: Name of the table before execution.
- futureTableName: Name of the table after execution.
*/
renameTableQuery: function(originalTableName, futureTableName) {
throwMethodUndefined('renameTableQuery')
},
/*
Returns a query, which gets all available table names in the database.
*/
showTablesQuery: function() {
throwMethodUndefined('showTablesQuery')
},
/*
Returns a query, which adds an attribute to an existing table.
Parameters:
- tableName: Name of the existing table.
- attributes: A hash with attribute-attributeOptions-pairs.
- key: attributeName
- value: A hash with attribute specific options:
- type: DataType
- defaultValue: A String with the default value
- allowNull: Boolean
*/
addColumnQuery: function(tableName, attributes) {
throwMethodUndefined('addColumnQuery')
},
/*
Returns a query, which removes an attribute from an existing table.
Parameters:
- tableName: Name of the existing table
- attributeName: Name of the obsolete attribute.
*/
removeColumnQuery: function(tableName, attributeName) {
throwMethodUndefined('removeColumnQuery')
},
/*
Returns a query, which modifies an existing attribute from a table.
Parameters:
- tableName: Name of the existing table.
- attributes: A hash with attribute-attributeOptions-pairs.
- key: attributeName
- value: A hash with attribute specific options:
- type: DataType
- defaultValue: A String with the default value
- allowNull: Boolean
*/
changeColumnQuery: function(tableName, attributes) {
throwMethodUndefined('changeColumnQuery')
},
/*
Returns a query, which renames an existing attribute.
Parameters:
- tableName: Name of an existing table.
- attrNameBefore: The name of the attribute, which shall be renamed.
- attrNameAfter: The name of the attribute, after renaming.
*/
renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter) {
throwMethodUndefined('renameColumnQuery')
},
/*
Returns a query for selecting elements in the table <tableName>.
Options:
- attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: *
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
- order -> e.g. 'id DESC'
- group
- limit -> The maximum count you want to get.
- offset -> An offset value to start from. Only useable with limit!
*/
selectQuery: function(tableName, options) {
throwMethodUndefined('selectQuery')
},
/*
Returns an insert into command. Parameters: table name + hash of attribute-value-pairs.
*/
insertQuery: function(tableName, attrValueHash) {
throwMethodUndefined('insertQuery')
},
/*
Returns an update query.
Parameters:
- tableName -> Name of the table
- values -> A hash with attribute-value-pairs
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
*/
updateQuery: function(tableName, values, where) {
throwMethodUndefined('updateQuery')
},
/*
Returns a deletion query.
Parameters:
- tableName -> Name of the table
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
Options:
- limit -> Maximaum count of lines to delete
*/
deleteQuery: function(tableName, where, options) {
throwMethodUndefined('deleteQuery')
},
/*
Returns an add index query.
Parameters:
- tableName -> Name of an existing table.
- attributes:
An array of attributes as string or as hash.
If the attribute is a hash, it must have the following content:
- attribute: The name of the attribute/column
- length: An integer. Optional
- order: 'ASC' or 'DESC'. Optional
- options:
- indicesType: UNIQUE|FULLTEXT|SPATIAL
- indexName: The name of the index. Default is <tableName>_<attrName1>_<attrName2>
- parser
*/
addIndexQuery: function(tableName, attributes, options) {
throwMethodUndefined('addIndexQuery')
},
/*
Returns an show index query.
Parameters:
- tableName: Name of an existing table.
- options:
- database: Name of the database.
*/
showIndexQuery: function(tableName, options) {
throwMethodUndefined('showIndexQuery')
},
/*
Returns a remove index query.
Parameters:
- tableName: Name of an existing table.
- indexNameOrAttributes: The name of the index as string or an array of attribute names.
*/
removeIndexQuery: function(tableName, indexNameOrAttributes) {
throwMethodUndefined('removeIndexQuery')
},
/*
Takes something and transforms it into values of a where condition.
*/
getWhereConditions: function(smth) {
throwMethodUndefined('getWhereConditions')
},
/*
Takes a hash and transforms it into a mysql where condition: {key: value, key2: value2} ==> key=value AND key2=value2
The values are transformed by the relevant datatype.
*/
hashToWhereConditions: function(hash) {
throwMethodUndefined('hashToWhereConditions')
},
/*
This method transforms an array of attribute hashes into equivalent
sql attribute definition.
*/
attributesToSQL: function(attributes) {
throwMethodUndefined('attributesToSQL')
},
/*
Returns all auto increment fields of a factory.
*/
findAutoIncrementField: function(factory) {
throwMethodUndefined('findAutoIncrementField')
}
}
var throwMethodUndefined = function(methodName) {
throw new Error('The method "' + methodName + '" is not defined! Please add it to your sql dialect.')
}
return QueryGenerator
})()
var Utils = require("../utils")
module.exports = (function() {
var Query = function(database, callee, options) {
throw new Error('Constructor was not overwritten!')
}
Utils.addEventEmitter(Query)
Query.prototype.run = function(sql) {
throw new Error("The run method wasn't overwritten!")
}
Query.prototype.success = Query.prototype.ok = function(fct) {
this.on('success', fct)
return this
}
Query.prototype.failure = Query.prototype.fail = Query.prototype.error = function(fct) {
this.on('failure', fct)
return this
}
return Query
})()
var Utils = require("../../utils")
, sqlite3 = require('sqlite3').verbose()
, Query = require("./query")
module.exports = (function() {
var ConnectorManager = function(sequelize) {
this.sequelize = sequelize
this.database = new sqlite3.Database(sequelize.options.storage || ':memory:')
}
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.query = function(sql, callee, options) {
return new Query(this.database, callee, options).run(sql)
}
return ConnectorManager
})()
var Utils = require("../../utils")
, util = require("util")
module.exports = (function() {
var QueryGenerator = {
createTableQuery: function(tableName, attributes, options) {
options = options || {}
var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)"
, primaryKeys = []
, attrStr = Utils._.map(attributes, function(dataType, attr) {
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr)
return Utils.addTicks(attr) + " " + dataType
} else {
return Utils.addTicks(attr) + " " + dataType
}
}).join(", ")
, values = {
table: Utils.addTicks(tableName),
attributes: attrStr,
charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : "")
}
return Utils._.template(query)(values).trim() + ";"
},
showTablesQuery: function() {
return "SELECT name FROM sqlite_master WHERE type='table';"
},
deleteQuery: function(tableName, where, options) {
options = options || {}
var query = "DELETE FROM <%= table %> WHERE <%= where %>"
var replacements = {
table: Utils.addTicks(tableName),
where: this.getWhereConditions(where),
limit: Utils.escape(options.limit)
}
return Utils._.template(query)(replacements)
},
attributesToSQL: function(attributes) {
var result = {}
Utils._.map(attributes, function(dataType, name) {
if(Utils.isHash(dataType)) {
var template = "<%= type %>"
, replacements = { type: dataType.type }
if(dataType.hasOwnProperty('allowNull') && !dataType.allowNull && !dataType.primaryKey)
template += " NOT NULL"
if(dataType.defaultValue != undefined) {
template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = Utils.escape(dataType.defaultValue)
}
if(dataType.unique) template += " UNIQUE"
if(dataType.primaryKey) template += " PRIMARY KEY"
result[name] = Utils._.template(template)(replacements)
} else {
result[name] = dataType
}
})
return result
},
findAutoIncrementField: function(factory) {
var fields = Utils._.map(factory.attributes, function(definition, name) {
var isAutoIncrementField = (definition && (definition.indexOf('INTEGER PRIMARY KEY') == 0))
return isAutoIncrementField ? name : null
})
return Utils._.compact(fields)
}
}
var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require("../query-generator")),
Utils._.clone(require("../mysql/query-generator"))
)
return Utils._.extend(MySqlQueryGenerator, QueryGenerator)
})()
var Utils = require("../../utils")
module.exports = (function() {
var Query = function(database, callee, options) {
var self = this
this.database = database
this.callee = callee
this.options = Utils._.extend({
logging: true,
plain: false,
raw: false
}, options || {})
}
Utils._.extend(Query.prototype, require("../query").prototype)
Query.prototype.run = function(sql) {
var self = this
this.sql = sql
if(this.options.logging)
console.log('Executing: ' + this.sql)
this.database.serialize(function() {
var isInsertCommand = (self.sql.toLowerCase().indexOf('insert') == 0)
, isUpdateCommand = (self.sql.toLowerCase().indexOf('update') == 0)
, databaseMethod = (isInsertCommand || isUpdateCommand) ? 'run' : 'all'
self.database[databaseMethod](self.sql, function(err, results) {
err ? onFailure.call(self, err) : onSuccess.call(self, results, this)
})
})
return this
}
//private
var onSuccess = function(results, metaData) {
var result = this.callee
, self = this
// add the inserted row id to the instance
if (this.callee && (this.sql.indexOf('INSERT INTO') == 0) && metaData.hasOwnProperty('lastID')) {
var autoIncrementField = this.callee.__factory.autoIncrementField
this.callee[autoIncrementField] = metaData.lastID
}
if (this.sql.indexOf('sqlite_master') != -1) {
result = results.map(function(resultSet){ return resultSet.name })
} else if (this.sql.indexOf('SELECT') == 0) {
// transform results into real model instances
// return the first real model instance if options.plain is set (e.g. Model.find)
if(this.options.raw) {
result = results
} else {
result = results.map(function(result) {
return self.callee.build(result, { isNewRecord: false })
})
}
if(this.options.plain)
result = (result.length == 0) ? null : result[0]
} else if((this.sql.indexOf('SHOW') == 0) || (this.sql.indexOf('DESCRIBE') == 0))
result = results
this.emit('success', result)
}
var onFailure = function(err) {
this.emit('failure', err, this.callee)
}
return Query
})()
var util = require("util")
, EventEmitter = require("events").EventEmitter
module.exports = (function() {
var CustomEventEmitter = function(fct) {
this.fct = fct
}
util.inherits(CustomEventEmitter, EventEmitter)
CustomEventEmitter.prototype.run = function() {
var self = this
// delay the function call and return the emitter
setTimeout(function(){
self.fct.call(self, self)
}, 5)
return this
}
CustomEventEmitter.prototype.success = CustomEventEmitter.prototype.ok = function(fct) {
this.on('success', fct)
return this
}
CustomEventEmitter.prototype.failure =
CustomEventEmitter.prototype.fail =
CustomEventEmitter.prototype.error =
function(fct) {
this.on('failure', fct)
return this
}
return CustomEventEmitter
})()
var Utils = require("./utils") var Utils = require("../utils")
var NullEmitter = module.exports = function(delay) {
var self = this module.exports = (function(){
var NullEmitter = function(delay) {
delay = delay || 10 var self = this
setTimeout(function() { self.emitNull() }, delay)
} delay = delay || 10
Utils.addEventEmitter(NullEmitter) setTimeout(function() { self.emitNull() }, delay)
}
NullEmitter.prototype.emitNull = function() { Utils.addEventEmitter(NullEmitter)
this.emit('success', null)
this.emit('failure', null) NullEmitter.prototype.emitNull = function() {
} this.emit('success', null)
\ No newline at end of file this.emit('failure', null)
}
return NullEmitter
})()
var moment = require("moment")
, Utils = require("./utils")
, DataTypes = require("./data-types")
, QueryInterface = require("./query-interface")
module.exports = (function() {
var Migration = function(migrator, path) {
var split = path.split('/')
this.migrator = migrator
this.path = path
this.filename = Utils._.last(this.path.split('/'))
this.migrationId = parseInt(this.filename.match(/(.*)-.*/)[1])
this.date = Migration.stringToDate(this.filename)
this.queryInterface = this.migrator.queryInterface
this.undoneMethods = 0
}
///////////////
// static /////
///////////////
Migration.getFormattedDateString = function(s) {
var result = null
try {
result = s.match(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/).slice(1, 6).join('-')
} catch(e) {
throw new Error(s + ' is no valid migration timestamp format! Use YYYYMMDDHHmmss!')
}
return result
}
Migration.stringToDate = function(s) {
return moment(Migration.getFormattedDateString(s), "YYYYMMDDHHmmss")
}
Migration.migrationHasInterfaceCalls = function(func) {
var functionString = Utils.removeCommentsFromFunctionString(func.toString())
, hasCalls = false
for(var method in QueryInterface.prototype) {
var regex = new RegExp('[\\s\\n\\r]*\\.[\\s\\n\\r]*' + method)
hasCalls = hasCalls || regex.test(functionString)
}
return hasCalls
}
///////////////
// member /////
///////////////
Object.defineProperty(Migration.prototype, 'migration', {
get: function() {
return require(this.path)
}
})
Migration.prototype.execute = function(options) {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
options = Utils._.extend({
method: 'up'
}, options || {})
var onSuccess = function() { emitter.emit('success', null) }
, func = self.migration[options.method]
extendMigrationWithQueryInterfaceMethods.call(self, onSuccess)
func.call(null, self, DataTypes)
if(!Migration.migrationHasInterfaceCalls(func))
onSuccess()
}).run()
}
Migration.prototype.isBefore = function(dateString, options) {
options = Utils._.extend({
withoutEquals: false
}, options || {})
var date = Migration.stringToDate(dateString.toString())
return options.withoutEqual ? (date > this.date) : (date >= this.date)
}
Migration.prototype.isAfter = function(dateString, options) {
options = Utils._.extend({
withoutEquals: false
}, options || {})
var date = Migration.stringToDate(dateString.toString())
return options.withoutEqual ? (date < this.date) : (date <= this.date)
}
// extends the Migration prototype with all methods of QueryInterface.prototype
// with additional tracking of start and finish. this is done in order to minimize
// asynchronous handling in migrations
var extendMigrationWithQueryInterfaceMethods = function(callback) {
var self = this
for(var method in QueryInterface.prototype) {
(function(_method) {
self[_method] = function() {
var emitter = self.QueryInterface
, args = Utils._.map(arguments, function(arg, _) { return arg })
self.undoneMethods++
// bind listeners to the query interface
// the event will have the same name like the method
self.queryInterface.on(_method, function(err) {
self.undoneMethods--
if(err)
throw new Error(err)
else
(self.undoneMethods == 0) && callback && callback()
})
self.queryInterface[_method].apply(self.queryInterface, args)
}
})(method)
}
}
return Migration
})()
const fs = require("fs")
, path = require("path")
, moment = require("moment")
var Utils = require("./utils")
, Migration = require("./migration")
, DataTypes = require("./data-types")
module.exports = (function() {
var Migrator = function(sequelize, options) {
this.sequelize = sequelize
this.options = Utils._.extend({
path: __dirname + '/../migrations',
from: null,
to: null,
logging: true
}, options || {})
}
Object.defineProperty(Migrator.prototype, "queryInterface", {
get: function() {
return this.sequelize.getQueryInterface()
}
})
Migrator.prototype.migrate = function(options) {
var self = this
options = Utils._.extend({
method: 'up'
}, options || {})
return new Utils.CustomEventEmitter(function(emitter) {
self.getUndoneMigrations(function(err, migrations) {
if(err) {
emitter.emit('failure', err)
} else {
var chainer = new Utils.QueryChainer
if(options.method == 'down')
migrations.reverse()
migrations.forEach(function(migration) {
chainer.add(migration, 'execute', [options], {
before: function(migration) {
if(self.options.logging)
console.log('Executing migration: ' + migration.filename)
},
after: function(migration) {
if(self.options.logging)
console.log('Executed migration: ' + migration.filename)
},
success: function(migration, callback) {
saveSuccessfulMigration.call(self, migrations[0], migration, callback)
}
})
})
chainer
.runSerially({ skipOnError: true })
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('failure', err) })
}
})
}).run()
}
Migrator.prototype.getUndoneMigrations = function(callback) {
var self = this
var filterFrom = function(migrations, from, callback, options) {
var result = migrations.filter(function(migration) { return migration.isAfter(from, options) })
callback && callback(null, result)
}
var filterTo = function(migrations, to, callback, options) {
var result = migrations.filter(function(migration) { return migration.isBefore(to, options) })
callback && callback(null, result)
}
var migrationFiles = fs.readdirSync(this.options.path)
var migrations = migrationFiles.map(function(file) {
return new Migration(self, self.options.path + '/' + file)
})
migrations = migrations.sort(function(a,b){
return parseInt(a.filename.split('-')[0]) - parseInt(b.filename.split('-')[0])
})
if(this.options.from) {
filterFrom(migrations, this.options.from, function(err, migrations) {
if(self.options.to)
filterTo(migrations, self.options.to, callback)
else
callback && callback(null, migrations)
})
} else {
getLastMigrationIdFromDatabase.call(this).success(function(lastMigrationId) {
if(lastMigrationId) {
filterFrom(migrations, lastMigrationId, function(err, migrations) {
if(self.options.to)
filterTo(migrations, self.options.to, callback)
else
callback && callback(null, migrations)
}, { withoutEqual: true })
} else {
if(self.options.to)
filterTo(migrations, self.options.to, callback)
else
callback && callback(null, migrations)
}
}).error(function(err) {
callback && callback(err, null)
})
}
}
Migrator.prototype.findOrCreateSequelizeMetaModel = function(syncOptions) {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var storedModel = self.sequelize.modelFactoryManager.getModel('SequelizeMeta')
, SequelizeMeta = storedModel
if(!storedModel) {
SequelizeMeta = self.sequelize.define('SequelizeMeta', {
from: DataTypes.STRING,
to: DataTypes.STRING
})
}
// force sync when model has newly created or if syncOptions are passed
if(!storedModel || syncOptions) {
SequelizeMeta
.sync(syncOptions || {})
.success(function() { emitter.emit('success', SequelizeMeta) })
.error(function(err) { emitter.emit('failure', err) })
} else {
emitter.emit('success', SequelizeMeta)
}
}).run()
}
// private
var getLastMigrationFromDatabase = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
self.findOrCreateSequelizeMetaModel().success(function(SequelizeMeta) {
SequelizeMeta.find({ order: 'id DESC' }).success(function(meta) {
emitter.emit('success', meta ? meta : null)
}).error(function(err) { emitter.emit('failure', err) })
}).error(function(err) { emitter.emit(err) })
}).run()
}
var getLastMigrationIdFromDatabase = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
getLastMigrationFromDatabase.call(self)
.success(function(meta) {
emitter.emit('success', meta ? meta.to : null)
})
.error(function(err) {
emitter.emit('failure', err)
})
}).run()
}
var getFormattedDateString = function(s) {
var result = null
try {
result = s.match(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/).slice(1, 6).join('-')
} catch(e) {
throw new Error(s + ' is no valid migration timestamp format! Use YYYYMMDDHHmmss!')
}
return result
}
var stringToDate = function(s) {
return moment(getFormattedDateString(s), "YYYYMMDDHHmmss")
}
var saveSuccessfulMigration = function(from, to, callback) {
var self = this
self.findOrCreateSequelizeMetaModel().success(function(SequelizeMeta) {
SequelizeMeta
.create({ from: from.migrationId, to: to.migrationId })
.success(callback)
})
}
return Migrator
})()
var ModelManager = module.exports = function(sequelize) { module.exports = (function() {
this.models = [] var ModelFactoryManager = function(sequelize) {
this.sequelize = sequelize this.models = []
} this.sequelize = sequelize
}
ModelManager.prototype.addModel = function(model) { ModelFactoryManager.prototype.addModel = function(model) {
model.modelManager = this this.models.push(model)
this.models.push(model)
return model
}
ModelManager.prototype.removeModel = function(model) { return model
this.models = this.models.filter(function(_model) { }
return _model.name != model.name
}) ModelFactoryManager.prototype.removeModel = function(model) {
} this.models = this.models.filter(function(_model) {
return _model.name != model.name
})
}
ModelFactoryManager.prototype.getModel = function(modelName) {
var model = this.models.filter(function(model) {
return model.name == modelName
})
return !!model ? model[0] : null
}
ModelManager.prototype.getModel = function(modelName) { ModelFactoryManager.prototype.__defineGetter__('all', function() {
var model = this.models.filter(function(model) { return this.models
return model.name == modelName
}) })
return !!model ? model[0] : null
}
ModelManager.prototype.__defineGetter__('all', function() { return ModelFactoryManager
return this.models })()
})
var Utils = require("./utils")
, Model = require("./model")
, DataTypes = require("./data-types")
module.exports = (function() {
var ModelFactory = function(name, attributes, options) {
var self = this
this.options = Utils._.extend({
timestamps: true,
instanceMethods: {},
classMethods: {},
validate: {},
freezeTableName: false,
underscored: false,
paranoid: false
}, options || {})
this.name = name
this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name)
this.rawAttributes = attributes
this.modelFactoryManager = null // defined in init function
this.associations = {}
// extract validation
this.validate = this.options.validate || {}
}
Object.defineProperty(ModelFactory.prototype, 'attributes', {
get: function() {
return this.QueryGenerator.attributesToSQL(this.rawAttributes)
}
})
Object.defineProperty(ModelFactory.prototype, 'QueryInterface', {
get: function() { return this.modelFactoryManager.sequelize.getQueryInterface() }
})
Object.defineProperty(ModelFactory.prototype, 'QueryGenerator', {
get: function() { return this.QueryInterface.QueryGenerator }
})
Object.defineProperty(ModelFactory.prototype, 'primaryKeyCount', {
get: function() { return Utils._.keys(this.primaryKeys).length }
})
Object.defineProperty(ModelFactory.prototype, 'hasPrimaryKeys', {
get: function() { return this.primaryKeyCount > 0 }
})
ModelFactory.prototype.init = function(modelFactoryManager) {
this.modelFactoryManager = modelFactoryManager
addDefaultAttributes.call(this)
addOptionalClassMethods.call(this)
findAutoIncrementField.call(this)
return this
}
ModelFactory.prototype.sync = function(options) {
options = Utils.merge(options || {}, this.options)
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var doQuery = function() {
self.QueryInterface
.createTable(self.tableName, self.attributes, options)
.success(function() { emitter.emit('success', self) })
.error(function(err) { emitter.emit('failure', err) })
}
if(options.force)
self.drop().success(doQuery).error(function(err) { emitter.emit('failure', err) })
else
doQuery()
}).run()
}
ModelFactory.prototype.drop = function() {
return this.QueryInterface.dropTable(this.tableName)
}
ModelFactory.prototype.all = function() {
return this.findAll()
}
ModelFactory.prototype.findAll = function(options) {
return this.QueryInterface.select(this, this.tableName, options)
}
//right now, the caller (has-many-double-linked) is in charge of the where clause
ModelFactory.prototype.findAllJoin = function(joinTableName, options) {
optcpy = Utils._.clone(options)
optcpy.attributes = optcpy.attributes || [Utils.addTicks(this.tableName)+".*"]
return this.query(QueryGenerator.selectQuery([this.tableName, joinTableName], optcpy))
}
ModelFactory.prototype.find = function(options) {
if([null, undefined].indexOf(options) > -1) {
var NullEmitter = require("./emitters/null-emitter")
return new NullEmitter()
}
// options is not a hash but an id
if(typeof options == 'number')
options = { where: options }
else if (Utils.argsArePrimaryKeys(arguments, this.primaryKeys)) {
var where = {}
, self = this
Utils._.each(arguments, function(arg, i) {
var key = Utils._.keys(self.primaryKeys)[i]
where[key] = arg
})
options = { where: where }
}
options.limit = 1
return this.QueryInterface.select(this, this.tableName, options, {plain: true})
}
ModelFactory.prototype.count = function(options) {
options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['count(*)', 'count'])
return this.QueryInterface.rawSelect(this.tableName, options, 'count')
}
ModelFactory.prototype.max = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['max(' + field + ')', 'max'])
return this.QueryInterface.rawSelect(this.tableName, options, 'max')
}
ModelFactory.prototype.min = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['min(' + field + ')', 'min'])
return this.QueryInterface.rawSelect(this.tableName, options, 'min')
}
ModelFactory.prototype.build = function(values, options) {
var instance = new Model(values, Utils._.extend(this.options, this.attributes, { hasPrimaryKeys: this.hasPrimaryKeys }))
, self = this
options = options || {}
instance.__factory = this
Utils._.map(this.attributes, function(definition, name) {
if(typeof instance[name] == 'undefined') {
var value = null
if(self.rawAttributes.hasOwnProperty(name) && self.rawAttributes[name].hasOwnProperty('defaultValue')) {
value = self.rawAttributes[name].defaultValue
}
instance[name] = value
instance.addAttribute(name, value)
}
// add validation
if (self.rawAttributes.hasOwnProperty(name) && self.rawAttributes[name].hasOwnProperty('validate')) {
instance.setValidators(name, self.rawAttributes[name].validate)
}
})
Utils._.each(this.options.instanceMethods || {}, function(fct, name) { instance[name] = fct })
Utils._.each(this.associations, function(association, associationName) {
association.injectGetter(instance)
association.injectSetter(instance)
})
instance.isNewRecord = options.hasOwnProperty('isNewRecord') ? options.isNewRecord : true
return instance
}
ModelFactory.prototype.create = function(values) {
return this.build(values).save()
}
ModelFactory.prototype.__defineGetter__('primaryKeys', function() {
var result = {}
Utils._.each(this.attributes, function(dataTypeString, attributeName) {
if((attributeName != 'id') && (dataTypeString.indexOf('PRIMARY KEY') > -1))
result[attributeName] = dataTypeString
})
return result
})
// private
var query = function() {
var args = Utils._.map(arguments, function(arg, _) { return arg })
, s = this.modelFactoryManager.sequelize
// add this as the second argument
if(arguments.length == 1) args.push(this)
return s.query.apply(s, args)
}
var addOptionalClassMethods = function() {
var self = this
Utils._.each(this.options.classMethods || {}, function(fct, name) { self[name] = fct })
}
var addDefaultAttributes = function() {
var self = this
, defaultAttributes = {
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
}
}
if(this.hasPrimaryKeys) defaultAttributes = {}
if(this.options.timestamps) {
defaultAttributes[Utils._.underscoredIf('createdAt', this.options.underscored)] = {type: DataTypes.DATE, allowNull: false}
defaultAttributes[Utils._.underscoredIf('updatedAt', this.options.underscored)] = {type: DataTypes.DATE, allowNull: false}
if(this.options.paranoid)
defaultAttributes[Utils._.underscoredIf('deletedAt', this.options.underscored)] = {type: DataTypes.DATE}
}
Utils._.each(defaultAttributes, function(value, attr) {
self.rawAttributes[attr] = value
})
}
var findAutoIncrementField = function() {
var self = this
, fields = this.QueryGenerator.findAutoIncrementField(this)
this.autoIncrementField = null
fields.forEach(function(field) {
if(self.autoIncrementField)
throw new Error('Invalid model definition. Only one autoincrement field allowed.')
else
self.autoIncrementField = field
})
}
Utils._.extend(ModelFactory.prototype, require("./associations/mixin"))
return ModelFactory
})()
var Utils = require("./utils")
, Mixin = require("./associations/mixin")
, Validator = require("validator")
module.exports = (function() {
var Model = function(values, options) {
var self = this
this.attributes = []
this.validators = {} // holds validation settings for each attribute
this.__factory = null // will be set in Model.build
this.__options = Utils._.extend({
underscored: false,
hasPrimaryKeys: false,
timestamps: true,
paranoid: false
}, options || {})
initAttributes.call(this, values)
}
Utils._.extend(Model.prototype, Mixin.prototype)
Object.defineProperty(Model.prototype, 'sequelize', {
get: function(){ return this.__factory.modelFactoryManager.sequelize }
})
Object.defineProperty(Model.prototype, 'QueryInterface', {
get: function(){ return this.sequelize.getQueryInterface() }
})
Object.defineProperty(Model.prototype, 'isDeleted', {
get: function() {
var result = this.__options.timestamps && this.__options.paranoid
result = result && this[this.__options.underscored ? 'deleted_at' : 'deletedAt'] != null
return result
}
})
Object.defineProperty(Model.prototype, 'values', {
get: function() {
var result = {}
, self = this
this.attributes.forEach(function(attr) {
result[attr] = self[attr]
})
return result
}
})
Object.defineProperty(Model.prototype, 'primaryKeyValues', {
get: function() {
var result = {}
, self = this
Utils._.each(this.__factory.primaryKeys, function(_, attr) {
result[attr] = self[attr]
})
return result
}
})
Object.defineProperty(Model.prototype, "identifiers", {
get: function() {
var primaryKeys = Utils._.keys(this.__factory.primaryKeys)
, result = {}
, self = this
if(!this.__factory.hasPrimaryKeys)
primaryKeys = ['id']
primaryKeys.forEach(function(identifier) {
result[identifier] = self[identifier]
})
return result
}
})
Model.prototype.save = function() {
var updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt'
if(this.hasOwnProperty(updatedAtAttr))
this[updatedAtAttr] = new Date()
if(this.isNewRecord) {
return this.QueryInterface.insert(this, this.__factory.tableName, this.values)
} else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id
, tableName = this.__factory.tableName
, query = this.QueryInterface.update(this, tableName, this.values, identifier)
return query
}
}
/*
* Validate this model's attribute values according to validation rules set in the model definition.
*
* @return null if and only if validation successful; otherwise an object containing { field name : [error msgs] } entries.
*/
Model.prototype.validate = function() {
var self = this
var failures = {}
// for each field and value
Utils._.each(self.values, function(value, field) {
// if field has validators
if (self.validators.hasOwnProperty(field)) {
// for each validator
Utils._.each(self.validators[field], function(details, validatorType) {
var is_custom_fn = false // if true then it's a custom validation method
var fn_method = null // the validation function to call
var fn_args = [] // extra arguments to pass to validation function
var fn_msg = "" // the error message to return if validation fails
// is it a custom validator function?
if (Utils._.isFunction(details)) {
is_custom_fn = true
fn_method = Utils._.bind(details, self, value)
}
// is it a validator module function?
else {
// extra args
fn_args = details.hasOwnProperty("args") ? details.args : []
if (!Utils._.isArray(fn_args))
fn_args = [fn_args]
// error msg
fn_msg = details.hasOwnProperty("msg") ? details.msg : false
// check method exists
var v = Validator.check(value, fn_msg)
if (!Utils._.isFunction(v[validatorType]))
throw new Error("Invalid validator function: " + validatorType)
// bind to validator obj
fn_method = Utils._.bind(v[validatorType], v)
}
try {
fn_method.apply(null, fn_args)
} catch (err) {
err = err.message
// if we didn't provide a custom error message then augment the default one returned by the validator
if (!fn_msg && !is_custom_fn)
err += ": " + field
// each field can have multiple validation failures stored against it
if (failures.hasOwnProperty(field)) {
failures[field].push(err)
} else {
failures[field] = [err]
}
}
}) // for each validator for this field
} // if field has validator set
}) // for each field
return (Utils._.isEmpty(failures) ? null : failures)
}
Model.prototype.updateAttributes = function(updates) {
var self = this
var readOnlyAttributes = Utils._.keys(this.__factory.primaryKeys)
readOnlyAttributes.push('id')
readOnlyAttributes.push('createdAt')
readOnlyAttributes.push('updatedAt')
readOnlyAttributes.push('deletedAt')
Utils._.each(updates, function(value, attr) {
var updateAllowed = (
(readOnlyAttributes.indexOf(attr) == -1) &&
(readOnlyAttributes.indexOf(Utils._.underscored(attr)) == -1) &&
(self.attributes.indexOf(attr) > -1)
)
updateAllowed && (self[attr] = value)
})
return this.save()
}
Model.prototype.destroy = function() {
if(this.__options.timestamps && this.__options.paranoid) {
var attr = this.__options.underscored ? 'deleted_at' : 'deletedAt'
this[attr] = new Date()
return this.save()
} else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id
return this.QueryInterface.delete(this, this.__factory.tableName, identifier)
}
}
Model.prototype.equals = function(other) {
var result = true
, self = this
Utils._.each(this.values, function(value, key) {
result = result && (value == other[key])
})
return result
}
Model.prototype.equalsOneOf = function(others) {
var result = false
, self = this
others.forEach(function(other) { result = result || self.equals(other) })
return result
}
Model.prototype.addAttribute = function(attribute, value) {
this[attribute] = value
this.attributes.push(attribute)
}
Model.prototype.setValidators = function(attribute, validators) {
this.validators[attribute] = validators
}
// private
var initAttributes = function(values) {
var self = this
// add all passed values to the model and store the attribute names in this.attributes
Utils._.map(values, function(value, key) { self.addAttribute(key, value) })
// set id to null if not passed as value
// a newly created model has no id
var defaults = this.__options.hasPrimaryKeys ? {} : { id: null }
if(this.__options.timestamps) {
defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = new Date()
defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = new Date()
if(this.__options.paranoid)
defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null
}
Utils._.map(defaults, function(value, attr) {
if(!self.hasOwnProperty(attr))
self.addAttribute(attr, value)
})
}
/* Add the instance methods to Model */
Utils._.extend(Model.prototype, Mixin.prototype)
return Model
})()
var Utils = require("./utils")
module.exports = (function() {
var QueryChainer = function(emitters) {
var self = this
this.finishedEmits = 0
this.emitters = []
this.serials = []
this.fails = []
this.finished = false
this.wasRunning = false
this.eventEmitter = null
emitters = emitters || []
emitters.forEach(function(emitter) { self.add(emitter) })
}
Utils.addEventEmitter(QueryChainer)
QueryChainer.prototype.add = function(emitterOrKlass, method, params, options) {
if(!!method) {
this.serials.push({ klass: emitterOrKlass, method: method, params: params, options: options })
} else {
observeEmitter.call(this, emitterOrKlass)
this.emitters.push(emitterOrKlass)
}
return this
}
QueryChainer.prototype.run = function() {
var self = this
this.eventEmitter = new Utils.CustomEventEmitter(function() {
self.wasRunning = true
finish.call(self)
})
return this.eventEmitter.run()
}
QueryChainer.prototype.runSerially = function(options) {
var self = this
options = Utils._.extend({
skipOnError: false
}, options)
var exec = function() {
var serial = self.serials.pop()
if(serial) {
serial.options = serial.options || {}
serial.options.before && serial.options.before(serial.klass)
var onSuccess = function() {
serial.options.after && serial.options.after(serial.klass)
self.finishedEmits++
exec()
}
var onError = function(err) {
serial.options.after && serial.options.after(serial.klass)
self.finishedEmits++
self.fails.push(err)
exec()
}
if(options.skipOnError && (self.fails.length > 0)) {
onError('Skipped due to earlier error!')
} else {
var emitter = serial.klass[serial.method].apply(serial.klass, serial.params)
emitter.success(function() {
if(serial.options.success)
serial.options.success(serial.klass, onSuccess)
else
onSuccess()
}).error(onError)
}
} else {
self.wasRunning = true
finish.call(self)
}
}
this.serials.reverse()
this.eventEmitter = new Utils.CustomEventEmitter(exec)
return this.eventEmitter.run()
}
// private
var observeEmitter = function(emitter) {
var self = this
emitter
.success(function(){
self.finishedEmits++
finish.call(self)
})
.error(function(err){
self.finishedEmits++
self.fails.push(err)
finish.call(self)
})
}
var finish = function() {
this.finished = true
if(this.emitters.length > 0)
this.finished = (this.finishedEmits == this.emitters.length)
else if(this.serials.length > 0)
this.finished = (this.finishedEmits == this.serials.length)
if(this.finished && this.wasRunning) {
var status = (this.fails.length == 0 ? 'success' : 'failure')
, result = (this.fails.length == 0 ? result : this.fails)
this.eventEmitter.emit(status, result)
}
}
return QueryChainer
})()
var Utils = require('./utils')
, DataTypes = require('./data-types')
module.exports = (function() {
var QueryInterface = function(sequelize) {
this.sequelize = sequelize
this.QueryGenerator = require('./dialects/' + this.sequelize.options.dialect + '/query-generator')
}
Utils.addEventEmitter(QueryInterface)
QueryInterface.prototype.createTable = function(tableName, attributes, options) {
var attributeHashes = {}
Utils._.each(attributes, function(dataTypeOrOptions, attributeName) {
if(Utils._.values(DataTypes).indexOf(dataTypeOrOptions) > -1)
attributeHashes[attributeName] = { type: dataTypeOrOptions }
else
attributeHashes[attributeName] = dataTypeOrOptions
})
attributes = this.QueryGenerator.attributesToSQL(attributeHashes)
var sql = this.QueryGenerator.createTableQuery(tableName, attributes, options)
return queryAndEmit.call(this, sql, 'createTable')
}
QueryInterface.prototype.dropTable = function(tableName) {
var sql = this.QueryGenerator.dropTableQuery(tableName)
return queryAndEmit.call(this, sql, 'dropTable')
}
QueryInterface.prototype.dropAllTables = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
self.showAllTables().success(function(tableNames) {
tableNames.forEach(function(tableName) {
chainer.add(self.sequelize.getQueryInterface().dropTable(tableName))
})
chainer
.run()
.success(function() {
self.emit('dropAllTables', null)
emitter.emit('success', null)
})
.error(function(err) {
self.emit('dropAllTables', err)
emitter.emit('failure', err)
})
}).error(function(err) {
self.emit('dropAllTables', err)
emitter.emit('failure', err)
})
}).run()
}
QueryInterface.prototype.renameTable = function(before, after) {
var sql = this.QueryGenerator.renameTableQuery(before, after)
return queryAndEmit.call(this, sql, 'renameTable')
}
QueryInterface.prototype.showAllTables = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var showTablesSql = self.QueryGenerator.showTablesQuery()
self.sequelize.query(showTablesSql, null, { raw: true }).success(function(tableNames) {
self.emit('showAllTables', null)
emitter.emit('success', Sequelize.Utils._.flatten(tableNames))
}).error(function(err) {
self.emit('showAllTables', err)
emitter.emit('failure', err)
})
}).run()
}
QueryInterface.prototype.describeTable = function(tableName) {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
self.sequelize.query('DESCRIBE `' + tableName + '`;', null, { raw: true }).success(function(data) {
emitter.emit('success', data)
}).error(function(err) {
emitter.emit('failure', err)
})
}).run()
}
QueryInterface.prototype.addColumn = function(tableName, attributeName, dataTypeOrOptions) {
var attributes = {}
if(Utils._.values(DataTypes).indexOf(dataTypeOrOptions) > -1)
attributes[attributeName] = { type: dataTypeOrOptions, allowNull: false }
else
attributes[attributeName] = dataTypeOrOptions
var options = this.QueryGenerator.attributesToSQL(attributes)
, sql = this.QueryGenerator.addColumnQuery(tableName, options)
return queryAndEmit.call(this, sql, 'addColumn')
}
QueryInterface.prototype.removeColumn = function(tableName, attributeName) {
var sql = this.QueryGenerator.removeColumnQuery(tableName, attributeName)
return queryAndEmit.call(this, sql, 'removeColumn')
}
QueryInterface.prototype.changeColumn = function(tableName, attributeName, dataTypeOrOptions) {
var attributes = {}
if(Utils._.values(DataTypes).indexOf(dataTypeOrOptions) > -1)
attributes[attributeName] = { type: dataTypeOrOptions, allowNull: false }
else
attributes[attributeName] = dataTypeOrOptions
var options = this.QueryGenerator.attributesToSQL(attributes)
, sql = this.QueryGenerator.changeColumnQuery(tableName, options)
return queryAndEmit.call(this, sql, 'changeColumn')
}
QueryInterface.prototype.renameColumn = function(tableName, attrNameBefore, attrNameAfter) {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
self.describeTable(tableName).success(function(data) {
data = data.filter(function(h) { return h.Field == attrNameBefore })[0]
var options = {}
options[attrNameAfter] = {
type: data.Type,
allowNull: data.Null == 'YES',
defaultValue: data.Default
}
var sql = self.QueryGenerator.renameColumnQuery(tableName,
attrNameBefore,
self.QueryGenerator.attributesToSQL(options)
)
self.sequelize.query(sql).success(function() {
self.emit('renameColumn', null)
emitter.emit('success', null)
}).error(function(err) {
self.emit('renameColumn', err)
emitter.emit('failure', err)
})
}).error(function(err) {
self.emit('renameColumn', err)
emitter.emit('failure', err)
})
}).run()
}
QueryInterface.prototype.addIndex = function(tableName, attributes, options) {
var sql = this.QueryGenerator.addIndexQuery(tableName, attributes, options)
return queryAndEmit.call(this, sql, 'addIndex')
}
QueryInterface.prototype.showIndex = function(tableName, options) {
var sql = this.QueryGenerator.showIndexQuery(tableName, options)
return queryAndEmit.call(this, sql, 'showIndex')
}
QueryInterface.prototype.removeIndex = function(tableName, indexNameOrAttributes) {
var sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes)
return queryAndEmit.call(this, sql, "removeIndex")
}
QueryInterface.prototype.insert = function(model, tableName, values) {
var sql = this.QueryGenerator.insertQuery(tableName, values)
return queryAndEmit.call(this, [sql, model], 'insert', {
success: function(obj) { obj.isNewRecord = false }
})
}
QueryInterface.prototype.update = function(model, tableName, values, identifier) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier)
return queryAndEmit.call(this, [sql, model], 'update')
}
QueryInterface.prototype.delete = function(model, tableName, identifier) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier)
return queryAndEmit.call(this, [sql, model], 'delete')
}
QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) {
var sql = this.QueryGenerator.selectQuery(tableName, options)
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
}
QueryInterface.prototype.rawSelect = function(tableName, options, attributeSelector) {
var self = this
if(attributeSelector == undefined)
throw new Error('Please pass an attribute selector!')
return new Utils.CustomEventEmitter(function(emitter) {
var sql = self.QueryGenerator.selectQuery(tableName, options)
self.sequelize
.query(sql, null, { plain: true, raw: true })
.success(function(data) {
self.emit('rawSelect', null)
emitter.emit('success', data[attributeSelector])
})
.error(function(err) {
self.emit('rawSelect', err)
emitter.emit('failure', err)
})
}).run()
}
// private
var queryAndEmit = function(sqlOrQueryParams, methodName, options) {
var self = this
options = Utils._.extend({
success: function(obj){},
error: function(err){}
}, options || {})
return new Utils.CustomEventEmitter(function(emitter) {
var query = null
if(Array.isArray(sqlOrQueryParams))
query = self.sequelize.query.apply(self.sequelize, sqlOrQueryParams)
else
query = self.sequelize.query(sqlOrQueryParams)
// append the query for better testing
emitter.query = query
query.success(function(obj) {
options.success && options.success(obj)
self.emit(methodName, null)
emitter.emit('success', obj)
}).error(function(err) {
options.error && options.error(err)
self.emit(methodName, err)
emitter.emit('failure', err)
})
}).run()
}
return QueryInterface
})()
var Utils = require("./utils")
, ModelFactory = require("./model-factory")
, DataTypes = require('./data-types')
, ModelFactoryManager = require("./model-factory-manager")
, Migrator = require("./migrator")
, QueryInterface = require("./query-interface")
module.exports = (function() {
var Sequelize = function(database, username, password, options) {
this.options = Utils._.extend({
dialect: 'mysql',
host: 'localhost',
port: 3306,
define: {},
query: {},
sync: {}
}, options || {})
this.config = {
database: database,
username: username,
password: (( (["", null, false].indexOf(password) > -1) || (typeof password == 'undefined')) ? null : password),
host : this.options.host,
port : this.options.port
}
var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager")
this.modelFactoryManager = new ModelFactoryManager(this)
this.connectorManager = new ConnectorManager(this, this.config)
}
Sequelize.Utils = Utils
Sequelize.Utils._.map(DataTypes, function(sql, accessor) { Sequelize[accessor] = sql})
Sequelize.prototype.getQueryInterface = function() {
this.queryInterface = this.queryInterface || new QueryInterface(this)
return this.queryInterface
}
Sequelize.prototype.getMigrator = function(options, force) {
if(force)
this.migrator = new Migrator(this, options)
else
this.migrator = this.migrator || new Migrator(this, options)
return this.migrator
}
Sequelize.prototype.define = function(modelName, attributes, options) {
options = options || {}
if(this.options.define)
options = Sequelize.Utils.merge(options, this.options.define)
var factory = new ModelFactory(modelName, attributes, options)
this.modelFactoryManager.addModel(factory.init(this.modelFactoryManager))
return factory
}
Sequelize.prototype.import = function(path) {
var defineCall = require(path)
return defineCall(this, DataTypes)
}
Sequelize.prototype.migrate = function(options) {
this.getMigrator().migrate(options)
}
Sequelize.prototype.query = function(sql, callee, options) {
options = Utils._.extend(Utils._.clone(this.options.query), options || {})
options = Utils._.extend(options, {
logging: this.options.hasOwnProperty('logging') ? this.options.logging : true
})
return this.connectorManager.query(sql, callee, options)
}
Sequelize.prototype.sync = function(options) {
options = options || {}
if(this.options.sync)
options = Sequelize.Utils.merge(options, this.options.sync)
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer
self.modelFactoryManager.models.forEach(function(model) { chainer.add(model.sync(options)) })
chainer
.run()
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('failure', err) })
}).run()
}
Sequelize.prototype.drop = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer
self.modelFactoryManager.models.forEach(function(model) { chainer.add(model.drop()) })
chainer
.run()
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('failure', err) })
}).run()
}
return Sequelize
})()
var Utils = require("./utils")
, Model = require("./model")
, QueryGenerator = require("./query-generator")
, DataTypes = require("./data-types")
var ModelDefinition = module.exports = function(name, attributes, options) {
var self = this
this.options = options || {}
this.options.timestamps = this.options.hasOwnProperty('timestamps') ? this.options.timestamps : true
this.name = name
this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name)
this.attributes = Utils.simplifyAttributes(attributes)
this.rawAttributes = attributes
this.modelManager = null // defined by model-manager during addModel
this.associations = {}
this.addDefaultAttributes()
this.addOptionalClassMethods()
this.findAutoIncrementField()
}
Utils.addEventEmitter(ModelDefinition)
ModelDefinition.prototype.addOptionalClassMethods = function() {
var self = this
Utils._.each(this.options.classMethods || {}, function(fct, name) { self[name] = fct })
}
ModelDefinition.prototype.addDefaultAttributes = function() {
var defaultAttributes = {id: {type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true}}
, self = this
if(this.hasPrimaryKeys) defaultAttributes = {}
if(this.options.timestamps) {
defaultAttributes[Utils._.underscoredIf('createdAt', this.options.underscored)] = {type: DataTypes.DATE, allowNull: false}
defaultAttributes[Utils._.underscoredIf('updatedAt', this.options.underscored)] = {type: DataTypes.DATE, allowNull: false}
if(this.options.paranoid)
defaultAttributes[Utils._.underscoredIf('deletedAt', this.options.underscored)] = {type: DataTypes.DATE}
}
defaultAttributes = Utils.simplifyAttributes(defaultAttributes)
Utils._.map(defaultAttributes, function(value, attr) { self.attributes[attr] = value })
}
ModelDefinition.prototype.findAutoIncrementField = function() {
var self = this
this.autoIncrementField = null
Utils._.map(this.attributes, function(definition, name) {
if (definition && (definition.indexOf('auto_increment') > -1)) {
if (self.autoIncrementField)
throw new Error('Invalid model definition. Only one autoincrement field allowed.')
else
self.autoIncrementField = name
}
})
}
ModelDefinition.prototype.query = function() {
var args = Utils._.map(arguments, function(arg, _) { return arg })
, s = this.modelManager.sequelize
// add this as the second argument
if(arguments.length == 1) args.push(this)
return s.query.apply(s, args)
}
ModelDefinition.prototype.sync = function(options) {
options = Utils.merge(options || {}, this.options)
var self = this
var doQuery = function() {
self.query(QueryGenerator.createTableQuery(self.tableName, self.attributes, options))
.on('success', function() { self.emit('success', self) })
.on('failure', function(err) { self.emit('failure', err) })
}
if(options.force) {
this.drop()
.on('success', function() { doQuery() })
.on('failure', function(err) { self.emit('failure', err) })
} else {
doQuery()
}
return this
}
ModelDefinition.prototype.drop = function() {
return this.query(QueryGenerator.dropTableQuery(this.tableName, this.id))
}
ModelDefinition.prototype.__defineGetter__('all', function() {
return this.query(QueryGenerator.selectQuery(this.tableName))
})
ModelDefinition.prototype.count = function(options) {
var self = this
var emitter = new Utils.CustomEventEmitter(function() {
self.query(QueryGenerator.countQuery(self.tableName, options), self, {plain: true}).on('success', function(obj) {
emitter.emit('success', obj['count(*)'])
})
})
return emitter.run()
}
ModelDefinition.prototype.max = function(field, options) {
var self = this
var emitter = new Utils.CustomEventEmitter(function() {
self.query(QueryGenerator.maxQuery(self.tableName, field,options), self, {plain: true}).on('success', function(obj) {
emitter.emit('success', obj['max'])
})
})
return emitter.run()
}
ModelDefinition.prototype.min = function(field, options) {
var self = this
var emitter = new Utils.CustomEventEmitter(function() {
self.query(QueryGenerator.minQuery(self.tableName, field,options), self, {plain: true}).on('success', function(obj) {
emitter.emit('success', obj['min'])
})
})
return emitter.run()
}
ModelDefinition.prototype.findAll = function(options) {
return this.query(QueryGenerator.selectQuery(this.tableName, options))
}
//right now, the caller (has-many-double-linked) is in charge of the where clause
ModelDefinition.prototype.findAllJoin = function(joinTableName, options) {
optcpy = Utils._.clone(options)
optcpy.attributes = optcpy.attributes || [Utils.addTicks(this.tableName)+".*"]
return this.query(QueryGenerator.selectQuery([this.tableName, joinTableName], optcpy))
}
ModelDefinition.prototype.find = function(options) {
// options is not a hash but an id
if(typeof options == 'number')
options = {where: options}
else if (Utils.argsArePrimaryKeys(arguments, this.primaryKeys)) {
var where = {}
, self = this
Utils._.each(arguments, function(arg, i) {
var key = Utils._.keys(self.primaryKeys)[i]
where[key] = arg
})
options = {where: where}
} else if((options == null) || (options == undefined)) {
var NullEmitter = require("./null-emitter")
return new NullEmitter()
}
options.limit = 1
var query = QueryGenerator.selectQuery(this.tableName, options)
return this.query(query, this, {plain: true})
}
ModelDefinition.prototype.build = function(values, options) {
var instance = new Model(values, Utils._.extend(this.options, {hasPrimaryKeys: this.hasPrimaryKeys}))
, self = this
options = options || {}
instance.__definition = this
Utils._.map(this.attributes, function(definition, name) {
if(typeof instance[name] == 'undefined') {
var value = null
if(self.rawAttributes.hasOwnProperty(name) && self.rawAttributes[name].hasOwnProperty('defaultValue'))
value = self.rawAttributes[name].defaultValue
instance[name] = value
instance.addAttribute(name, value)
}
})
Utils._.each(this.options.instanceMethods || {}, function(fct, name) { instance[name] = fct })
Utils._.each(this.associations, function(association, associationName) {
association.injectGetter(instance)
association.injectSetter(instance)
})
instance.isNewRecord = options.hasOwnProperty('isNewRecord') ? options.isNewRecord : true
return instance
}
ModelDefinition.prototype.create = function(values) {
return this.build(values).save()
}
ModelDefinition.prototype.__defineGetter__('primaryKeys', function() {
var result = {}
Utils._.each(this.attributes, function(dataTypeString, attributeName) {
if((attributeName != 'id') && (dataTypeString.indexOf('PRIMARY KEY') > -1))
result[attributeName] = dataTypeString
})
return result
})
ModelDefinition.prototype.__defineGetter__('primaryKeyCount', function() {
return Utils._.keys(this.primaryKeys).length
})
ModelDefinition.prototype.__defineGetter__('hasPrimaryKeys', function() {
return this.primaryKeyCount > 0
})
Utils._.map(require("./associations/mixin").classMethods, function(fct, name) { ModelDefinition.prototype[name] = fct })
var Utils = require("./utils")
, Mixin = require("./associations/mixin")
, QueryGenerator = require("./query-generator")
var Model = module.exports = function(values, options) {
var self = this
this.__definition = null // will be set in Model.build
this.attributes = []
this.__options = options || {}
// add all passed values to the model and store the attribute names in this.attributes
Utils._.map(values, function(value, key) { self.addAttribute(key, value) })
// set id to null if not passed as value
// a newly created model has no id
var defaults = this.__options.hasPrimaryKeys ? {} : { id: null }
if(this.__options.timestamps) {
defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = new Date()
defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = new Date()
if(this.__options.paranoid)
defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null
}
Utils._.map(defaults, function(value, attr) {
if(!self.hasOwnProperty(attr))
self.addAttribute(attr, value)
})
}
Utils.addEventEmitter(Model)
Utils._.map(Mixin.classMethods, function(fct, name) { Model[name] = fct })
Model.Events = {
insert: 'InsertQuery',
update: 'UpdateQuery',
destroy: 'DestroyQuery'
}
Model.prototype.addAttribute = function(attribute, value) {
this[attribute] = value
this.attributes.push(attribute)
}
Model.prototype.query = function() {
var args = Utils._.map(arguments, function(arg, _) { return arg })
, s = this.__definition.modelManager.sequelize
args.push(this)
return s.query.apply(s, args)
}
Model.prototype.save = function() {
var attr = this.__options.underscored ? 'updated_at' : 'updatedAt'
if(this.hasOwnProperty(attr))
this[attr] = new Date()
if(this.isNewRecord) {
var self = this
var eventEmitter = new Utils.CustomEventEmitter(function() {
self.query(QueryGenerator.insertQuery(self.__definition.tableName, self.values))
.on('success', function(obj) {
obj.isNewRecord = false
eventEmitter.emit('success', obj)
})
.on('failure', function(err) { eventEmitter.emit('failure', err) })
.on('sql', function(sql) { eventEmitter.emit('sql', sql) })
})
return eventEmitter.run()
} else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id
return this.query(QueryGenerator.updateQuery(this.__definition.tableName, this.values, identifier))
}
}
Model.prototype.updateAttributes = function(updates) {
var self = this
var readOnlyAttributes = Utils._.keys(this.__definition.primaryKeys)
readOnlyAttributes.push('id')
readOnlyAttributes.push('createdAt')
readOnlyAttributes.push('updatedAt')
readOnlyAttributes.push('deletedAt')
Utils._.each(updates, function(value, attr) {
var updateAllowed = (
(readOnlyAttributes.indexOf(attr) == -1) &&
(readOnlyAttributes.indexOf(Utils._.underscored(attr)) == -1) &&
(self.attributes.indexOf(attr) > -1)
)
updateAllowed && (self[attr] = value)
})
return this.save()
}
Model.prototype.destroy = function() {
if(this.__options.timestamps && this.__options.paranoid) {
this[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = new Date()
return this.save()
} else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id
return this.query(QueryGenerator.deleteQuery(this.__definition.tableName, identifier))
}
}
Model.prototype.__defineGetter__("identifiers", function() {
var primaryKeys = Utils._.keys(this.__definition.primaryKeys)
, result = {}
, self = this
if(!this.__definition.hasPrimaryKeys)
primaryKeys = ['id']
primaryKeys.forEach(function(identifier) {
result[identifier] = self[identifier]
})
return result
})
Model.prototype.__defineGetter__('isDeleted', function() {
var result = this.__options.timestamps && this.__options.paranoid
result = result && this[this.__options.underscored ? 'deleted_at' : 'deletedAt'] != null
return result
})
Model.prototype.__defineGetter__('values', function() {
var result = {}
, self = this
this.attributes.forEach(function(attr) {
result[attr] = self[attr]
})
return result
})
Model.prototype.__defineGetter__('primaryKeyValues', function() {
var result = {}
, self = this
Utils._.each(this.__definition.primaryKeys, function(_, attr) {
result[attr] = self[attr]
})
return result
})
Model.prototype.equals = function(other) {
var result = true
, self = this
Utils._.each(this.values, function(value, key) {
result = result && (value == other[key])
})
return result
}
Model.prototype.equalsOneOf = function(others) {
var result = false
, self = this
others.forEach(function(other) { result = result || self.equals(other) })
return result
}
/* Add the instance methods to Model */
Utils._.map(Mixin.instanceMethods, function(fct, name) { Model.prototype[name] = fct})
var Utils = require("./utils")
var QueryChainer = module.exports = function(emitters) {
var self = this
this.finishedEmits = 0
this.emitters = []
this.fails = []
this.finished = false
this.runned = false
this.eventEmitter = null
emitters = emitters || []
emitters.forEach(function(emitter) { self.add(emitter) })
}
Utils.addEventEmitter(QueryChainer)
QueryChainer.prototype.add = function(emitter) {
this.observeEmitter(emitter)
this.emitters.push(emitter)
return this
}
QueryChainer.prototype.observeEmitter = function(emitter) {
var self = this
emitter
.on('success', function(){
self.finishedEmits++
self.finish()
})
.on('failure', function(err){
self.finishedEmits++
self.fails.push(err)
self.finish()
})
}
QueryChainer.prototype.finish = function(result) {
this.finished = (this.finishedEmits == this.emitters.length)
if(this.finished && this.runned) {
var status = this.fails.length == 0 ? 'success' : 'failure'
result = this.fails.length == 0 ? result : this.fails
this.eventEmitter.emit(status, result)
}
}
QueryChainer.prototype.run = function() {
var self = this
this.eventEmitter = new Utils.CustomEventEmitter(function() {
self.runned = true
self.finish()
})
return this.eventEmitter.run()
}
\ No newline at end of file
var Utils = require("./utils")
var QueryGenerator = module.exports = {
/*
Returns a query for creating a table.
Attributes should have the format: {attributeName: type, attr2: type2} --> {title: 'VARCHAR(255)'}
*/
createTableQuery: function(tableName, attributes, options) {
options = options || {}
var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>) ENGINE=<%= engine %> <%= charset %>"
, primaryKeys = []
, attrStr = Utils._.map(attributes, function(dataType, attr) {
var dt = dataType
if (Utils._.includes(dt, 'PRIMARY KEY')) {
primaryKeys.push(attr)
return Utils.addTicks(attr) + " " + dt.replace(/PRIMARY KEY/, '')
} else {
return Utils.addTicks(attr) + " " + dt
}
}).join(", ")
, values = {
table: Utils.addTicks(tableName),
attributes: attrStr,
engine: options.engine || 'InnoDB',
charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : "")
}
, pkString = primaryKeys.map(function(pk) {return Utils.addTicks(pk)}).join(", ")
if(pkString.length > 0) values.attributes += ", PRIMARY KEY (" + pkString + ")"
return Utils._.template(query)(values).trim() + ";"
},
dropTableQuery: function(tableName, options) {
options = options || {}
var query = "DROP TABLE IF EXISTS <%= table %>;"
return Utils._.template(query)({table: Utils.addTicks(tableName)})
},
/*
Returns a query for selecting elements in the database <tableName>.
Options:
- attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: *
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
- order -> e.g. 'id DESC'
- group
- limit -> The maximum count you want to get.
- offset -> An offset value to start from. Only useable with limit!
*/
selectQuery: function(tableName, options) {
options = options || {}
options.table = Array.isArray(tableName) ? tableName.map(function(tbl){return Utils.addTicks(tbl)}).join(", ") : Utils.addTicks(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr){return attr.indexOf(Utils.TICK_CHAR)<0 ? Utils.addTicks(attr) : attr}).join(", ")
options.attributes = options.attributes || '*'
var query = "SELECT <%= attributes %> FROM <%= table %>"
if(options.where) {
options.where = QueryGenerator.getWhereConditions(options.where)
query += " WHERE <%= where %>"
}
if(options.order) query += " ORDER BY <%= order %>"
if(options.group) {
options.group = Utils.addTicks(options.group)
query += " GROUP BY <%= group %>"
}
if(options.limit) {
if(options.offset) query += " LIMIT <%= offset %>, <%= limit %>"
else query += " LIMIT <%= limit %>"
}
query += ";"
return Utils._.template(query)(options)
},
countQuery: function(tableName, options) {
return QueryGenerator.selectQuery(tableName, options).replace("*", "count(*)")
},
maxQuery: function(tableName, field,options) {
return QueryGenerator.selectQuery(tableName ,options).replace("*", "max("+field+") as max")
},
minQuery: function(tableName, field,options) {
return QueryGenerator.selectQuery(tableName ,options).replace("*", "min("+field+") as min")
},
/*
Returns an insert into command. Parameters: table name + hash of attribute-value-pairs.
*/
insertQuery: function(tableName, attrValueHash) {
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>);"
var replacements = {
table: Utils.addTicks(tableName),
attributes: Utils._.keys(attrValueHash).map(function(attr){return Utils.addTicks(attr)}).join(","),
values: Utils._.values(attrValueHash).map(function(value){
return Utils.escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).join(",")
}
return Utils._.template(query)(replacements)
},
/*
Returns an update query.
Parameters:
- tableName -> Name of the table
- values -> A hash with attribute-value-pairs
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
*/
updateQuery: function(tableName, values, where) {
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
var replacements = {
table: Utils.addTicks(tableName),
values: Utils._.map(values, function(value, key){
return Utils.addTicks(key) + "=" + Utils.escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).join(","),
where: QueryGenerator.getWhereConditions(where)
}
return Utils._.template(query)(replacements)
},
/*
Returns a deletion query.
Parameters:
- tableName -> Name of the table
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
Options:
- limit -> Maximaum count of lines to delete
*/
deleteQuery: function(tableName, where, options) {
options = options || {}
options.limit = options.limit || 1
var query = "DELETE FROM <%= table %> WHERE <%= where %> LIMIT <%= limit %>"
var replacements = {
table: Utils.addTicks(tableName),
where: QueryGenerator.getWhereConditions(where),
limit: Utils.escape(options.limit)
}
return Utils._.template(query)(replacements)
},
/*
Takes something and transforms it into values of a where condition.
*/
getWhereConditions: function(smth) {
var result = null
if(Utils.isHash(smth))
result = QueryGenerator.hashToWhereConditions(smth)
else if(typeof smth == 'number')
result = Utils.addTicks('id') + "=" + Utils.escape(smth)
else if(typeof smth == "string")
result = smth
else if(Array.isArray(smth))
result = Utils.format(smth)
return result
},
/*
Takes a hash and transforms it into a mysql where condition: {key: value, key2: value2} ==> key=value AND key2=value2
The values are transformed by the relevant datatype.
*/
hashToWhereConditions: function(hash) {
return Utils._.map(hash, function(value, key) {
//handle qualified key names
var _key = key.split('.').map(function(col){return Utils.addTicks(col)}).join(".")
var _value = null
if(Array.isArray(value)) {
_value = "(" + Utils._.map(value, function(subvalue) {
return Utils.escape(subvalue);
}).join(',') + ")"
return [_key, _value].join(" IN ")
}
else if ((value) && (typeof value == 'object')) {
//using as sentinel for join column => value
_value = value.join.split('.').map(function(col){return Utils.addTicks(col)}).join(".")
return [_key, _value].join("=")
} else {
_value = Utils.escape(value)
return (_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("=")
}
}).join(" AND ")
}
}
var Utils = require("./utils")
var Query = module.exports = function(client, callee, options) {
var self = this
this.client = client
this.callee = callee
this.options = options || {}
this.bindClientFunction = function(err) { self.onFailure(err) }
}
Utils.addEventEmitter(Query)
Query.prototype.run = function(query) {
var self = this
this.sql = query
this.bindClient()
if(this.options.logging)
console.log('Executing: ' + this.sql)
this.client.query(this.sql, function(err, results, fields) {
//allow clients to listen to sql to do their own logging or whatnot
self.emit('sql', self.sql)
err ? self.onFailure(err) : self.onSuccess(self.sql, results, fields)
}).setMaxListeners(100)
return this
}
Query.prototype.bindClient = function() {
this.client.on('error', this.bindClientFunction)
}
Query.prototype.unbindClient = function() {
this.client.removeListener('error', this.bindClientFunction)
}
Query.prototype.onSuccess = function(query, results, fields) {
var result = this.callee
, self = this
// add the inserted row id to the instance
if (this.callee && (query.indexOf('INSERT INTO') == 0) && (results.hasOwnProperty('insertId')))
this.callee[this.callee.__definition.autoIncrementField] = results.insertId
// transform results into real model instances
// return the first real model instance if options.plain is set (e.g. Model.find)
if (query.indexOf('SELECT') == 0) {
result = results.map(function(result) { return self.callee.build(result, {isNewRecord: false}) })
if(this.options.plain)
result = (result.length == 0) ? null : result[0]
}
this.unbindClient()
this.emit('success', result)
}
Query.prototype.onFailure = function(err) {
this.unbindClient()
this.emit('failure', err, this.callee)
}
var Utils = require("./utils")
, ModelDefinition = require("./model-definition")
, DataTypes = require('./data-types')
, ModelManager = require("./model-manager")
, ConnectorManager = require('./connector-manager')
var Sequelize = module.exports = function(database, username, password, options) {
options = options || {}
Utils._.reject(options, function(_, key) {
return ["host", "port", "disableTableNameModification"].indexOf(key) > -1
})
this.options = options
this.config = {
database: database,
username: username,
password: (( (["", null, false].indexOf(password) > -1) || (typeof password == 'undefined')) ? null : password),
host : options.host || 'localhost',
port : options.port || 3306
}
this.modelManager = new ModelManager(this)
this.connectorManager = new ConnectorManager(this.config)
}
Sequelize.Utils = Utils
var instanceMethods = {
define: function(modelName, attributes, options) {
options = options || {}
if(this.options.define)
options = Sequelize.Utils.merge(options, this.options.define)
var model = this.modelManager.addModel(new ModelDefinition(modelName, attributes, options))
return model
},
import: function(path) {
var defineCall = require(path)
return defineCall(this, DataTypes)
},
query: function(sql, callee, options) {
options = options || {}
if(this.options.query)
options = Sequelize.Utils.merge(options, this.options.query)
options.logging = this.options.hasOwnProperty('logging') ? this.options.logging : true
return this.connectorManager.query(sql, callee, options)
},
sync: function(options) {
options = options || {}
if(this.options.sync)
options = Sequelize.Utils.merge(options, this.options.sync)
var self = this
var eventEmitter = new Utils.CustomEventEmitter(function() {
var chainer = new Utils.QueryChainer
self.modelManager.models.forEach(function(model) { chainer.add(model.sync(options)) })
chainer
.run()
.on('success', function() { eventEmitter.emit('success', null) })
.on('failure', function(err) { eventEmitter.emit('failure', err) })
})
return eventEmitter.run()
},
drop: function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer
self.modelManager.models.forEach(function(model) { chainer.add(model.drop()) })
chainer
.run()
.on('success', function() { emitter.emit('success', null) })
.on('failure', function(err) { emitter.emit('failure', err) })
}).run()
}
}
Sequelize.Utils._.map(DataTypes, function(sql, accessor) { Sequelize[accessor] = sql})
Sequelize.Utils._.map(instanceMethods, function(fct, name) { Sequelize.prototype[name] = fct})
var client = new (require("mysql").Client)() var client = new (require("mysql").Client)()
, util = require("util")
var Utils = module.exports = { var Utils = module.exports = {
_: (function() { _: (function() {
var _ = require("underscore") var _ = require("underscore")
, _s = require('underscore.string')
_.mixin(require('underscore.string')) _.mixin(_s.exports())
_.mixin({ _.mixin({
includes: _s.include,
camelizeIf: function(string, condition) { camelizeIf: function(string, condition) {
var result = string var result = string
if(condition) result = _.camelize(string) if(condition) result = _.camelize(string)
...@@ -20,11 +24,11 @@ var Utils = module.exports = { ...@@ -20,11 +24,11 @@ var Utils = module.exports = {
return _ return _
})(), })(),
addEventEmitter: function(_class) { addEventEmitter: function(_class) {
require("sys").inherits(_class, require('events').EventEmitter) util.inherits(_class, require('events').EventEmitter)
}, },
TICK_CHAR: '`', TICK_CHAR: '`',
addTicks: function(s) { addTicks: function(s) {
return '`' + Utils.removeTicks(s) + '`' return Utils.TICK_CHAR + Utils.removeTicks(s) + Utils.TICK_CHAR
}, },
removeTicks: function(s) { removeTicks: function(s) {
return s.replace("`", "") return s.replace("`", "")
...@@ -41,71 +45,6 @@ var Utils = module.exports = { ...@@ -41,71 +45,6 @@ var Utils = module.exports = {
isHash: function(obj) { isHash: function(obj) {
return (typeof obj == 'object') && !obj.hasOwnProperty('length') return (typeof obj == 'object') && !obj.hasOwnProperty('length')
}, },
getDataTypeForValue: function(value) {
var DataTypes = require("./data-types")
switch(typeof value) {
case 'number':
return (value.toString().indexOf('.') > -1) ? DataTypes.FLOAT : DataTypes.INTEGER
break
case 'boolean':
return DataTypes.BOOLEAN
break
case 'object':
return (value.getMilliseconds) ? DataTypes.DATE : "WTF!"
break
default:
return DataTypes.TEXT
break
}
},
transformValueByDataType: function(value, dataType) {
dataType = dataType || Utils.getDataTypeForValue(value)
var DataTypes = require("./data-types")
if((value == null)||(typeof value == 'undefined')||((dataType.indexOf(DataTypes.INTEGER) > -1) && isNaN(value)))
return "NULL"
if(dataType.indexOf(DataTypes.FLOAT) > -1)
return (typeof value == 'number') ? value : parseFloat(value.replace(",", "."))
if(dataType.indexOf(DataTypes.BOOLEAN) > -1)
return (value === true ? 1 : 0)
if(dataType.indexOf(DataTypes.INTEGER) > -1)
return value
if(dataType.indexOf(DataTypes.DATE) > -1)
return ("'" + Utils.asSqlDate(value) + "'")
return ("'" + value + "'")
},
simplifyAttributes: function(attributes) {
var result = {}
Utils._.map(attributes, function(dataType, name) {
if(Utils.isHash(dataType)) {
var template = "<%= type %>"
, replacements = { type: dataType.type }
if(dataType.hasOwnProperty('allowNull') && (!dataType.allowNull)) template += " NOT NULL"
if(dataType.autoIncrement) template +=" auto_increment"
if(dataType.defaultValue != undefined) {
template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = Utils.escape(dataType.defaultValue)
}
if(dataType.unique) template += " UNIQUE"
if(dataType.primaryKey) template += " PRIMARY KEY"
result[name] = Utils._.template(template)(replacements)
} else {
result[name] = dataType
}
})
return result
},
toSqlDate: function(date) { toSqlDate: function(date) {
return [ return [
[ [
...@@ -143,20 +82,15 @@ var Utils = module.exports = { ...@@ -143,20 +82,15 @@ var Utils = module.exports = {
a[key] = b[key] a[key] = b[key]
} }
return a return a
} },
} removeCommentsFromFunctionString: function(s) {
s = s.replace(/\s*(\/\/.*)/g, '')
// Some nice class accessors s = s.replace(/(\/\*[\n\r\s\S]*?\*\/)/mg, '')
var CustomEventEmitter = Utils.CustomEventEmitter = function(fct) {
this.fct = fct
}
Utils.addEventEmitter(CustomEventEmitter)
CustomEventEmitter.prototype.run = function() { return s
var self = this }
setTimeout(function(){ self.fct.call(self, self) }, 5) // delay the function call and return the emitter
return this
} }
Utils.CustomEventEmitter = require("./emitters/custom-event-emitter")
Utils.QueryChainer = require("./query-chainer") Utils.QueryChainer = require("./query-chainer")
Utils.Lingo = require("lingo") Utils.Lingo = require("lingo")
{ {
"name": "sqlize", "name": "sequelize",
"description": "Modified version of sequelize - MySQL ORM for Node.JS", "description": "MySQL ORM for Node.JS",
"version": "0.2.3", "version": "1.3.0",
"author": "Meg Sharkey <meg@metamx.com>", "author": "Sascha Depold <sascha@depold.com>",
"contributors": [ "contributors": [
{ "name": "Sascha Depold", "email": "sascha@depold.com" }, { "name": "Sascha Depold", "email": "sascha@depold.com" },
{ "name": "Meg Sharkey", "email": "meg@metamx.com" } { "name": "Meg Sharkey", "email": "meg@metamx.com" }
], ],
"dependencies": { "dependencies": {
"mysql": "=0.9.4", "mysql": "0.9.x",
"underscore": "=1.1.5", "underscore": "1.2.x",
"underscore.string": "=1.1.3", "underscore.string": "2.0.x",
"lingo": "=0.0.4" "lingo": "0.0.x",
"validator": "0.3.x",
"moment": "1.1.x",
"commander": "0.5.x"
}, },
"devDependencies": { "devDependencies": {
"jasmine-node": "=1.0.12", "jasmine-node": "1.0.x",
"expresso": "=0.9.2" "sqlite3": ">=2.0.0"
}, },
"keywords": [], "keywords": [],
"main": "index", "main": "index",
"scripts": {}, "scripts": {},
"bin": {}, "bin": {
"engines": { "node": ">= 0.1.93" } "sequelize": "bin/sequelize"
},
"engines": {
"node": ">=0.4.6"
},
"license": "MIT"
} }
module.exports = {
up: function(migration, DataTypes) {
migration.createTable('Person', {
name: DataTypes.STRING,
isBetaMember: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
}
})
},
down: function(migration) {
migration.dropTable('Person')
}
}
module.exports = {
up: function() {},
down: function() {}
}
module.exports = {
up: function(migration, DataTypes) {
migration.renameTable('Person', 'User')
},
down: function(migration, DataTypes) {
migration.renameTable('User', 'Person')
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.addColumn('User', 'signature', DataTypes.TEXT)
migration.addColumn('User', 'shopId', { type: DataTypes.INTEGER, allowNull: true })
migration.addColumn('User', 'isAdmin', { type: DataTypes.BOOLEAN, defaultValue: false, allowNull: false })
},
down: function(migration, DataTypes) {
migration.removeColumn('User', 'signature')
migration.removeColumn('User', 'shopId')
migration.removeColumn('User', 'isAdmin')
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.removeColumn('User', 'shopId')
},
down: function(migration, DataTypes) {
migration.addColumn('User', 'shopId', { type: DataTypes.INTEGER, allowNull: true })
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.changeColumn('User', 'signature', {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'Signature'
})
},
down: function(migration, DataTypes) {}
}
module.exports = {
up: function(migration, DataTypes) {
migration.renameColumn('User', 'signature', 'sig')
},
down: function(migration, DataTypes) {
migration.renameColumn('User', 'sig', 'signature')
}
}
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('BelongsTo', function() {
var User = null
, Task = null
var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
it('adds the foreign key', function() {
Task.belongsTo(User)
expect(Task.attributes['UserId']).toEqual("INTEGER")
})
it("underscores the foreign key", function() {
Task = sequelize.define('Task', { title: Sequelize.STRING }, {underscored: true})
Task.belongsTo(User)
expect(Task.attributes['user_id']).toEqual("INTEGER")
})
it("uses the passed foreign key", function() {
Task.belongsTo(User, {foreignKey: 'person_id'})
expect(Task.attributes['person_id']).toEqual("INTEGER")
})
it("defines getters and setters", function() {
Task.belongsTo(User)
var task = Task.build({title: 'asd'})
expect(task.setUser).toBeDefined()
expect(task.getUser).toBeDefined()
})
it("aliases the getters and setters according to the passed 'as' option", function() {
Task.belongsTo(User, {as: 'Person'})
var task = Task.build({title: 'asd'})
expect(task.setPerson).toBeDefined()
expect(task.getPerson).toBeDefined()
})
it("intializes the foreign key with null", function() {
Task.belongsTo(User)
var task = Task.build({title: 'asd'})
expect(task['UserId']).toBeNull()
})
it("sets and gets the correct objects", function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd'}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser().success(function(user) {
expect(user.username).toEqual('asd')
done()
})
})
})
})
})
})
it("handles self associations", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.belongsTo(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).success(function() {
var p = Person.build()
expect(p.setFather).toBeDefined()
expect(p.setMother).toBeDefined()
done()
})
})
})
it("sets the foreign key in self associations", function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother'})
expect(Person.associations.MotherPersons.options.foreignKey).toEqual('MotherId')
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
var User = null
, Task = null
, sequelize = null
, Helpers = null
var setup = function() {
sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
Helpers = new (require("../config/helpers"))(sequelize)
Helpers.dropAllTables()
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { setup() })
afterEach(function() { Helpers.dropAllTables() })
describe('mono-directional', function() {
it("adds the foreign key", function() {
User.hasMany(Task)
expect(Task.attributes.UserId).toEqual("INTEGER")
})
it('adds the foreign key with underscore', function() {
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING }, { underscored: true })
Task.hasMany(User)
expect(User.attributes.task_id).toBeDefined()
})
it('uses the passed foreign key', function() {
User.hasMany(Task, { foreignKey: 'person_id' })
expect(Task.attributes.person_id).toEqual("INTEGER")
})
it('defines getters and setters', function() {
User.hasMany(Task)
var u = User.build({username: 'asd'})
expect(u.setTasks).toBeDefined()
expect(u.getTasks).toBeDefined()
})
it("defines getters and setters according to the 'as' option", function() {
User.hasMany(Task, {as: 'Tasks'})
var u = User.build({username: 'asd'})
expect(u.setTasks).toBeDefined()
expect(u.getTasks).toBeDefined()
})
it("sets and gets associated objects", function() {
var user, task1, task2;
User.hasMany(Task, { as: 'Tasks' })
Helpers.async(function(done) {
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'task1'}).success(function(_task1) {
Task.create({title: 'task2'}).success(function(_task2) {
user = _user
task1 = _task1
task2 = _task2
done()
})
})
})
})
Helpers.async(function(done) {
user.setTasks([task1, task2]).success(function() {
user.getTasks().success(function(tasks) {
expect(tasks.length).toEqual(2)
done()
})
})
})
})
})
describe("when a join table name is specified", function() {
var Table2 = sequelize.define('ms_table1', {foo: Sequelize.STRING})
, Table1 = sequelize.define('ms_table2', {foo: Sequelize.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
it("should not use a combined name", function() {
expect(sequelize.modelManager.getModel('ms_table1sms_table2s')).toBeUndefined()
})
it("should use the specified name", function() {
expect(sequelize.modelManager.getModel('table1_to_table2')).toBeDefined()
})
})
describe('bi-directional', function() {
it('adds the foreign key', function() {
Task.hasMany(User)
User.hasMany(Task)
expect(Task.attributes.UserId).toBeUndefined()
expect(User.attributes.UserId).toBeUndefined()
var models = sequelize.modelFactoryManager.models.filter(function(model) {
return (model.tableName == (Task.tableName + User.tableName))
})
models.forEach(function(model) {
expect(model.attributes.UserId).toBeDefined()
expect(model.attributes.TaskId).toBeDefined()
})
})
it("adds the foreign key with underscores", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, { underscored: true })
Task = sequelize.define('Task', { title: Sequelize.STRING })
Task.hasMany(User)
User.hasMany(Task)
expect(Task.attributes.user_id).toBeUndefined()
expect(User.attributes.user_id).toBeUndefined()
var models = sequelize.modelFactoryManager.models.filter(function(model) {
return (model.tableName == (Task.tableName + User.tableName))
})
models.forEach(function(model) {
expect(model.attributes.user_id).toBeDefined()
expect(model.attributes.TaskId).toBeDefined()
})
})
it("uses the passed foreign keys", function() {
User.hasMany(Task, { foreignKey: 'person_id' })
Task.hasMany(User, { foreignKey: 'work_item_id' })
var models = sequelize.modelFactoryManager.models.filter(function(model) {
return (model.tableName == (Task.tableName + User.tableName))
})
models.forEach(function(model) {
expect(model.attributes.person_id).toBeDefined()
expect(model.attributes.work_item_id).toBeDefined()
})
})
it("defines getters and setters", function() {
User.hasMany(Task)
Task.hasMany(User)
var u = User.build({ username: 'asd' })
expect(u.setTasks).toBeDefined()
expect(u.getTasks).toBeDefined()
var t = Task.build({ title: 'foobar' })
expect(t.setUsers).toBeDefined()
expect(t.getUsers).toBeDefined()
})
it("defines getters and setters according to the 'as' option", function() {
User.hasMany(Task, { as: 'Tasks' })
Task.hasMany(User, { as: 'Users' })
var u = User.build({ username: 'asd' })
expect(u.setTasks).toBeDefined()
expect(u.getTasks).toBeDefined()
var t = Task.build({ title: 'asd' })
expect(t.setUsers).toBeDefined()
expect(t.getUsers).toBeDefined()
})
it("sets and gets the corrected associated objects", function() {
var users = []
, tasks = []
User.hasMany(Task, {as: 'Tasks'})
Task.hasMany(User, {as: 'Users'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'name'}).success(function(user1) {
User.create({username: 'name2'}).success(function(user2) {
Task.create({title: 'task1'}).success(function(task1) {
Task.create({title: 'task2'}).success(function(task2) {
users.push(user1)
users.push(user2)
tasks.push(task1)
tasks.push(task2)
done()
})
})
})
})
})
Helpers.async(function(done) {
users[0].setTasks(tasks).success(function() {
users[0].getTasks().success(function(_tasks) {
expect(_tasks.length).toEqual(2)
tasks[1].setUsers(users).success(function() {
tasks[1].getUsers().success(function(_users) {
expect(users.length).toEqual(2)
done()
})
})
})
})
})
})
})
it("build the connector models name", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasMany(Person, {as: 'Children'})
Person.hasMany(Person, {as: 'Friends'})
Person.hasMany(Person, {as: 'CoWorkers'})
Person.sync({force: true}).success(function() {
var modelNames = sequelize.modelFactoryManager.models.map(function(model) { return model.tableName })
, expectation = ["Persons", "ChildrenPersons", "CoWorkersPersons", "FriendsPersons"]
expectation.forEach(function(ex) {
expect(modelNames.indexOf(ex) > -1).toBeTruthy()
})
done()
})
})
})
it("gets and sets the connector models", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasMany(Person, {as: 'Children'})
Person.hasMany(Person, {as: 'Friends'})
Person.hasMany(Person, {as: 'CoWorkers'})
Person.sync({force: true}).success(function() {
Person.create({name: 'foobar'}).success(function(person) {
Person.create({name: 'friend'}).success(function(friend) {
person.setFriends([friend]).success(function() {
person.getFriends().success(function(friends) {
expect(friends.length).toEqual(1)
expect(friends[0].name).toEqual('friend')
done()
})
})
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasOne', function() {
var User = null
, Task = null
var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
it("adds the foreign key", function() {
User.hasOne(Task)
expect(Task.attributes.UserId).toEqual("INTEGER")
})
it("adds an underscored foreign key", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, {underscored: true})
Task = sequelize.define('Task', { title: Sequelize.STRING })
User.hasOne(Task)
expect(Task.attributes.user_id).toEqual("INTEGER")
})
it("uses the passed foreign key", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, {underscored: true})
Task = sequelize.define('Task', { title: Sequelize.STRING })
User.hasOne(Task, {foreignKey: 'person_id'})
expect(Task.attributes.person_id).toEqual("INTEGER")
})
it("defines the getter and the setter", function() {
User.hasOne(Task)
var u = User.build({username: 'asd'})
expect(u.setTask).toBeDefined()
expect(u.getTask).toBeDefined()
})
it("defined the getter and the setter according to the passed 'as' option", function() {
User.hasOne(Task, {as: 'Work'})
var u = User.build({username: 'asd'})
expect(u.setWork).toBeDefined()
expect(u.getWork).toBeDefined()
})
it("gets and sets the correct objects", function() {
var user, task;
User.hasOne(Task, {as: 'Task'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(function() {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'snafu'}).success(function(_task) {
user = _user
task = _task
done()
})
})
})
})
})
Helpers.async(function(done) {
user.setTask(task).on('success', function() {
user.getTask().on('success', function(task2) {
expect(task.title).toEqual(task2.title)
done()
})
})
})
})
it("unsets unassociated objects", function() {
var user, task1, task2;
User.hasOne(Task, {as: 'Task'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(function() {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'snafu'}).success(function(_task1) {
Task.create({title: 'another task'}).success(function(_task2) {
user = _user
task1 = _task1
task2 = _task2
done()
})
})
})
})
})
})
Helpers.async(function(done) {
user.setTask(task1).success(function() {
user.getTask().success(function(_task) {
expect(task1.title).toEqual(_task.title)
user.setTask(task2).success(function() {
user.getTask().success(function(_task2) {
expect(task2.title).toEqual(task2.title)
done()
})
})
})
})
})
})
it("sets self associations", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.hasOne(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).success(function() {
var p = Person.build()
expect(p.setFather).toBeDefined()
expect(p.setMother).toBeDefined()
done()
})
})
})
it("automatically sets the foreign key on self associations", function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother'})
expect(Person.associations.MotherPersons.options.foreignKey).toEqual('MotherId')
})
})
...@@ -6,16 +6,32 @@ var Factories = module.exports = function(helpers) { ...@@ -6,16 +6,32 @@ var Factories = module.exports = function(helpers) {
Factories.prototype.Model = function(modelName, options, callback, count) { Factories.prototype.Model = function(modelName, options, callback, count) {
count = count || 1 count = count || 1
var self = this var self = this
, models = []
this.helpers.async(function(done) { this.helpers.async(function(done) {
self.sequelize.modelManager.getModel(modelName).create(options).on('success', function(model){ var Model = self.sequelize.modelFactoryManager.getModel(modelName)
done()
--count ? self.Model(modelName, options, callback, count) : (callback && callback(model)) var create = function(cb) {
}).on('failure', function(err) { Model.create(options).on('success', function(model) {
console.log(err) models.push(model)
done() cb && cb()
}) }).on('failure', function(err) {
console.log(err)
done()
})
}
var cb = function() {
if(--count) {
create(cb)
} else {
done()
callback && callback(models)
}
}
create(cb)
}) })
} }
......
Sequelize = require("../../index")
var Helpers = module.exports = function(sequelize) { var Helpers = module.exports = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
this.Factories = new (require("./factories"))(this) this.Factories = new (require("./factories"))(this)
...@@ -8,8 +10,8 @@ Helpers.prototype.sync = function() { ...@@ -8,8 +10,8 @@ Helpers.prototype.sync = function() {
this.async(function(done) { this.async(function(done) {
self.sequelize self.sequelize
.sync({force: true}) .sync({force: true})
.on('success', done) .success(done)
.on('failure', function(err) { console.log(err) }) .failure(function(err) { console.log(err) })
}) })
} }
...@@ -23,6 +25,18 @@ Helpers.prototype.drop = function() { ...@@ -23,6 +25,18 @@ Helpers.prototype.drop = function() {
}) })
} }
Helpers.prototype.dropAllTables = function() {
var self = this
this.async(function(done) {
self.sequelize
.getQueryInterface()
.dropAllTables()
.success(done)
.error(function(err) { console.log(err) })
})
}
Helpers.prototype.async = function(fct) { Helpers.prototype.async = function(fct) {
var done = false var done = false
runs(function() { runs(function() {
......
var config = require("./config/config")
, Sequelize = require("../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("./config/helpers"))(sequelize)
, Migrator = require("../lib/migrator")
, Migration = require("../lib/migration")
, _ = Sequelize.Utils._
describe('Migration', function() {
describe('migrationHasInterfaceCalls', function() {
// the syntax in the following tests are correct
// don't touch them! the functions will get stringified below
var tests = [
{
topic: function(migration, DataTypes) {
migration.createTable()
},
expectation: true
},
{
topic: function(migration, DataTypes) {
// migration.createTable()
},
expectation: false
},
{
topic: function(migration, DataTypes) {
migration
.createTable()
},
expectation: true
},
{
topic: function(migration, DataTypes) {
migration.
createTable()
},
expectation: true
},
{
topic: function(migration, DataTypes) {
migration . createTable ()
},
expectation: true
},
{
topic: function(migration, DataTypes) {
/*
migration . createTable()
*/
},
expectation: false
},
{
topic: function(migration, DataTypes) {
migration/* noot noot */.createTable()
},
expectation: true
}
]
tests.forEach(function(test) {
it('correctly result in ' + test.expectation + ' for ' + test.topic.toString(), function() {
expect(Migration.migrationHasInterfaceCalls(test.topic)).toEqual(test.expectation)
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("./config/helpers"))(sequelize)
, Migrator = require("../lib/migrator")
, _ = Sequelize.Utils._
describe('Migrator', function() {
var migrator = null
, SequelizeMeta = null
var setup = function(_options) {
Helpers.async(function(done) {
var options = Sequelize.Utils._.extend({
path: __dirname + '/assets/migrations',
logging: false
}, _options || {})
migrator = new Migrator(sequelize, options)
migrator
.findOrCreateSequelizeMetaModel({ force: true })
.success(function(_SequelizeMeta) {
SequelizeMeta = _SequelizeMeta
done()
})
.error(function(err) { console.log(err) })
})
}
var reset = function() {
migrator = null
Helpers.dropAllTables()
}
beforeEach(reset)
afterEach(reset)
describe('getUndoneMigrations', function() {
it("returns no files if timestamps are after the files timestamp", function() {
setup({ from: 20120101010101 })
Helpers.async(function(done) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(0)
done()
})
})
})
it("returns only files between from and to", function() {
setup({ from: 19700101000000, to: 20111117063700 })
Helpers.async(function(done) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(1)
expect(_.last(migrations).filename).toEqual('20111117063700-createPerson.js')
done()
})
})
})
it("returns also the file which is exactly options.from or options.to", function() {
setup({ from: 20111117063700, to: 20111130161100 })
Helpers.async(function(done) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(2)
expect(migrations[0].filename).toEqual('20111117063700-createPerson.js')
expect(migrations[1].filename).toEqual('20111130161100-emptyMigration.js')
done()
})
})
})
it("returns all files to options.to if no options.from is defined", function() {
setup({ to: 20111130161100 })
Helpers.async(function(done) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(2)
done()
})
})
})
it("returns all files from last migration id stored in database", function() {
setup()
Helpers.async(function(done) {
SequelizeMeta.create({ from: null, to: '20111117063700' }).success(function() {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(6)
expect(migrations[0].filename).toEqual('20111130161100-emptyMigration.js')
done()
})
})
})
})
})
describe('migrations', function() {
beforeEach(function() {
setup({ from: 20111117063700, to: 20111117063700 })
Helpers.async(function(done) {
migrator.migrate().success(done).error(function(err) { console.log(err) })
})
})
describe('executions', function() {
it("executes migration #20111117063700 and correctly creates the table", function() {
Helpers.async(function(done) {
sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames.length).toEqual(1)
expect(tableNames[0]).toEqual('Person')
done()
})
})
})
it("executes migration #20111117063700 and correctly adds isBetaMember", function() {
Helpers.async(function(done) {
sequelize.getQueryInterface().describeTable('Person').success(function(data) {
var beta = data.filter(function(d) { return d.Field == 'isBetaMember'})
expect(beta).toBeDefined()
done()
})
})
})
it("executes migration #20111117063700 correctly up (createTable) and downwards (dropTable)", function() {
Helpers.async(function(done) {
sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames.length).toEqual(1)
done()
})
})
Helpers.async(function(done) {
migrator.migrate({ method: 'down' }).success(function() {
sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames.length).toEqual(0)
done()
}).error(function(err){ console.log(err); done() })
}).error(function(err){ console.log(err); done() })
})
})
it("executes the empty migration #20111130161100", function() {
Helpers.async(function(done) {
setup({ from: 20111130161100, to: 20111130161100})
done()
})
Helpers.async(function(done) {
migrator.migrate().success(done).error(function(err) { console.log(err) })
// this migration isn't actually testing anything but
// should not timeout
})
})
})
describe('renameTable', function() {
it("executes migration #20111205064000 and renames a table", function() {
Helpers.async(function(done) {
sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames.length).toEqual(1)
expect(tableNames[0]).toEqual('Person')
done()
})
})
setup({from: 20111205064000, to: 20111205064000})
Helpers.async(function(done) {
migrator.migrate().success(done).error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames.length).toEqual(1)
expect(tableNames[0]).toEqual('User')
done()
})
})
})
})
describe('addColumn', function() {
it('adds a column to the user table', function() {
setup({from: 20111205064000, to: 20111205162700})
Helpers.async(function(done) {
migrator.migrate().success(done).error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.filter(function(hash){ return hash.Field == 'signature' })[0]
, isAdmin = data.filter(function(hash){ return hash.Field == 'isAdmin' })[0]
, shopId = data.filter(function(hash){ return hash.Field == 'shopId' })[0]
expect(signature.Field).toEqual('signature')
expect(signature.Null).toEqual('NO')
expect(isAdmin.Field).toEqual('isAdmin')
expect(isAdmin.Null).toEqual('NO')
expect(isAdmin.Default).toEqual('0')
expect(shopId.Field).toEqual('shopId')
expect(shopId.Null).toEqual('YES')
done()
}).error(function(err) {
console.log(err)
})
})
})
})
describe('removeColumn', function() {
it('removes the shopId column from user', function() {
setup({from: 20111205064000, to: 20111206061400})
Helpers.async(function(done) {
migrator.migrate().success(done).error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.filter(function(hash){ return hash.Field == 'signature' })[0]
, isAdmin = data.filter(function(hash){ return hash.Field == 'isAdmin' })[0]
, shopId = data.filter(function(hash){ return hash.Field == 'shopId' })[0]
expect(signature.Field).toEqual('signature')
expect(signature.Null).toEqual('NO')
expect(isAdmin.Field).toEqual('isAdmin')
expect(isAdmin.Null).toEqual('NO')
expect(isAdmin.Default).toEqual('0')
expect(shopId).toBeFalsy()
done()
}).error(function(err) {
console.log(err)
})
})
})
})
describe('changeColumn', function() {
it('changes the signature column from user to default "signature" + notNull', function() {
setup({from: 20111205064000, to: 20111206063000})
Helpers.async(function(done) {
migrator.migrate().success(done).error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.filter(function(hash){ return hash.Field == 'signature' })[0]
expect(signature.Field).toEqual('signature')
expect(signature.Type).toEqual('varchar(255)')
expect(signature.Null).toEqual('NO')
expect(signature.Default).toEqual('Signature')
done()
}).error(function(err) {
console.log(err)
})
})
})
})
})
describe('renameColumn', function() {
it("renames the signature column from user to sig", function() {
setup({from: 20111117063700, to: 20111206163300})
Helpers.async(function(done) {
migrator.migrate().success(done).error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.filter(function(hash){ return hash.Field == 'signature' })[0]
, sig = data.filter(function(hash){ return hash.Field == 'sig' })[0]
expect(signature).toBeFalsy()
expect(sig).toBeTruthy()
done()
}).error(function(err) {
console.log(err)
})
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("./config/helpers"))(sequelize)
describe('ModelDefinition', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var User = sequelize.define('User', { age: Sequelize.INTEGER, name: Sequelize.STRING, bio: Sequelize.TEXT })
//////////// all //////////////
describe('.all', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("should return all users", function() {
Helpers.async(function(done) {
User.all.on('success', function(users) {
done()
expect(users.length).toEqual(2)
}).on('failure', function(err) { console.log(err) })
})
})
})
/////////// create ////////////
describe('.create with options', function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING, options: Sequelize.TEXT })
it('should allow the creation of an object with options as attribute', function() {
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
Helpers.Factories.Model('Person', {name: 'John Doe', options: options}, function(person) {
expect(person.options).toEqual(options)
})
})
})
//////////// min //////////////
describe('.min', function() {
it("should return the min value", function() {
for(var i = 2; i < 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.min('age').on('success', function(min) {
expect(min).toEqual(2); done()
})
})
})
})
//////////// max //////////////
describe('.max', function() {
it("should return the max value", function() {
for(var i = 2; i <= 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.max('age').on('success', function(min) {
expect(min).toEqual(5); done()
})
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, dialects = ['sqlite', 'mysql']
describe('ModelFactory', function() {
dialects.forEach(function(dialect) {
describe('with dialect "' + dialect + '"', function() {
var User = null
, sequelize = new Sequelize(config.database, config.username, config.password, {
logging: false,
dialect: dialect
})
, Helpers = new (require("./config/helpers"))(sequelize)
var setup = function(options) {
User = sequelize.define('User', options || {
age: Sequelize.INTEGER,
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
Helpers.dropAllTables()
Helpers.async(function(done) {
User.sync({force: true}).success(done).error(function(err) { console.log(err) })
})
}
var checkMatchForDialects = function(value, expectations) {
if(!!expectations[dialect])
expect(value).toMatch(expectations[dialect])
else
throw new Error('Undefined expectation for "' + dialect + '"!')
}
beforeEach(function() { setup() })
afterEach(function() { Helpers.dropAllTables() })
describe('constructor', function() {
it("uses the passed model name as tablename if freezeTableName", function() {
var User = sequelize.define('User', {}, {freezeTableName: true})
expect(User.tableName).toEqual('User')
})
it("uses the pluralized modelname as tablename unless freezeTableName", function() {
var User = sequelize.define('User', {}, {freezeTableName: false})
expect(User.tableName).toEqual('Users')
})
it("attaches class and instance methods", function() {
var User = sequelize.define('User', {}, {
classMethods: { doSmth: function(){ return 1 } },
instanceMethods: { makeItSo: function(){ return 2}}
})
expect(User.doSmth).toBeDefined()
expect(User.doSmth()).toEqual(1)
expect(User.makeItSo).toBeUndefined()
expect(User.build().makeItSo).toBeDefined()
expect(User.build().makeItSo()).toEqual(2)
})
it("throws an error if 2 autoIncrements are passed", function() {
expect(function () {
var User = sequelize.define('User', {
userid: {type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true},
userscore: {type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true},
})
}).toThrow('Invalid model definition. Only one autoincrement field allowed.')
})
})
describe('build', function() {
it("doesn't create database entries", function() {
Helpers.async(function(done) {
User.build({ name: 'John Wayne', bio: 'noot' })
User.all().success(function(users) {
expect(users.length).toEqual(0)
done()
})
})
})
it("fills the objects with default values", function() {
var Task = sequelize.define('Task' + config.rand(), {
title: {type: Sequelize.STRING, defaultValue: 'a task!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
bar: {type: Sequelize.DATE},
foobar: {type: Sequelize.TEXT, defaultValue: 'asd'},
flag: {type: Sequelize.BOOLEAN, defaultValue: false}
})
expect(Task.build().title).toEqual('a task!')
expect(Task.build().foo).toEqual(2)
expect(Task.build().bar).toEqual(null)
expect(Task.build().foobar).toEqual('asd')
expect(Task.build().flag).toEqual(false)
})
})
describe('create', function() {
it("doesn't allow duplicated records with unique:true", function() {
setup({ username: {type: Sequelize.STRING, unique: true} })
Helpers.async(function(done) {
User.create({ username:'foo' }).success(function() {
User.create({ username: 'foo' }).error(function(err) {
expect(err).toBeDefined()
checkMatchForDialects(err.message, {
sqlite: /.*SQLITE_CONSTRAINT.*/,
mysql: /.*Duplicate\ entry.*/
})
done()
})
})
})
})
it("raises an error if created object breaks definition contraints", function() {
setup({
username: {type: Sequelize.STRING, unique: true},
smth: {type: Sequelize.STRING, allowNull: false}
})
Helpers.async(function(done) {
User.create({ username: 'foo', smth: null }).error(function(err) {
expect(err).toBeDefined()
checkMatchForDialects(err.message, {
sqlite: /.*SQLITE_CONSTRAINT.*/,
mysql: "Column 'smth' cannot be null"
})
User.create({username: 'foo', smth: 'foo'}).success(function() {
User.create({username: 'foo', smth: 'bar'}).error(function(err) {
expect(err).toBeDefined()
checkMatchForDialects(err.message, {
sqlite: /.*SQLITE_CONSTRAINT.*/,
mysql: "Duplicate entry 'foo' for key 'username'"
})
done()
})
})
})
})
})
it('sets auto increment fields', function() {
setup({
userid: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false}
})
Helpers.async(function(done) {
User.create({}).on('success', function(user) {
expect(user.userid).toEqual(1)
done()
})
})
Helpers.async(function(done) {
User.create({}).on('success', function(user) {
expect(user.userid).toEqual(2)
done()
})
})
})
it('allows the usage of options as attribute', function() {
setup({ name: Sequelize.STRING, options: Sequelize.TEXT })
Helpers.async(function(done) {
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
User
.create({ name: 'John Doe', options: options })
.success(function(user) {
expect(user.options).toEqual(options)
done()
})
})
})
})
describe('destroy', function() {
it('deletes a record from the database if model is not paranoid', function() {
Helpers.async(function(done) {
User = sequelize.define('User', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
User.sync({force: true}).success(done)
})
Helpers.async(function(done) {
User.create({name: 'hallo', bio: 'welt'}).success(function(u) {
User.all().success(function(users) {
expect(users.length).toEqual(1)
u.destroy().success(function() {
User.all().success(function(users) {
expect(users.length).toEqual(0)
done()
}).error(function(err) { console.log(err) })
}).error(function(err) { console.log(err) })
}).error(function(err) { console.log(err) })
})
})
})
it('marks the database entry as deleted if model is paranoid', function() {
Helpers.async(function(done) {
User = sequelize.define('User', {
name: Sequelize.STRING, bio: Sequelize.TEXT
}, { paranoid:true })
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
User.create({ name: 'asd', bio: 'asd' }).success(function(u) {
expect(u.deletedAt).toBeNull()
u.destroy().success(function(u) {
expect(u.deletedAt).toBeTruthy()
done()
})
})
})
})
})
describe('find', function() {
var users = []
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, function(_users) {
users = _users
}, 2)
})
it("should make aliased attributes available", function() {
Helpers.async(function(done) {
User.find({ where: 'id = 1', attributes: ['id', ['name', 'username']] }).success(function(user) {
expect(user.username).toEqual('user')
done()
})
})
})
it('returns a single model', function() {
Helpers.async(function(done) {
User.find(users[0].id).success(function(user) {
expect(Array.isArray(user)).toBeFalsy()
expect(user.id).toEqual(users[0].id)
done()
})
})
})
it('finds a specific user via where option', function() {
Helpers.async(function(done) {
User.find({where: { name: 'user' }}).success(function(user) {
expect(user.name).toEqual('user')
done()
})
})
})
it("doesn't find a user if conditions are not matching", function() {
Helpers.async(function(done) {
User.find({ where: { name: 'foo' } }).success(function(user) {
expect(user).toBeNull()
done()
})
})
})
it('ignores passed limit option', function() {
Helpers.async(function(done) {
User.find({limit: 10}).success(function(user) {
// it returns an object instead of an array
expect(Array.isArray(user)).toBeFalsy()
expect(user.hasOwnProperty('name')).toBeTruthy()
done()
})
})
})
it('finds entries via primary keys', function() {
setup({
identifier: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING
})
Helpers.async(function(done) {
User.create({identifier: 'an identifier', name: 'John'}).success(function(u) {
expect(u.id).toBeUndefined()
User.find('an identifier').success(function(u2) {
expect(u2.identifier).toEqual('an identifier')
expect(u2.name).toEqual('John')
done()
})
})
})
})
})
describe('findAll', function() {
var users = []
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, function(_users) {
users = _users
}, 2)
})
it("finds all entries", function() {
Helpers.async(function(done) {
User.findAll().on('success', function(_users) {
expect(_users.length).toEqual(2)
done()
})
})
})
it("finds all users matching the passed conditions", function() {
Helpers.async(function(done) {
User.findAll({where: "id != " + users[1].id}).success(function(_users) {
expect(_users.length).toEqual(1)
done()
})
})
})
it("can also handle array notation", function() {
Helpers.async(function(done){
User.findAll({where: ['id = ?', users[1].id]}).success(function(_users) {
expect(_users.length).toEqual(1)
expect(_users[0].id).toEqual(users[1].id)
done()
})
})
})
it("sorts the results", function() {
Helpers.async(function(done) {
User.findAll({ order: "id DESC" }).success(function(users) {
expect(users[0].id).toBeGreaterThan(users[1].id)
done()
})
})
})
it("handles offset and limit", function() {
setup()
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 10)
Helpers.async(function(done) {
User.findAll({ limit: 2, offset: 2 }).success(function(users) {
expect(users.length).toEqual(2)
expect(users[0].id).toEqual(3)
done()
})
})
})
})
describe('all', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("should return all users", function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
done()
expect(users.length).toEqual(2)
}).on('failure', function(err) { console.log(err) })
})
})
})
describe('count', function() {
it('counts all created objects', function() {
Helpers.async(function(done) {
User.create({name: 'user1'}).success(function() {
User.create({name: 'user2'}).success(done)
})
})
Helpers.async(function(done) {
User.count().success(function(count) {
expect(count).toEqual(2)
done()
})
})
})
it('filters object', function() {
Helpers.async(function(done) {
User.create({name: 'user1'}).success(function() {
User.create({name: 'foo'}).success(done)
})
})
Helpers.async(function(done) {
User.count({where: "name LIKE '%us%'"}).success(function(count) {
expect(count).toEqual(1)
done()
})
})
})
})
describe('min', function() {
it("should return the min value", function() {
for(var i = 2; i < 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.min('age').on('success', function(min) {
expect(min).toEqual(2); done()
})
})
})
})
describe('max', function() {
it("should return the max value", function() {
for(var i = 2; i <= 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.max('age').on('success', function(max) {
expect(max).toEqual(5); done()
})
})
})
})
describe('equals', function() {
it("correctly determines equality of objects", function() {
setup({ name: Sequelize.STRING, bio: Sequelize.TEXT })
Helpers.async(function(done) {
User.create({name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equals(u)).toBeTruthy()
done()
})
})
})
// sqlite can't handle multiple primary keys
if(dialect != 'sqlite') {
it("correctly determines equality with multiple primary keys", function() {
setup({
foo: {type: Sequelize.STRING, primaryKey: true},
bar: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING, bio: Sequelize.TEXT
})
Helpers.async(function(done) {
User.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equals(u)).toBeTruthy()
done()
}).error(function(err) { console.log(err) })
})
})
}
})
describe('equalsOneOf', function() {
// sqlite can't handle multiple primary keys
if(dialect != 'sqlite') {
beforeEach(function() {
setup({
foo: {type: Sequelize.STRING, primaryKey: true},
bar: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING, bio: Sequelize.TEXT
})
})
it('determines equality if one is matching', function() {
Helpers.async(function(done) {
User.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equalsOneOf([u, {a:1}])).toBeTruthy()
done()
})
})
})
it("doesn't determine equality if none is matching", function() {
Helpers.async(function(done) {
User.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equalsOneOf([{b:2}, {a:1}])).toBeFalsy()
done()
})
})
})
}
})
describe('Mixin', function() {
var ModelFactory = require("../lib/model-factory")
it("adds the mixed-in functions to the model", function() {
expect(ModelFactory.prototype.hasOne).toBeDefined()
expect(ModelFactory.prototype.hasMany).toBeDefined()
expect(ModelFactory.prototype.belongsTo).toBeDefined()
})
})
describe('sync', function() {
it('works with correct database credentials', function() {
Helpers.async(function(done) {
User.sync().success(done)
})
})
it("fails with incorrect database credentials", function() {
Helpers.async(function(done) {
var sequelize2 = new Sequelize('foo', 'bar', null, { logging: false })
, User2 = sequelize2.define('User', { name: Sequelize.STRING, bio: Sequelize.TEXT })
User2.sync().error(function(err) {
expect(err.message).toMatch(/.*Access\ denied.*/)
done()
})
})
})
})
describe('drop should work', function() {
it('correctly succeeds', function() {
Helpers.async(function(done) {
User.drop().success(done)
})
})
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, dialects = ['sqlite', 'mysql']
describe('Model', function() {
dialects.forEach(function(dialect) {
describe('with dialect "' + dialect + '"', function() {
var User = null
, sequelize = new Sequelize(config.database, config.username, config.password, {
logging: false,
dialect: dialect
})
, Helpers = new (require("./config/helpers"))(sequelize)
var setup = function() {
Helpers.async(function(done) {
User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(done)
})
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
describe('Validations', function() {
var checks = {
is : {
spec : { args: ["[a-z]",'i'] },
fail: "0",
pass: "a"
}
, not : {
spec: { args: ["[a-z]",'i'] },
fail: "a",
pass: "0"
}
, isEmail : {
fail: "a",
pass: "abc@abc.com"
}
, isUrl : {
fail: "abc",
pass: "http://abc.com"
}
, isIP : {
fail: "abc",
pass: "129.89.23.1"
}
, isAlpha : {
fail: "012",
pass: "abc"
}
, isAlphanumeric : {
fail: "_abc019",
pass: "abc019"
}
, isNumeric : {
fail: "abc",
pass: "019"
}
, isInt : {
fail: "9.2",
pass: "-9"
}
, isLowercase : {
fail: "AB",
pass: "ab"
}
, isUppercase : {
fail: "ab",
pass: "AB"
}
, isDecimal : {
fail: "a",
pass: "0.2"
}
, isFloat : {
fail: "a",
pass: "9.2"
}
, notNull : {
fail: null,
pass: 0
}
, isNull : {
fail: 0,
pass: null
}
, notEmpty : {
fail: " ",
pass: "a"
}
, equals : {
spec : { args : "bla bla bla" },
fail: "bla",
pass: "bla bla bla"
}
, contains : {
spec : { args : "bla" },
fail: "la",
pass: "0bla23"
}
, notContains : {
spec : { args : "bla" },
fail: "0bla23",
pass: "la"
}
, regex : {
spec : { args: ["[a-z]",'i'] },
fail: "0",
pass: "a"
}
, notRegex : {
spec: { args: ["[a-z]",'i'] },
fail: "a",
pass: "0"
}
, len : {
spec: { args: [2,4] },
fail: ["1", "12345"],
pass: ["12", "123", "1234"],
raw: true
}
, isUUID : {
spec: { args: 4 },
fail: "f47ac10b-58cc-3372-a567-0e02b2c3d479",
pass: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
, isDate : {
fail: "not a date",
pass: "2011-02-04"
}
, isAfter : {
spec: { args: "2011-11-05" },
fail: "2011-11-04",
pass: "2011-11-05"
}
, isBefore : {
spec: { args: "2011-11-05" },
fail: "2011-11-06",
pass: "2011-11-05"
}
, isIn : {
spec: { args: "abcdefghijk" },
fail: "ghik",
pass: "ghij"
}
, notIn : {
spec: { args: "abcdefghijk" },
fail: "ghij",
pass: "ghik"
}
, max : {
spec: { args: 23 },
fail: "24",
pass: "23"
}
, min : {
spec: { args: 23 },
fail: "22",
pass: "23"
}
, isArray : {
fail: 22,
pass: [22]
}
, isCreditCard : {
fail: "401288888888188f",
pass: "4012888888881881"
}
};
var User, i;
it('should correctly validate using node-validator methods', function() {
Helpers.async(function(done) {
for (var validator in checks) {
if (checks.hasOwnProperty(validator)) {
// build spec
var v = {};
v[validator] = checks[validator].hasOwnProperty("spec") ? checks[validator].spec : {};
var check = checks[validator];
// test for failure
if (!check.hasOwnProperty("raw"))
check.fail = new Array(check.fail);
for (i=0; i<check.fail.length; ++i) {
v[validator].msg = validator + "(" + check.fail[i] + ")";
// define user
User = sequelize.define('User' + Math.random(), {
name: {
type: Sequelize.STRING,
validate: v
}
});
var u_fail = User.build({
name : check.fail[i]
});
var errors = u_fail.validate();
expect(errors).toNotBe(null);
expect(errors).toEqual({
name : [v[validator].msg]
});
}
// test for success
if (!check.hasOwnProperty("raw"))
check.pass = new Array(check.pass);
for (i=0; i<check.pass.length; ++i) {
v[validator].msg = validator + "(" + check.pass[i] + ")";
// define user
User = sequelize.define('User' + Math.random(), {
name: {
type: Sequelize.STRING,
validate: v
}
});
var u_success = User.build({
name : check.pass[i]
});
expect(u_success.validate()).toBe(null);
}
}
} // for each check
done();
});
});
it('should correctly validate using custom validation methods', function() {
Helpers.async(function(done) {
User = sequelize.define('User' + Math.random(), {
name: {
type: Sequelize.STRING,
validate: {
customFn: function(val) {
if (val !== "2")
throw new Error("name should equal '2'")
}
}
}
});
var u_fail = User.build({
name : "3"
});
var errors = u_fail.validate();
expect(errors).toNotBe(null);
expect(errors).toEqual({
name : ["name should equal '2'"]
});
var u_success = User.build({
name : "2"
});
expect(u_success.validate()).toBe(null);
done();
});
});
})
describe('isNewRecord', function() {
it('returns true for non-saved objects', function() {
var user = User.build({ username: 'user' })
expect(user.id).toBeNull()
expect(user.isNewRecord).toBeTruthy()
})
it("returns false for saved objects", function() {
Helpers.async(function(done) {
User.build({ username: 'user' }).save().success(function(user) {
expect(user.isNewRecord).toBeFalsy()
done()
})
})
})
it("returns false for created objects", function() {
Helpers.async(function(done) {
User.create({ username: 'user' }).success(function(user) {
expect(user.isNewRecord).toBeFalsy()
done()
})
})
})
it("returns false for objects found by find method", function() {
Helpers.async(function(done) {
User.create({ username: 'user' }).success(function(user) {
User.create({ username: 'user' }).success(function(user) {
User.find(user.id).success(function(user) {
expect(user.isNewRecord).toBeFalsy()
done()
})
})
})
})
})
it("returns false for objects found by findAll method", function() {
var chainer = new Sequelize.Utils.QueryChainer
for(var i = 0; i < 10; i++)
chainer.add(User.create({ username: 'user' }))
Helpers.async(function(done) {
chainer.run().success(function() {
User.findAll().success(function(users) {
users.forEach(function(u) {
expect(u.isNewRecord).toBeFalsy()
})
done()
})
})
})
})
})
describe('save', function() {
it("stores an entry in the database", function() {
var username = 'user'
, user = User.build({ username: username })
Helpers.async(function(done) {
User.all().success(function(users) {
expect(users.length).toEqual(0)
done()
})
})
Helpers.async(function(done) {
user.save().success(done)
})
Helpers.async(function(done) {
User.all().success(function(users) {
expect(users.length).toEqual(1)
expect(users[0].username).toEqual(username)
done()
})
})
})
it("updates the timestamps", function() {
var now = Date.now()
, user = null
, updatedAt = null
Helpers.async(function(done) {
// timeout is needed, in order to check the update of the timestamp
setTimeout(function() {
user = User.build({ username: 'user' })
updatedAt = user.updatedAt
expect(updatedAt.getTime()).toBeGreaterThan(now)
done()
}, 10)
})
Helpers.async(function(done) {
setTimeout(function() {
user.save().success(function() {
expect(updatedAt.getTime()).toBeLessThan(user.updatedAt.getTime())
done()
})
}, 10)
})
})
})
describe('updateAttributes', function() {
it("updates attributes in the database", function() {
Helpers.async(function(done) {
User.create({ username: 'user' }).success(function(user) {
expect(user.username).toEqual('user')
user.updateAttributes({ username: 'person' }).success(function(user) {
expect(user.username).toEqual('person')
done()
})
})
})
})
it("ignores unknown attributes", function() {
Helpers.async(function(done) {
User.create({ username: 'user' }).success(function(user) {
user.updateAttributes({ username: 'person', foo: 'bar'}).success(function(user) {
expect(user.username).toEqual('person')
expect(user.foo).toBeUndefined()
done()
})
})
})
})
it("doesn't update primary keys or timestamps", function() {
var User = sequelize.define('User' + config.rand(), {
name: Sequelize.STRING, bio: Sequelize.TEXT, identifier: {type: Sequelize.STRING, primaryKey: true}
})
Helpers.async(function(done) {
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
User.create({
name: 'snafu',
identifier: 'identifier'
}).success(function(user) {
var oldCreatedAt = user.createdAt
, oldIdentifier = user.identifier
user.updateAttributes({
name: 'foobar',
createdAt: new Date(2000, 1, 1),
identifier: 'another identifier'
}).success(function(user) {
expect(user.createdAt).toEqual(oldCreatedAt)
expect(user.identifier).toEqual(oldIdentifier)
done()
})
})
})
})
it("uses primary keys in where clause", function() {
var User = sequelize.define('User' + config.rand(), {
name: Sequelize.STRING, bio: Sequelize.TEXT, identifier: {type: Sequelize.STRING, primaryKey: true}
})
Helpers.async(function(done) {
User.sync({ force:true }).success(done)
})
Helpers.async(function(done) {
User.create({
name: 'snafu',
identifier: 'identifier'
}).success(function(user) {
var emitter = user.updateAttributes({name: 'foobar'})
emitter.success(function() {
expect(emitter.query.sql).toMatch(/WHERE `identifier`..identifier./)
done()
})
})
})
})
})
describe('values', function() {
it('returns all values', function() {
var User = sequelize.define('User', {
username: Sequelize.STRING
}, { timestamps: false, logging: false })
Helpers.async(function(done) {
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
var user = User.build({ username: 'foo' })
expect(user.values).toEqual({ username: "foo", id: null })
done()
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var User = sequelize.define('User' + Math.random(), { name: Sequelize.STRING })
, Task = sequelize.define('Task' + Math.random(), { name: Sequelize.STRING })
, users = null
, tasks = null
User.hasMany(Task, {as:'Tasks'})
Task.hasMany(User, {as:'Users'})
beforeEach(function() {
Helpers.async(function(_done) {
Helpers.Factories.Model(User.name, {name: 'User' + Math.random()}, function(_users) {
users = _users; _done()
}, 5)
})
Helpers.async(function(_done) {
Helpers.Factories.Model(Task.name, {name: 'Task' + Math.random()}, function(_tasks) {
tasks = _tasks; _done()
}, 2)
})
})
describe('addModel / getModel', function() {
var user = null
, task = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(_users) {
Task.all().on('success', function(_tasks) {
user = _users[0]
task = _tasks[0]
done()
})
})
})
})
it('should correctly add an association to the model', function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0)
user.addTask(task).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1)
done()
})
})
})
})
})
})
describe('removeModel', function() {
var user = null
, tasks = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
Task.all().on('success', function(_tasks) {
user = users[0]
tasks = _tasks
done()
})
})
})
})
it("should correctly remove associated objects", function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).toEqual(0)
user.setTasks(tasks).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length)
user.removeTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
var config = require("./config/config") var config = require("../config/config")
, Sequelize = require("../index") , Sequelize = require("../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false }) , sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("./config/helpers"))(sequelize) , Helpers = new (require("../config/helpers"))(sequelize)
describe('Associations', function() { describe('Associations', function() {
beforeEach(function() { Helpers.sync() }) beforeEach(function() { Helpers.sync() })
...@@ -18,7 +18,10 @@ describe('Associations', function() { ...@@ -18,7 +18,10 @@ describe('Associations', function() {
Table2.hasMany(Table1) Table2.hasMany(Table1)
it("should create a table wp_table1wp_table2s", function() { it("should create a table wp_table1wp_table2s", function() {
expect(sequelize.modelManager.getModel('wp_table1swp_table2s')).toBeDefined() Helpers.async(function(done) {
expect(sequelize.modelFactoryManager.getModel('wp_table1swp_table2s')).toBeDefined()
done()
})
}) })
}) })
describe('when join table name is specified', function() { describe('when join table name is specified', function() {
......
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('ConnectorManager', function() {
beforeEach(function() {
Helpers.dropAllTables()
})
afterEach(function() {
Helpers.dropAllTables()
})
it('works correctly after being idle', function() {
var User = sequelize.define('User', { username: Sequelize.STRING })
Helpers.async(function(done) {
User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() {
User.count().on('success', function(count) {
expect(count).toEqual(1)
done()
})
})
})
})
Helpers.async(function(done) {
setTimeout(function() {
User.count().on('success', function(count) {
expect(count).toEqual(1)
done()
})
}, 1000)
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('ModelFactory', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var User = sequelize.define('User', { age: Sequelize.INTEGER, name: Sequelize.STRING, bio: Sequelize.TEXT })
describe('constructor', function() {
it("handles extended attributes (unique)", function() {
var User = sequelize.define('User' + config.rand(), {
username: { type: Sequelize.STRING, unique: true }
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) UNIQUE",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (default)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, defaultValue: 'foo'}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) DEFAULT 'foo'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (null)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, allowNull: false}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) NOT NULL",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (primaryKey)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, primaryKey: true}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) PRIMARY KEY"})
})
it("adds timestamps", function() {
var User1 = sequelize.define('User' + config.rand(), {})
var User2 = sequelize.define('User' + config.rand(), {}, { timestamps: true })
expect(User1.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
expect(User2.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
})
it("adds deletedAt if paranoid", function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true })
expect(User.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deletedAt:"DATETIME", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
})
it("underscores timestamps if underscored", function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true, underscored: true })
expect(User.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deleted_at:"DATETIME", updated_at:"DATETIME NOT NULL", created_at:"DATETIME NOT NULL"})
})
})
describe('primaryKeys', function() {
it("determines the correct primaryKeys", function() {
var User = sequelize.define('User' + config.rand(), {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: Sequelize.STRING
})
expect(User.primaryKeys).toEqual({"foo":"VARCHAR(255) PRIMARY KEY"})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("../config/helpers"))(sequelize)
, QueryGenerator = require("../../lib/dialects/mysql/query-generator")
, util = require("util")
describe('QueryGenerator', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var suites = {
createTableQuery: [
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {engine: 'MyISAM'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {charset: 'latin1'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;"
}
],
dropTableQuery: [
{
arguments: ['myTable'],
expectation: "DROP TABLE IF EXISTS `myTable`;"
}
],
selectQuery: [
{
arguments: ['myTable'],
expectation: "SELECT * FROM `myTable`;"
}, {
arguments: ['myTable', {attributes: ['id', 'name']}],
expectation: "SELECT `id`, `name` FROM `myTable`;"
}, {
arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM `myTable` WHERE `id`=2;"
}, {
arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM `myTable` WHERE `name`='foo';"
}, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;';"
}, {
arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM `myTable` WHERE `id`=2;"
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as `count` FROM `foo`;'
}, {
arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM `myTable` WHERE foo='bar';"
}, {
arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM `myTable` ORDER BY id DESC;"
}, {
arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;"
}, {
arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM `myTable` LIMIT 10;"
}, {
arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 10;"
}, {
title: 'ignores offset if no limit was passed',
arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM `myTable`;"
}
],
insertQuery: [
{
arguments: ['myTable', {name: 'foo'}],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo');"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;');"
}, {
arguments: ['myTable', {name: 'foo', birthday: new Date(2011, 2, 27, 10, 1, 55)}],
expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55');"
}, {
arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);"
}
],
updateQuery: [
{
arguments: ['myTable', {name: 'foo', birthday: new Date(2011, 2, 27, 10, 1, 55)}, {id: 2}],
expectation: "UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2"
}, {
arguments: ['myTable', {name: 'foo', birthday: new Date(2011, 2, 27, 10, 1, 55)}, 2],
expectation: "UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2"
}, {
arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE `myTable` SET `name`='foo\\';DROP TABLE myTable;' WHERE `name`='foo'"
}
],
deleteQuery: [
{
arguments: ['myTable', {name: 'foo'}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo' LIMIT 1"
}, {
arguments: ['myTable', 1],
expectation: "DELETE FROM `myTable` WHERE `id`=1 LIMIT 1"
}, {
arguments: ['myTable', 1, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `id`=1 LIMIT 10"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;' LIMIT 10"
}
],
addIndexQuery: [
{
arguments: ['User', ['username', 'isAdmin']],
expectation: 'CREATE INDEX user_username_is_admin ON User (username, isAdmin)'
}, {
arguments: [
'User', [
{ attribute: 'username', length: 10, order: 'ASC'},
'isAdmin'
]
],
expectation: "CREATE INDEX user_username_is_admin ON User (username(10) ASC, isAdmin)"
}, {
arguments: [
'User', ['username', 'isAdmin'], { parser: 'foo', indicesType: 'FULLTEXT', indexName: 'bar'}
],
expectation: "CREATE FULLTEXT INDEX bar ON User (username, isAdmin) WITH PARSER foo"
}
],
showIndexQuery: [
{
arguments: ['User'],
expectation: 'SHOW INDEX FROM User'
}, {
arguments: ['User', { database: 'sequelize' }],
expectation: "SHOW INDEX FROM User FROM sequelize"
}
],
removeIndexQuery: [
{
arguments: ['User', 'user_foo_bar'],
expectation: "DROP INDEX user_foo_bar ON User"
}, {
arguments: ['User', ['foo', 'bar']],
expectation: "DROP INDEX user_foo_bar ON User"
}
],
hashToWhereConditions: [
{
arguments: [{ id: [1,2,3] }],
expectation: "`id` IN (1,2,3)"
}
]
}
Sequelize.Utils._.each(suites, function(tests, suiteTitle) {
describe(suiteTitle, function() {
tests.forEach(function(test) {
var title = test.title || 'correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function() {
var conditions = QueryGenerator[suiteTitle].apply(null, test.arguments)
expect(conditions).toEqual(test.expectation)
})
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("./config/helpers"))(sequelize)
, QueryGenerator = require("../lib/sequelize/query-generator")
describe('QueryGenerator', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
describe('hashToWhereConditions', function() {
it("should correctly transform array into IN", function() {
expect(
QueryGenerator.hashToWhereConditions({ id: [1,2,3] })
).toEqual(
"`id` IN (1,2,3)"
)
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false })
, Helpers = new (require("./config/helpers"))(sequelize)
, QueryInterface = require("../lib/query-interface")
describe('QueryInterface', function() {
var interface = null
beforeEach(function() {
interface = sequelize.getQueryInterface()
Helpers.dropAllTables()
})
afterEach(function() {
interface = null
Helpers.dropAllTables()
})
describe('dropAllTables', function() {
it("should drop all tables", function() {
Helpers.async(function(done) {
interface.dropAllTables().success(done).error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
interface.showAllTables().success(function(tableNames) {
expect(tableNames.length).toEqual(0)
done()
})
})
Helpers.async(function(done) {
interface.createTable('table', { name: Sequelize.STRING })
.success(done)
.error(function(err){ console.log(err)})
})
Helpers.async(function(done) {
interface.showAllTables().success(function(tableNames) {
expect(tableNames.length).toEqual(1)
done()
})
})
Helpers.async(function(done) {
interface.dropAllTables().success(done).error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
interface.showAllTables().success(function(tableNames) {
expect(tableNames.length).toEqual(0)
done()
})
})
})
})
describe('indexes', function() {
beforeEach(function(){
Helpers.async(function(done) {
interface.createTable('User', {
username: Sequelize.STRING,
isAdmin: Sequelize.BOOLEAN
}).success(done)
})
})
it('adds, reads and removes an index to the table', function() {
Helpers.async(function(done) {
interface.addIndex('User', ['username', 'isAdmin']).success(done).error(function(err) {
console.log(err)
})
})
Helpers.async(function(done) {
interface.showIndex('User').success(function(indexes) {
var indexColumns = indexes.map(function(index) { return index.Column_name }).sort()
expect(indexColumns).toEqual(['isAdmin', 'username'])
done()
}).error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
interface.removeIndex('User', ['username', 'isAdmin']).success(done).error(function(err) {
console.log(err)
})
})
Helpers.async(function(done) {
interface.showIndex('User').success(function(indexes) {
var indexColumns = indexes.map(function(index) { return index.Column_name }).sort()
expect(indexColumns).toEqual([])
done()
}).error(function(err) { console.log(err) })
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, QueryInterface = require("../lib/query-interface")
describe('Sequelize', function() {
var sequelize = null
, Helpers = null
var setup = function(options) {
options = options || {logging: false}
sequelize = new Sequelize(config.database, config.username, config.password, options)
Helpers = new (require("./config/helpers"))(sequelize)
return options
}
beforeEach(function() { setup() })
afterEach(function() { sequelize = null })
describe('constructor', function() {
it('should pass the global options correctly', function() {
setup({ logging: false, define: { underscored:true } })
var Model = sequelize.define('model', {name: Sequelize.STRING})
expect(Model.options.underscored).toBeTruthy()
})
it('should correctly set the host and the port', function() {
var options = setup({ host: '127.0.0.1', port: 1234 })
expect(sequelize.config.host).toEqual(options.host)
expect(sequelize.config.port).toEqual(options.port)
})
})
describe('define', function() {
it("adds a new model to the model manager", function() {
expect(sequelize.modelFactoryManager.all.length).toEqual(0)
sequelize.define('foo', { title: Sequelize.STRING })
expect(sequelize.modelFactoryManager.all.length).toEqual(1)
})
})
describe('sync', function() {
it("synchronizes all models", function() {
var Project = sequelize.define('project' + config.rand(), { title: Sequelize.STRING })
var Task = sequelize.define('task' + config.rand(), { title: Sequelize.STRING })
Helpers.async(function(done) {
sequelize.sync().success(function() {
Project.create({title: 'bla'}).success(function() {
Task.create({title: 'bla'}).success(done)
})
})
})
})
})
describe('import', function() {
it("imports a model definition from a file", function() {
var Project = sequelize.import(__dirname + "/assets/project")
expect(Project).toBeDefined()
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, dbFile = __dirname + '/test.sqlite'
, storages = [':memory:', dbFile]
describe('ModelFactory', function() {
storages.forEach(function(storage) {
describe('with storage "' + storage + '"', function() {
var User = null
, sequelize = null
, Helpers = null
beforeEach(function() {
sequelize = new Sequelize(config.database, config.username, config.password, {
logging: false,
dialect: 'sqlite',
storage: storage
})
Helpers = new (require("../config/helpers"))(sequelize)
User = sequelize.define('User', {
age: Sequelize.INTEGER,
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
Helpers.sync()
})
afterEach(function() {
Helpers.dropAllTables()
if(storage == dbFile) {
Helpers.async(function(done) {
require("fs").unlink(__dirname + '/test.sqlite', done)
})
}
})
describe('create', function() {
it('creates a table entry', function() {
Helpers.async(function(done) {
User
.create({ age: 21, name: 'John Wayne', bio: 'noot noot' })
.success(done)
.error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
User.all().success(function(users) {
var usernames = users.map(function(user) {
return user.name
})
expect(usernames).toEqual(['John Wayne'])
done()
}).error(function(err){ console.log(err) })
})
})
it('should allow the creation of an object with options as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
options: Sequelize.TEXT
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
Helpers.Factories.Model('Person', {
name: 'John Doe',
options: options
}, function(people) {
expect(people[0].options).toEqual(options)
done()
})
})
})
})
////////// find //////////////
describe('.find', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("finds normal lookups", function() {
Helpers.async(function(done) {
User.find({ where: { name:'user' } }).success(function(user) {
expect(user.name).toEqual('user')
done()
})
})
})
it("should make aliased attributes available", function() {
Helpers.async(function(done) {
User.find({ where: { name:'user' }, attributes: ['id', ['name', 'username']] }).success(function(user) {
expect(user.username).toEqual('user')
done()
})
})
})
})
////////// all //////////////
describe('.all', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("should return all users", function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
done()
expect(users.length).toEqual(2)
}).on('failure', function(err) { console.log(err) })
})
})
})
////////// min //////////////
describe('.min', function() {
it("should return the min value", function() {
for(var i = 2; i < 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.min('age').on('success', function(min) {
expect(min).toEqual(2); done()
})
})
})
})
////////// max //////////////
describe('.max', function() {
it("should return the max value", function() {
for(var i = 2; i <= 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.max('age').on('success', function(min) {
expect(min).toEqual(5); done()
})
})
})
})
})
})
})
var Utils = require('../lib/utils')
describe('Utils', function() {
describe('removeCommentsFromFunctionString', function() {
it("removes line comments at the start of a line", function() {
var functionWithLineComments = function() {
// noot noot
}
var result = Utils.removeCommentsFromFunctionString(functionWithLineComments.toString())
expect(result).toNotMatch(/.*noot.*/)
})
it("removes lines comments in the middle of a line", function() {
var functionWithLineComments = function() {
alert(1) // noot noot
}
var result = Utils.removeCommentsFromFunctionString(functionWithLineComments.toString())
expect(result).toNotMatch(/.*noot.*/)
})
it("removes range comments", function() {
var s = function() {
alert(1) /*
noot noot
*/
alert(2) /*
foo
*/
}.toString()
var result = Utils.removeCommentsFromFunctionString(s)
expect(result).toNotMatch(/.*noot.*/)
expect(result).toNotMatch(/.*foo.*/)
expect(result).toMatch(/.*alert\(2\).*/)
})
})
describe('argsArePrimaryKeys', function() {
it("doesn't detect primary keys if primareyKeys and values have different lengths", function() {
expect(Utils.argsArePrimaryKeys([1,2,3], [1])).toBeFalsy()
})
it("doesn't detect primary keys if primary keys are hashes or arrays", function() {
expect(Utils.argsArePrimaryKeys([[]], [1])).toBeFalsy()
})
it('detects primary keys if length is correct and data types are matching', function() {
expect(Utils.argsArePrimaryKeys([1,2,3], ["INTEGER", "INTEGER", "INTEGER"])).toBeTruthy()
})
it("detects primary keys if primary keys are dates and lengths are matching", function() {
expect(Utils.argsArePrimaryKeys([new Date()], ['foo'])).toBeTruthy()
})
})
describe('underscore', function() {
describe('underscoredIf', function() {
it('is defined', function() {
expect(Utils._.underscoredIf).toBeDefined()
})
it('underscores if second param is true', function() {
expect(Utils._.underscoredIf('fooBar', true)).toEqual('foo_bar')
})
it("doesn't underscore if second param is false", function() {
expect(Utils._.underscoredIf('fooBar', false)).toEqual('fooBar')
})
})
describe('camelizeIf', function() {
it('is defined', function() {
expect(Utils._.camelizeIf).toBeDefined()
})
it('camelizes if second param is true', function() {
expect(Utils._.camelizeIf('foo_bar', true)).toEqual('fooBar')
})
it("doesn't camelize if second param is false", function() {
expect(Utils._.underscoredIf('fooBar', true)).toEqual('foo_bar')
})
})
})
})
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'it should work correctly after being idle': function(exit) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() {
User.count().on('success', function(count) {
assert.eql(count, 1)
setTimeout(function() {
User.count().on('success', function() { assert.eql(count, 1) })
exit(function(){})
}, 1000)
})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
var initialize = function(options, callback) {
options = options || {}
options.taskCount = options.taskCount || 1
options.userCount = options.userCount || 1
var num = config.rand()
, User = sequelize.define('User' + num, { name: Sequelize.STRING })
, Task = sequelize.define('Task' + num, { name: Sequelize.STRING })
, chainer = new Sequelize.Utils.QueryChainer
User.hasMany(Task, {as:'Tasks'})
Task.hasMany(User, {as:'Users'})
sequelize.sync({force: true}).on('success', function() {
for(var i = 0; i < options.taskCount; i++)
chainer.add(Task.create({name: 'task'+i}))
for(var i = 0; i < options.userCount; i++)
chainer.add(User.create({name: 'user'+i}))
chainer.run().on('success', function() {
callback(Task, User)
})
})
}
module.exports = {
'it should correctly add an association to the model': function(exit) {
initialize({taskCount:5, userCount:2}, function(Task, User) {
User.all.on('success', function(users) {
Task.all.on('success', function(tasks) {
var user = users[0]
user.getTasks().on('success', function(_tasks) {
assert.eql(_tasks.length, 0)
user.addTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
assert.eql(_tasks.length, 1)
exit(function(){})
})
})
})
})
})
})
},
'it should correctly remove associated objects': function(exit) {
initialize({taskCount:5, userCount:2}, function(Task, User) {
User.all.on('success', function(users) {
Task.all.on('success', function(tasks) {
var user = users[0]
user.getTasks().on('success', function(_tasks) {
assert.eql(_tasks.length, 0)
user.setTasks(tasks).on('success', function() {
user.getTasks().on('success', function(_tasks) {
assert.eql(_tasks.length, tasks.length)
user.removeTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
assert.eql(_tasks.length, tasks.length - 1)
exit(function(){})
})
})
})
})
})
})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'it should correctly add the foreign id': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
Task.belongsTo(User)
assert.eql(Task.attributes['User'+num+'Id'], "INT")
},
'it should correctly add the foreign id with underscore': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING }, {underscored: true})
Task.belongsTo(User)
assert.eql(Task.attributes['user'+num+'_id'], "INT")
},
'it should correctly add the foreign id if foreignKey is passed': function() {
var User = sequelize.define('User' + config.rand(), { username: Sequelize.STRING })
var Task = sequelize.define('Task' + config.rand(), { title: Sequelize.STRING })
Task.belongsTo(User, {foreignKey: 'person_id'})
assert.eql(Task.attributes['person_id'], "INT")
},
'it should define getter and setter': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
Task.belongsTo(User)
var t = Task.build({title: 'asd'})
assert.isDefined(t['setUser'+num])
assert.isDefined(t['getUser'+num])
},
'it should define getter and setter according to passed as option': function() {
var User = sequelize.define('User' + config.rand(), { username: Sequelize.STRING })
var Task = sequelize.define('Task' + config.rand(), { title: Sequelize.STRING })
Task.belongsTo(User, {as: 'Person'})
var t = Task.build({title: 'asd'})
assert.isDefined(t.setPerson)
assert.isDefined(t.getPerson)
},
'it should set the foreign id to null': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
Task.belongsTo(User)
var t = Task.build({title: 'asd'})
assert.isNull(t['User'+num+'Id'])
},
'it should set and get the correct object': function(exit) {
var User = sequelize.define('User' + config.rand(), { username: Sequelize.STRING })
var Task = sequelize.define('Task' + config.rand(), { title: Sequelize.STRING })
Task.belongsTo(User, {as: 'User'})
User.sync({force: true}).on('success', function() {
Task.sync({force: true}).on('success', function() {
User.create({username: 'asd'}).on('success', function(u) {
Task.create({title: 'a task'}).on('success', function(t) {
t.setUser(u).on('success', function() {
t.getUser().on('success', function(user) {
assert.eql(user.username, 'asd')
exit(function(){})
})
})
})
})
})
})
},
'it should correctly delete associations': function(exit) {
var User = sequelize.define('User' + config.rand(), { username: Sequelize.STRING })
var Task = sequelize.define('Task' + config.rand(), { title: Sequelize.STRING })
Task.belongsTo(User, {as: 'User'})
User.sync({force: true}).on('success', function() {
Task.sync({force: true}).on('success', function() {
User.create({username: 'asd'}).on('success', function(u) {
Task.create({title: 'a task'}).on('success', function(t) {
t.setUser(u).on('success', function() {
t.getUser().on('success', function(user) {
assert.eql(user.username, 'asd')
t.setUser(null).on('success', function() {
t.getUser().on('success', function(user) {
assert.isNull(user)
exit(function(){})
})
})
})
})
})
})
})
})
},
'it should correctly handle self associations': function(exit) {
var Person = sequelize.define('Person' + config.rand(), { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.belongsTo(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).on('success', function() {
var p = Person.build()
assert.isDefined(p.setFather)
assert.isDefined(p.setMother)
exit(function(){})
})
},
'it should automatically set the foreignKey if it is a self association': function() {
var num = config.rand()
var Person = sequelize.define('Person' + num, { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother'})
assert.eql(Person.associations["MotherPerson"+num+"s"].options.foreignKey, 'MotherId')
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
var initUsers = function(num, callback) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
, users = []
User.sync({force: true}).on('success', function() {
while(num--) users.push(User.build({name: 'user' + num, bio: 'foobar'}))
callback(users, User)
})
}
module.exports = {
'build should not create database entries': function(exit) {
initUsers(10, function(users, User) {
assert.eql(users.length, 10)
User.all.on('success', function(users) {
assert.eql(users.length, 0)
exit(function(){})
})
})
},
'build should fill the object with default values': function() {
var Task = sequelize.define('Task' + config.rand(), {
title: {type: Sequelize.STRING, defaultValue: 'a task!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
bar: {type: Sequelize.DATE},
foobar: {type: Sequelize.TEXT, defaultValue: 'asd'},
flag: {type: Sequelize.BOOLEAN, defaultValue: false}
})
assert.eql(Task.build().title, 'a task!')
assert.eql(Task.build().foo, 2)
assert.eql(Task.build().bar, null)
assert.eql(Task.build().foobar, 'asd')
assert.eql(Task.build().flag, false)
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'it should correctly count all objects': function(exit) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() {
User.create({username: 'user2'}).on('success', function() {
User.count().on('success', function(count) {
assert.eql(count, 2)
exit(function(){})
})
})
})
})
},
'it should correctly count filtered objects': function(exit) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() {
User.create({username: 'foo'}).on('success', function() {
User.count({where: "username LIKE '%us%'"}).on('success', function(count) {
assert.eql(count, 1)
exit(function(){})
})
})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
var initUsers = function(num, callback) {
return sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
}
module.exports = {
'do not allow duplicated records with unique:true': function(exit) {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, unique: true}
})
User.sync({force:true}).on('success', function() {
User.create({username:'foo'}).on('success', function() {
User.create({username: 'foo'}).on('failure', function(err) {
assert.eql(err.message, "Duplicate entry 'foo' for key 'username'")
exit(function(){})
})
})
})
},
'it should raise an error if created object breaks definition constraints': function(exit) {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, unique: true},
smth: {type: Sequelize.STRING, allowNull: false}
})
User.sync({force:true}).on('success', function() {
User.create({username: 'foo', smth: null}).on('failure', function(err) {
assert.eql(err.message, "Column 'smth' cannot be null")
User.create({username: 'foo', smth: 'foo'}).on('success', function() {
User.create({username: 'foo', smth: 'bar'}).on('failure', function(err) {
assert.eql(err.message, "Duplicate entry 'foo' for key 'username'")
exit(function(){})
})
})
})
})
},
'it should set the auto increment field to the insert id': function(exit) {
var User = sequelize.define('User' + config.rand(), {
userid: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false}
})
User.sync({force:true}).on('success', function() {
User.create({}).on('success', function(user) {
assert.eql(user.userid, 1)
exit(function(){})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'destroy should delete a saved record from the database': function(exit) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
User.sync({force: true}).on('success', function() {
User.create({name: 'hallo', bio: 'welt'}).on('success', function(u) {
User.all.on('success', function(users) {
assert.eql(users.length, 1)
u.destroy().on('success', function() {
User.all.on('success', function(users) {
assert.eql(users.length, 0)
exit(function(){})
})
})
})
})
})
},
'destroy should mark the record as deleted if paranoid is activated': function(exit) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT }, {paranoid:true})
User.sync({force: true}).on('success', function() {
User.create({name: 'asd', bio: 'asd'}).on('success', function(u) {
assert.isNull(u.deletedAt)
u.destroy().on('success', function(u) {
assert.isNotNull(u.deletedAt)
exit(function(){})
})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, { logging: false, define: { charset: 'latin1' } })
module.exports = {
'it should correctly determine equal objects': function(exit) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
User.sync({force: true}).on('success', function() {
User.create({name: 'hallo', bio: 'welt'}).on('success', function(u) {
assert.eql(u.equals(u), true)
exit(function(){})
})
})
},
'it should correctly work with different primary keys': function(exit) {
var User = sequelize.define('User' + config.rand(), {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING, bio: Sequelize.TEXT
})
User.sync({force: true, charset: 'latin1'}).on('success', function() {
User.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).on('success', function(u) {
assert.eql(u.equals(u), true)
exit(function(){})
}).on('failure', function(err) { console.log(err) })
}).on('failure', function(err) { console.log(err) })
},
'equalsOneOf should work': function(exit) {
var User = sequelize.define('User' + config.rand(), {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING, bio: Sequelize.TEXT
})
User.sync({force: true}).on('success', function() {
User.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).on('success', function(u) {
assert.eql(u.equalsOneOf([u, {a:1}]), true)
assert.eql(u.equalsOneOf([{b:2}, {a:1}]), false)
exit(function(){})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
var initUsers = function(num, callback) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
var createUser = function() {
User.create({name: 'user' + num, bio: 'foobar'}).on('success', function(user){
if(--num) createUser()
else callback(user, User)
})
}
User.sync({force: true}).on('success', function() {
createUser()
})
}
module.exports = {
'all should return all created models': function(exit) {
initUsers(2, function(_, User) {
User.all.on('success', function(users) {
assert.eql(users.length, 2)
exit(function(){})
})
})
},
'find should return a single model': function(exit) {
initUsers(2, function(lastInsertedUser, User) {
User.find(lastInsertedUser.id).on('success', function(user) {
assert.eql(user.id, lastInsertedUser.id)
exit(function(){})
})
})
},
'find a specific user': function(exit) {
initUsers(2, function(_, User) {
var username = 'user1'
User.find({where: {name: username}}).on('success', function(user) {
assert.eql(user.name, username)
exit(function(){})
})
})
},
'should find no user with invalid conditions': function(exit) {
initUsers(2, function(_, User) {
User.find({where: {name: 'foo'}}).on('success', function(user) {
assert.eql(user, null)
exit(function(){})
})
})
},
'find should ignore passed limit': function(exit) {
initUsers(2, function(_, User) {
User.find({limit: 10}).on('success', function(user) {
// it returns an object instead of an array
assert.eql(user.hasOwnProperty('name'), true)
exit(function(){})
})
})
},
'find should find records by primaryKeys': function(exit) {
var User = sequelize.define('User' + config.rand(), {
identifier: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING
})
User.sync({force:true}).on('success', function() {
User.create({identifier: 'an identifier', name: 'John'}).on('success', function(u) {
assert.isUndefined(u.id)
User.find('an identifier').on('success', function(u2) {
assert.eql(u.identifier, 'an identifier')
assert.eql(u.name, 'John')
exit(function(){})
})
})
})
},
'findAll should find all records': function(exit) {
initUsers(2, function(_, User) {
User.findAll().on('success', function(users) {
assert.eql(users.length, 2)
exit(function(){})
})
})
},
'findAll should return the correct elements for passed conditions': function(exit) {
initUsers(2, function(lastUser, User) {
User.findAll({where: "id != " + lastUser.id}).on('success', function(users) {
assert.eql(users.length, 1)
exit(function(){})
})
})
},
'findAll should work with array usage': function(exit) {
initUsers(2, function(lastUser, User) {
User.findAll({where: ['id = ?', lastUser.id]}).on('success', function(users) {
assert.eql(users.length, 1)
assert.eql(users[0].id, lastUser.id)
exit(function(){})
})
})
},
'findAll should return the correct order when order is passed': function(exit) {
initUsers(2, function(lastUser, User) {
User.findAll({order: "id DESC"}).on('success', function(users) {
assert.eql(users[0].id > users[1].id, true)
exit(function(){})
})
})
},
'findAll should handle offset and limit correctly': function(exit) {
initUsers(10, function(_, User) {
User.findAll({limit: 2, offset: 2}).on('success', function(users) {
assert.eql(users.length, 2)
assert.eql(users[0].id, 3)
exit(function(){})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'it should correctly add the foreign id - monodirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasMany(Task)
assert.eql(Task.attributes['User'+num+'Id'], "INT")
},
'it should correctly add the foreign ids - bidirectional': function(exit) {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
Task.hasMany(User)
User.hasMany(Task)
assert.isUndefined(Task.attributes['User'+num+'Id'])
assert.isUndefined(User.attributes['User'+num+'Id'])
sequelize.modelManager.models.forEach(function(model) {
if(model.tableName == (Task.tableName + User.tableName)) {
assert.isDefined(model.attributes['User'+num+'Id'])
assert.isDefined(model.attributes['Task'+num+'Id'])
exit(function(){})
}
})
},
'it should correctly add the foreign id with underscore - monodirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING}, {underscored: true})
Task.hasMany(User)
assert.isDefined(User.attributes['task'+ num +'_id'])
},
'it should correctly add the foreign id with underscore - bidirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING }, {underscored: true})
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
Task.hasMany(User)
User.hasMany(Task)
assert.isUndefined(Task.attributes['user'+ num +'_id'])
assert.isUndefined(User.attributes['user'+ num +'_id'])
sequelize.modelManager.models.forEach(function(model) {
if(model.tableName == (Task.tableName + User.tableName)) {
assert.isDefined(model.attributes['user'+ num +'_id'])
assert.isDefined(model.attributes['Task'+ num +'Id'])
}
})
},
'it should correctly add the foreign id when defining the foreignkey as option - monodirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING }, {underscored: true})
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasMany(Task, {foreignKey: 'person_id'})
assert.eql(Task.attributes.person_id, "INT")
},
'it should correctly add the foreign id when defining the foreignkey as option - bidirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING }, {underscored: true})
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasMany(Task, {foreignKey: 'person_id'})
Task.hasMany(User, {foreignKey: 'work_item_id'})
sequelize.modelManager.models.forEach(function(model) {
if(model.tableName == (Task.tableName + User.tableName)) {
assert.isDefined(model.attributes.person_id)
assert.isDefined(model.attributes.work_item_id)
}
})
},
'it should define getter and setter - monodirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasMany(Task)
var u = User.build({username: 'asd'})
assert.isDefined(u['setTask'+num+"s"])
assert.isDefined(u['getTask'+num+"s"])
},
'it should define getter and setter - bidirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasMany(Task)
Task.hasMany(User)
var u = User.build({username: 'asd'})
assert.isDefined(u['setTask'+num+"s"])
assert.isDefined(u['getTask'+num+"s"])
var t = Task.build({title: 'foobar'})
assert.isDefined(t['setUser'+num+'s'])
assert.isDefined(t['getUser'+num+'s'])
},
'it should define getter and setter according to as option - monodirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasMany(Task, {as: 'Tasks'})
var u = User.build({username: 'asd'})
assert.isDefined(u.setTasks)
assert.isDefined(u.getTasks)
},
'it should define getter and setter according to as option - bidirectional': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasMany(Task, {as: 'Tasks'})
Task.hasMany(User, {as: 'Users'})
var u = User.build({username: 'asd'})
assert.isDefined(u.setTasks)
assert.isDefined(u.getTasks)
var t = Task.build({title: 'asd'})
assert.isDefined(t.setUsers)
assert.isDefined(t.getUsers)
},
'it should set and get the correct objects - monodirectional': function(exit) {
var User = sequelize.define('User' + config.rand(), { username: Sequelize.STRING })
var Task = sequelize.define('Task' + config.rand(), { title: Sequelize.STRING })
User.hasMany(Task, {as: 'Tasks'})
User.sync({force: true}).on('success', function() {
Task.sync({force: true}).on('success', function() {
User.create({username: 'name'}).on('success', function(user) {
Task.create({title: 'task1'}).on('success', function(task1) {
Task.create({title: 'task2'}).on('success', function(task2) {
user.setTasks([task1, task2]).on('success', function() {
user.getTasks().on('success', function(tasks) {
assert.eql(tasks.length, 2)
exit(function(){})
})
})
})
})
})
})
})
},
'it should set and get the correct objects - bidirectional': function(exit) {
var User = sequelize.define('User' + config.rand(), { username: Sequelize.STRING })
var Task = sequelize.define('Task' + config.rand(), { title: Sequelize.STRING })
User.hasMany(Task, {as: 'Tasks'})
Task.hasMany(User, {as: 'Users'})
User.sync({force: true}).on('success', function() {
Task.sync({force: true}).on('success', function() {
User.create({username: 'name'}).on('success', function(user1) {
User.create({username: 'name2'}).on('success', function(user2) {
Task.create({title: 'task1'}).on('success', function(task1) {
Task.create({title: 'task2'}).on('success', function(task2) {
user1.setTasks([task1, task2]).on('success', function() {
user1.getTasks().on('success', function(tasks) {
assert.eql(tasks.length, 2)
task2.setUsers([user1, user2]).on('success', function() {
task2.getUsers().on('success', function(users) {
assert.eql(users.length, 2)
exit(function(){})
})
})
})
})
})
})
})
})
})
})
},
'it should correctly build the connector model names': function(exit){
var num = config.rand()
, Person = sequelize.define('Person' + num, { name: Sequelize.STRING })
Person.hasMany(Person, {as: 'Children'})
Person.hasMany(Person, {as: 'Friends'})
Person.hasMany(Person, {as: 'CoWorkers'})
Person.sync({force: true}).on('success', function() {
var modelNames = sequelize.modelManager.models.map(function(model) { return model.tableName })
, expectation = ["Person" + num + "s", "ChildrenPerson" + num + "s", "CoWorkersPerson" + num + "s", "FriendsPerson" + num + "s"]
expectation.forEach(function(ex) {
assert.eql(modelNames.indexOf(ex) > -1, true)
})
exit(function(){})
})
},
'it should correctly get and set the connected models': function(exit) {
var num = config.rand()
, Person = sequelize.define('Person' + num, { name: Sequelize.STRING })
Person.hasMany(Person, {as: 'Children'})
Person.hasMany(Person, {as: 'Friends'})
Person.hasMany(Person, {as: 'CoWorkers'})
Person.sync({force: true}).on('success', function() {
Person.create({name: 'foobar'}).on('success', function(person) {
Person.create({name: 'friend'}).on('success', function(friend) {
person.setFriends([friend]).on('success', function() {
person.getFriends().on('success', function(friends) {
assert.eql(friends.length, 1)
assert.eql(friends[0].name, 'friend')
exit(function(){})
})
})
})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'it should correctly add the foreign id': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasOne(Task)
assert.eql(Task.attributes['User'+num+'Id'], "INT")
},
'it should correctly add the foreign id with underscore': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING }, {underscored: true})
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasOne(Task)
assert.eql(Task.attributes['user'+num+'_id'], "INT")
},
'it should correctly add the foreign id when defining the foreignkey as option': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING }, {underscored: true})
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasOne(Task, {foreignKey: 'person_id'})
assert.eql(Task.attributes.person_id, "INT")
},
'it should define getter and setter': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasOne(Task)
var u = User.build({username: 'asd'})
assert.isDefined(u['setTask'+num])
assert.isDefined(u['getTask'+num])
},
'it should define getter and setter according to as option': function() {
var num = config.rand()
var User = sequelize.define('User' + num, { username: Sequelize.STRING })
var Task = sequelize.define('Task' + num, { title: Sequelize.STRING })
User.hasOne(Task, {as: 'Task'})
var u = User.build({username: 'asd'})
assert.isDefined(u.setTask)
assert.isDefined(u.getTask)
},
'it should set and get the correct objects': function(exit) {
var User = sequelize.define('User' + config.rand(), { username: Sequelize.STRING })
var Task = sequelize.define('Task' + config.rand(), { title: Sequelize.STRING })
User.hasOne(Task, {as: 'Task'})
User.sync({force: true}).on('success', function() {
Task.sync({force: true}).on('success', function() {
User.create({username: 'name'}).on('success', function(user) {
Task.create({title: 'snafu'}).on('success', function(task) {
user.setTask(task).on('success', function() {
user.getTask().on('success', function(task2) {
assert.eql(task.title, task2.title)
exit(function(){})
})
})
})
})
})
})
},
'it should correctly unset the obsolete objects': function(exit) {
var User = sequelize.define('User' + config.rand(), { username: Sequelize.STRING })
var Task = sequelize.define('Task' + config.rand(), { title: Sequelize.STRING })
User.hasOne(Task, {as: 'Task'})
User.sync({force: true}).on('success', function() {
Task.sync({force: true}).on('success', function() {
User.create({username: 'name'}).on('success', function(user) {
Task.create({title: 'snafu'}).on('success', function(task) {
Task.create({title: 'another task'}).on('success', function(task2) {
user.setTask(task).on('success', function() {
user.getTask().on('success', function(_task) {
assert.eql(task.title, _task.title)
user.setTask(task2).on('success', function() {
user.getTask().on('success', function(_task2) {
assert.eql(task2.title, _task2.title)
exit(function(){})
})
})
})
})
})
})
})
})
})
},
'it should correctly associate with itself': function(exit) {
var Person = sequelize.define('Person' + config.rand(), { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.hasOne(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).on('success', function() {
var p = Person.build()
assert.isDefined(p.setFather)
assert.isDefined(p.setMother)
exit(function(){})
})
},
'it should automatically set the foreignKey if it is a self association': function() {
var num = config.rand()
var Person = sequelize.define('Person' + num, { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother'})
assert.eql(Person.associations["MotherPerson"+num+"s"].options.foreignKey, 'MotherId')
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
var initUsers = function(num, callback) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
, users = []
User.sync({force: true}).on('success', function() {
while(num--) users.push(User.build({name: 'user' + num, bio: 'foobar'}))
callback(users, User)
})
}
module.exports = {
'build should not create database entries': function(exit) {
initUsers(1, function(users, User) {
assert.isNull(users[0].id)
assert.eql(users[0].isNewRecord, true)
exit(function(){})
})
},
'should be false for saved objects': function(exit) {
initUsers(1, function(users, User) {
users[0].save().on('success', function(user) {
assert.eql(user.isNewRecord, false)
exit(function(){})
})
})
},
'should be false for created objects': function(exit) {
initUsers(1, function(users, User) {
User.create({name: 'user'}).on('success', function(user) {
assert.eql(user.isNewRecord, false)
exit(function(){})
})
})
},
'should be false for find': function(exit) {
initUsers(1, function(users, User) {
User.create({name: 'user'}).on('success', function(user) {
User.find(user.id).on('success', function(user) {
assert.eql(user.isNewRecord, false)
exit(function(){})
})
})
})
},
'should be false for findAll': function(exit) {
var chainer = new Sequelize.Utils.QueryChainer
initUsers(10, function(users, User) {
users.forEach(function(user) {
chainer.add(user.save())
})
chainer.run().on('success', function() {
User.findAll().on('success', function(users) {
users.forEach(function(u) {
assert.eql(u.isNewRecord, false)
})
exit(function() {})
})
})
})
}
}
var assert = require("assert")
, ModelDefinition = require("./../../lib/sequelize/model-definition")
module.exports = {
'mixin should be correctly added to the model': function() {
assert.isDefined(ModelDefinition.prototype.hasOne)
assert.isDefined(ModelDefinition.prototype.hasMany)
assert.isDefined(ModelDefinition.prototype.belongsTo)
}
}
\ No newline at end of file
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'save should add a record to the database': function(exit) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
User.sync({force: true}).on('success', function() {
var u = User.build({name: 'hallo', bio: 'welt'})
User.all.on('success', function(users) {
assert.eql(users.length, 0)
u.save().on('success', function() {
User.all.on('success', function(users) {
assert.eql(users.length, 1)
assert.eql(users[0].name, 'hallo')
exit(function(){})
})
})
})
})
},
'save should update the timestamp updated_at': function(exit) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
User.sync({force: true}).on('success', function() {
var now = Date.now()
// timeout is needed, in order to check the update of the timestamp
setTimeout(function() {
var u = User.build({name: 'foo', bio: 'bar'})
, uNow = u.updatedAt
assert.eql(true, uNow.getTime() > now)
setTimeout(function() {
u.save().on('success', function() {
assert.eql(true, uNow.getTime() < u.updatedAt.getTime())
exit(function(){})
})
}, 10)
}, 10)
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
, User = sequelize.define('User', { name: Sequelize.STRING, bio: Sequelize.TEXT })
module.exports = {
'sync should work with correct database config': function(exit) {
User.sync().on('success', function(){exit(function(){})})
},
'sync should fail with incorrect database config': function(exit) {
var s = new Sequelize('foo', 'bar', null, {logging: false})
var User2 = s.define('User', { name: Sequelize.STRING, bio: Sequelize.TEXT })
User2.sync().on('failure', function(err){
exit(function(){}
)})
},
'drop should work': function(exit) {
User.drop().on('success', function(){exit(function(){})})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
, User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT })
module.exports = {
'it should update the attributes': function(exit) {
User.sync({force:true}).on('success', function() {
User.create({name: 'snafu'}).on('success', function(user) {
assert.eql(user.name, 'snafu')
user.updateAttributes({name: 'foobar'}).on('success', function(user) {
assert.eql(user.name, 'foobar')
exit(function(){})
})
})
})
},
'it should not set attributes which were not defined': function(exit) {
User.sync({force:true}).on('success', function() {
User.create({name: 'snafu'}).on('success', function(user) {
user.updateAttributes({name: 'foobar', foo: 'bar'}).on('success', function(user) {
assert.eql(user.name, 'foobar')
assert.isUndefined(user.foo)
exit(function(){})
})
})
})
},
'it should not set primary keys or timestamps': function(exit) {
var User = sequelize.define('User' + config.rand(), {
name: Sequelize.STRING, bio: Sequelize.TEXT, identifier: {type: Sequelize.STRING, primaryKey: true}
})
User.sync({force:true}).on('success', function() {
User.create({name: 'snafu', identifier: 'identifier'}).on('success', function(user) {
var oldCreatedAt = user.createdAt
, oldIdentifier = user.identifier
user.updateAttributes({name: 'foobar', createdAt: new Date(2000, 1, 1), identifier: 'another identifier'}).on('success', function(user) {
assert.eql(user.createdAt, oldCreatedAt)
assert.eql(user.identifier, oldIdentifier)
exit(function(){})
})
})
})
},
"it should use the primary keys in the where clause": function(exit) {
var User = sequelize.define('User' + config.rand(), {
name: Sequelize.STRING, bio: Sequelize.TEXT, identifier: {type: Sequelize.STRING, primaryKey: true}
})
User.sync({force:true}).on('success', function() {
User.create({name: 'snafu', identifier: 'identifier'}).on('success', function(user) {
var query = user.updateAttributes({name: 'foobar'})
assert.match(query.sql, /WHERE `identifier`..identifier./)
exit(function(){})
})
})
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
var initUsers = function(num, callback) {
var User = sequelize.define('User' + config.rand(), { name: Sequelize.STRING, bio: Sequelize.TEXT }, {timestamps:false})
, users = []
User.sync({force: true}).on('success', function() {
while(num--) users.push(User.build({name: 'user' + num, bio: 'foobar'}))
callback(users, User)
})
}
module.exports = {
'build should not create database entries': function(exit) {
initUsers(1, function(users, User) {
assert.eql(users[0].values, {"name":"user0","bio":"foobar","id":null})
exit(function(){})
})
}
}
var assert = require("assert")
, QueryGenerator = require("../../lib/sequelize/query-generator")
, eql = assert.equal
module.exports = {
'create table query': function() {
eql(QueryGenerator.createTableQuery('myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}), "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB;")
eql(QueryGenerator.createTableQuery('myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {engine: 'MyISAM'}), "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;")
eql(QueryGenerator.createTableQuery('myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {charset: 'latin1'}), "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;")
},
'drop table query': function() {
eql(QueryGenerator.dropTableQuery('myTable'), "DROP TABLE IF EXISTS `myTable`;")
},
'select query #default': function() {
eql(QueryGenerator.selectQuery('myTable'), "SELECT * FROM `myTable`;")
},
'select query #attributes': function() {
eql(QueryGenerator.selectQuery('myTable', {attributes: ['id', 'name']}), "SELECT `id`, `name` FROM `myTable`;")
},
'select query #where': function() {
eql(QueryGenerator.selectQuery('myTable', {where: {id: 2}}), "SELECT * FROM `myTable` WHERE `id`=2;")
eql(QueryGenerator.selectQuery('myTable', {where: {name: 'foo'}}), "SELECT * FROM `myTable` WHERE `name`='foo';")
eql(QueryGenerator.selectQuery('myTable', {where: {name: "foo';DROP TABLE myTable;"}}), "SELECT * FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;';")
eql(QueryGenerator.selectQuery('myTable', {where: 2}), "SELECT * FROM `myTable` WHERE `id`=2;")
eql(QueryGenerator.selectQuery('myTable', {where: "foo='bar'"}), "SELECT * FROM `myTable` WHERE foo='bar';")
},
'select query #order': function() {
eql(QueryGenerator.selectQuery('myTable', {order: "id DESC"}), "SELECT * FROM `myTable` ORDER BY id DESC;")
},
'select query #group': function() {
eql(QueryGenerator.selectQuery('myTable', {group: "name"}), "SELECT * FROM `myTable` GROUP BY `name`;")
},
'select query #limit': function() {
eql(QueryGenerator.selectQuery('myTable', {limit: 10}), "SELECT * FROM `myTable` LIMIT 10;")
},
'select query #offset': function() {
eql(QueryGenerator.selectQuery('myTable', {limit: 10, offset: 2}), "SELECT * FROM `myTable` LIMIT 2, 10;")
eql(QueryGenerator.selectQuery('myTable', {offset: 2}), "SELECT * FROM `myTable`;")
},
'insert query': function() {
eql(QueryGenerator.insertQuery('myTable', {name: 'foo'}), "INSERT INTO `myTable` (`name`) VALUES ('foo');")
eql(QueryGenerator.insertQuery('myTable', {name: "foo';DROP TABLE myTable;"}), "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;');")
eql(QueryGenerator.insertQuery('myTable', {name: 'foo', birthday: new Date(2011, 2, 27, 10, 1, 55)}), "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55');")
eql(QueryGenerator.insertQuery('myTable', {name: 'foo', foo: 1}), "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);")
},
'update query': function() {
eql(
QueryGenerator.updateQuery('myTable', {name: 'foo', birthday: new Date(2011, 2, 27, 10, 1, 55)}, {id: 2}),
"UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2"
)
eql(
QueryGenerator.updateQuery('myTable', {name: 'foo', birthday: new Date(2011, 2, 27, 10, 1, 55)}, 2),
"UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2"
)
eql(QueryGenerator.updateQuery('myTable', {bar: 2}, {name: 'foo'}), "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'")
eql(QueryGenerator.updateQuery('myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}), "UPDATE `myTable` SET `name`='foo\\';DROP TABLE myTable;' WHERE `name`='foo'")
},
'deleteQuery': function() {
eql(QueryGenerator.deleteQuery('myTable', {name: 'foo'}), "DELETE FROM `myTable` WHERE `name`='foo' LIMIT 1")
eql(QueryGenerator.deleteQuery('myTable', 1), "DELETE FROM `myTable` WHERE `id`=1 LIMIT 1")
eql(QueryGenerator.deleteQuery('myTable', 1, {limit: 10}), "DELETE FROM `myTable` WHERE `id`=1 LIMIT 10")
eql(QueryGenerator.deleteQuery('myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}), "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;' LIMIT 10")
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize('database', 'username', 'password')
module.exports = {
'it should add a new model to the model-manager': function() {
var s = new Sequelize('database', 'username', 'password')
assert.eql(s.modelManager.all.length, 0)
s.define('foo', { title: Sequelize.STRING })
assert.eql(s.modelManager.all.length, 1)
},
'it should handle extended attributes correctly - unique': function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, unique: true}
}, { timestamps: false })
assert.eql(User.attributes, {username:"VARCHAR(255) UNIQUE",id:"INT NOT NULL auto_increment PRIMARY KEY"})
},
'it should handle extended attributes correctly - default': function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, defaultValue: 'foo'}
}, { timestamps: false })
assert.eql(User.attributes, {username:"VARCHAR(255) DEFAULT 'foo'",id:"INT NOT NULL auto_increment PRIMARY KEY"})
},
'it should handle extended attributes correctly - null': function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, allowNull: false}
}, { timestamps: false })
assert.eql(User.attributes, {username:"VARCHAR(255) NOT NULL",id:"INT NOT NULL auto_increment PRIMARY KEY"})
},
'it should handle extended attributes correctly - primary key': function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, primaryKey: true}
}, { timestamps: false })
assert.eql(User.attributes, {username:"VARCHAR(255) PRIMARY KEY"})
},
'primaryKeys should be correctly determined': function() {
var User = sequelize.define('User' + config.rand(), {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: Sequelize.STRING
})
assert.eql(User.primaryKeys, {"foo":"VARCHAR(255) PRIMARY KEY"})
},
'it should add updatedAt and createdAt if timestamps is undefined or true': function() {
var User1 = sequelize.define('User' + config.rand(), {})
var User2 = sequelize.define('User' + config.rand(), {}, { timestamps: true })
assert.eql(User1.attributes, {id:"INT NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
assert.eql(User2.attributes, {id:"INT NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
},
'it should add deletedAt if paranoid is true': function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true })
assert.eql(User.attributes, {id:"INT NOT NULL auto_increment PRIMARY KEY", deletedAt:"DATETIME", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
},
'timestamp columns should be underscored if underscored is passed': function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true, underscored: true })
assert.eql(User.attributes, {id:"INT NOT NULL auto_increment PRIMARY KEY", deleted_at:"DATETIME", updated_at:"DATETIME NOT NULL", created_at:"DATETIME NOT NULL"})
},
'tablenames should be as passed if they are frozen': function() {
var User = sequelize.define('User', {}, {freezeTableName: true})
assert.eql(User.tableName, 'User')
},
'tablenames should be pluralized if they are not frozen': function() {
var User = sequelize.define('User', {}, {freezeTableName: false})
assert.eql(User.tableName, 'Users')
},
'it should add the passed class/instance methods': function() {
var User = sequelize.define('User', {}, {
classMethods: { doSmth: function(){ return 1 } },
instanceMethods: { makeItSo: function(){ return 2}}
})
assert.isDefined(User.doSmth)
assert.eql(User.doSmth(), 1)
assert.isUndefined(User.makeItSo)
assert.isDefined(User.build().makeItSo)
assert.eql(User.build().makeItSo(), 2)
},
'it shouldn\'t allow two auto increment fields': function() {
assert.throws(function () {
var User = sequelize.define('User', {
userid: {type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true},
userscore: {type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true},
})
})
}
}
\ No newline at end of file
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'the import should work correctly': function() {
var Project = sequelize.import(__dirname + "/../project")
assert.isDefined(Project)
}
}
var assert = require("assert")
, config = require("./../config")
, Sequelize = require("./../../index")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, define: { charset: 'latin1' }})
module.exports = {
'it should sync all models - so instances can be created and saved to the database without failures': function(exit) {
var Project = sequelize.define('project' + config.rand(), {
title: Sequelize.STRING
})
var Task = sequelize.define('task' + config.rand(), {
title: Sequelize.STRING
})
sequelize.sync().on('success', function() {
Project.create({title: 'bla'}).on('success', function() {
Task.create({title: 'bla'}).on('success', function() {
exit(function(){})
})
})
})
}
}
var assert = require("assert")
, Utils = require("../../lib/sequelize/utils")
module.exports = {
'it should be false if primaryKeys and args have different lengths': function() {
assert.eql(false, Utils.argsArePrimaryKeys([1,2,3], [1]))
},
'it should be false if primaryKeys are hashes or arrays': function() {
assert.eql(false, Utils.argsArePrimaryKeys([[]], [1]))
},
'it should be true if primaryKeys are primitive data types and lengths are matching': function() {
assert.eql(true, Utils.argsArePrimaryKeys([1,2,3], ["INT", "INT", "INT"]))
},
'it should be true if primaryKeys are dates and lengths are matching': function() {
assert.eql(true, Utils.argsArePrimaryKeys([new Date()], ['foo']))
}
}
\ No newline at end of file
var assert = require("assert")
, Utils = require("../../lib/sequelize/utils")
module.exports = {
'underscoredIf should be defined': function() {
assert.isDefined(Utils._.underscoredIf)
},
'underscoredIf should work correctly': function() {
assert.eql(Utils._.underscoredIf('fooBar', false), 'fooBar')
assert.eql(Utils._.underscoredIf('fooBar', true), 'foo_bar')
},
'camelizeIf should be defined': function() {
assert.isDefined(Utils._.camelizeIf)
},
'camelizeIf should work correctly': function() {
assert.eql(Utils._.camelizeIf('foo_bar', false), 'foo_bar')
assert.eql(Utils._.camelizeIf('foo_bar', true), 'fooBar')
}
}
\ No newline at end of file
module.exports = {
username: "meg",
password: "meg",
database: 'test',
host: '127.0.0.1',
rand: function() {
return parseInt(Math.random() * 999)
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!