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

Commit 5703c754 by Meg Sharkey

merged

2 parents d4d1b11f 370c22d7
Showing with 3149 additions and 1544 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")
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()
})
})
})
})
})
})
})
})
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!