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

Commit 1350aa91 by Sascha Depold

Merge branch 'master' of github.com:sequelize/sequelize

2 parents 30ad8c19 c5fee254
......@@ -30,7 +30,7 @@ The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databa
- Associations
- Importing definitions from single files
## Documentation, Examples and Updates ##
## Documentation and Updates ##
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).
......@@ -42,6 +42,11 @@ Also make sure to take a look at the examples in the repository. The website wil
- [Google Groups](https://groups.google.com/forum/#!forum/sequelize)
- [XING](https://www.xing.com/net/priec1b5cx/sequelize) (pretty much inactive, but you might want to name it on your profile)
## Running Examples
Instructions for running samples are located in the [example directory](https://github.com/sequelize/sequelize/tree/master/examples). Try these samples in a live sandbox environment:
<a href="https://runnable.com/sequelize" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png"></a>
## Roadmap
A very basic roadmap. Chances aren't too bad, that not mentioned things are implemented as well. Don't panic :)
......@@ -70,8 +75,12 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 2.0.0
- ~~save datetimes in UTC~~
- encapsulate attributes if a dao inside the attributes property + add getters and setters
- encapsulate attributes if a dao inside the attributes property
- ~~add getters and setters for dao~~ Implemented in [#538](https://github.com/sequelize/sequelize/pull/538), thanks to iamjochem
- add proper error message everywhere
- refactor validate() output data structure, separating field-specific errors
from general model validator errors (i.e.
`{fields: {field1: ['field1error1']}, model: ['modelError1']}` or similar)
## Collaboration 2.0 ##
......
# v1.7.0 #
- [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango
- [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx
- [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics
- [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango
- [BUG] Null dates don't break SQLite anymore. [#572](https://github.com/sequelize/sequelize/pull/572). thanks to mweibel
- [BUG] Correctly handle booleans in MySQL. [#608](https://github.com/sequelize/sequelize/pull/608). Thanks to terraflubb
- [BUG] Fixed empty where conditions in MySQL. [#619](https://github.com/sequelize/sequelize/pull/619). Thanks to terraflubb
- [BUG] Allow overriding of default columns. [#635](https://github.com/sequelize/sequelize/pull/635). Thanks to sevastos
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
- [FEATURE] Support for bulk insert (`<DAOFactory>.bulkCreate()`, update (`<DAOFactory>.update()`) and delete (`<DAOFactory>.destroy()`) [#569](https://github.com/sequelize/sequelize/pull/569). thanks to optilude
- [FEATURE] Add an extra `queryOptions` parameter to `DAOFactory.find` and `DAOFactory.findAll`. This allows a user to specify `{ raw: true }`, meaning that the raw result should be returned, instead of built DAOs. Usefull for queries returning large datasets, see [#611](https://github.com/sequelize/sequelize/pull/611) janmeier
- [FEATURE] Added convenient data types. [#616](https://github.com/sequelize/sequelize/pull/616). Thanks to Costent
- [FEATURE] Binary is more verbose now. [#612](https://github.com/sequelize/sequelize/pull/612). Thanks to terraflubb
- [FEATURE] Promises/A support. [#626](https://github.com/sequelize/sequelize/pull/626). Thanks to kevinbeaty
- [FEATURE] Added Getters/Setters method for DAO. [#538](https://github.com/sequelize/sequelize/pull/538). Thanks to iamjochem
- [FEATURE] Added model wide validations. [#640](https://github.com/sequelize/sequelize/pull/640). Thanks to tremby
# v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
......
......@@ -5,11 +5,11 @@
First of all, Person is getting associated via many-to-many with other Person objects (e.g. Person.hasMany('brothers')).
Afterwards a Person becomes associated with a 'father' and a mother using a one-to-one association created by hasOneAndBelongsTo.
The last association has the type many-to-one and is defined by the function hasManyAndBelongsTo.
The rest of the example is about setting and getting the associated data.
The rest of the example is about setting and getting the associated data.
*/
var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
, Person = sequelize.define('Person', { name: Sequelize.STRING })
, Pet = sequelize.define('Pet', { name: Sequelize.STRING })
......@@ -36,13 +36,13 @@ sequelize.sync({force:true}).on('success', function() {
.add(brother.save())
.add(sister.save())
.add(pet.save())
chainer.run().on('success', function() {
person.setMother(mother).on('success', function() { person.getMother().on('success', function(mom) {
console.log('my mom: ', mom.name)
console.log('my mom: ', mom.name)
})})
person.setFather(father).on('success', function() { person.getFather().on('success', function(dad) {
console.log('my dad: ', dad.name)
console.log('my dad: ', dad.name)
})})
person.setBrothers([brother]).on('success', function() { person.getBrothers().on('success', function(brothers) {
console.log("my brothers: " + brothers.map(function(b) { return b.name }))
......
var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
var Person = sequelize.define('Person', { name: Sequelize.STRING })
......@@ -8,12 +8,12 @@ var Person = sequelize.define('Person', { name: Sequelize.STRING })
sequelize.sync({force: true}).on('success', function() {
var count = 10,
queries = []
for(var i = 0; i < count; i++)
chainer.add(Person.create({name: 'someone' + (i % 3)}))
console.log("Begin to save " + count + " items!")
chainer.run().on('success', function() {
console.log("finished")
Person.count().on('success', function(count) {
......
var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
var Person = sequelize.define('Person',
var Person = sequelize.define('Person',
{ name: Sequelize.STRING,
age : Sequelize.INTEGER
......@@ -12,12 +12,12 @@ var Person = sequelize.define('Person',
sequelize.sync({force: true}).on('success', function() {
var count = 10,
queries = []
for(var i = 0; i < count; i++)
chainer.add(Person.create({name: 'someone' + (i % 3), age : i+5}))
console.log("Begin to save " + count + " items!")
chainer.run().on('success', function() {
console.log("finished")
Person.max('age').on('success', function(max) {
......
var Sequelize = require(__dirname + "/../../index")
, config = require("../../test/config")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, host: config.host})
, QueryChainer = Sequelize.Utils.QueryChainer
, sys = require("sys")
......@@ -10,16 +10,16 @@ Person.sync({force: true}).on('success', function() {
var start = Date.now()
, count = 10000
, done = 0
var createPerson = function() {
Person.create({name: 'someone'}).on('success', function() {
if(++done == count) {
var duration = (Date.now() - start)
console.log("\nFinished creation of " + count + " people. Took: " + duration + "ms (avg: " + (duration/count) + "ms)")
start = Date.now()
console.log("Will now read them from the database:")
Person.findAll().on('success', function(people) {
console.log("Reading " + people.length + " items took: " + (Date.now() - start) + "ms")
})
......@@ -35,7 +35,7 @@ Person.sync({force: true}).on('success', function() {
for(var i = 0; i < count; i++) {
createPerson()
}
}).on('failure', function(err) {
console.log(err)
})
\ No newline at end of file
var fs = require("fs")
, Sequelize = require("sequelize")
, sequelize = new Sequelize('sequelize_test', 'root', null, {logging: false})
, Image = sequelize.define('Image', { data: Sequelize.TEXT })
/*
Title: Default values
Image.sync({force: true}).on('success', function() {
console.log("reading image")
var image = fs.readFileSync(__dirname + '/source.png').toString("base64")
console.log("done\n")
console.log("creating database entry")
Image.create({data: image}).on('success', function(img) {
console.log("done\n")
console.log("writing file")
fs.writeFileSync(__dirname + '/target.png', img.data, "base64")
console.log("done\n")
console.log("you might open the file ./target.png")
This example demonstrates the use of default values for defined model fields. Instead of just specifying the datatype,
you have to pass a hash with a type and a default. You also might want to specify either an attribute can be null or not!
*/
var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
var User = sequelize.define('User', {
name: { type: Sequelize.STRING, allowNull: false},
isAdmin: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false }
})
, user = User.build({ name: 'Someone' })
sequelize.sync({force: true}).on('success', function() {
user.save().on('success', function(user) {
console.log("user.isAdmin should be the default value (false): ", user.isAdmin)
user.updateAttributes({ isAdmin: true }).on('success', function(user) {
console.log("user.isAdmin was overwritten to true: " + user.isAdmin)
})
})
}).on('failure', function(err) {
console.log(err)
})
\ No newline at end of file
/*
Title: Defining class and instance methods
This example shows the usage of the classMethods and instanceMethods option for Models.
*/
var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
// model definition
// model definition
var Task = sequelize.define("Task", {
name: Sequelize.STRING,
deadline: Sequelize.DATE,
......@@ -51,7 +51,7 @@ Task.sync({force: true}).on('success', function() {
console.log("should be false: " + task1.passedDeadline())
console.log("should be true: " + task2.passedDeadline())
console.log("should be 10: " + task1.importance)
Task.setImportance(30, function() {
Task.findAll().on('success', function(tasks) {
tasks.forEach(function(task) {
......
var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {
// use other database server or port
host: 'my.srv.tld',
port: 12345,
// disable logging
logging: false
})
......
var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
, Project = sequelize.import(__dirname + "/Project")
, Task = sequelize.import(__dirname + "/Task")
Project.hasMany(Task)
Task.belongsTo(Project)
sequelize.sync({force: true}).on('success', function() {
Project
.create({ name: 'Sequelize', description: 'A nice MySQL ORM for NodeJS' })
......
......@@ -19,7 +19,15 @@ module.exports = (function() {
whereCollection: null,
schema: null,
schemaDelimiter: ''
}, options || {})
}, options || {})
// error check options
Utils._.each(options.validate, function(validator, validatorType) {
if (Utils._.contains(Utils._.keys(attributes), validatorType))
throw new Error("A model validator function must not have the same name as a field. Model: " + name + ", field/validation name: " + validatorType)
if (!Utils._.isFunction(validator))
throw new Error("Members of the validate option must be functions. Model: " + name + ", error with validate member " + validatorType)
})
this.name = name
if (!this.options.tableName) {
......@@ -30,9 +38,6 @@ module.exports = (function() {
this.rawAttributes = attributes
this.daoFactoryManager = null // defined in init function
this.associations = {}
// extract validation
this.validate = this.options.validate || {}
}
Object.defineProperty(DAOFactory.prototype, 'attributes', {
......@@ -75,11 +80,33 @@ module.exports = (function() {
Util.inherits(this.DAO, DAO);
this.DAO.prototype.rawAttributes = this.rawAttributes;
if (this.options.instanceMethods) {
Utils._.each(this.options.instanceMethods, function(fct, name) {
self.DAO.prototype[name] = fct
})
}
Utils._.each(['Get', 'Set'], function(type) {
var prop = type.toLowerCase(),
opt = prop + 'terMethods',
meth = '__define' + type + 'ter__',
funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {}
;
Utils._.each(self.rawAttributes, function(attr, name) {
if (attr.hasOwnProperty(prop))
funcs[name] = attr[prop]
});
Utils._.each(funcs, function(fct, name) {
if (!Utils._.isFunction(fct))
throw new Error(type + 'ter for "' + name + '" is not a function.')
self.DAO.prototype[meth](name, fct);
})
})
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes);
this.DAO.prototype.booleanValues = [];
......@@ -481,7 +508,9 @@ module.exports = (function() {
}
Utils._.each(defaultAttributes, function(value, attr) {
self.rawAttributes[attr] = value
if (Utils._.isUndefined(self.rawAttributes[attr])) {
self.rawAttributes[attr] = value
}
})
}
......
......@@ -7,28 +7,15 @@ module.exports = (function() {
var DAO = function(values, options, isNewRecord) {
var self = this
this.dataValues = {}
this.__options = options
this.hasPrimaryKeys = options.hasPrimaryKeys
this.selectedValues = values
this.__eagerlyLoadedAssociations = []
initAttributes.call(this, values, isNewRecord)
if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function (value, name) {
if (typeof self[name] === 'undefined') {
self.addAttribute(name, value());
}
})
}
if (this.booleanValues.length) {
this.booleanValues.forEach(function (name) {
//transform integer 0,1 into boolean
self[name] = !!self[name];
});
}
}
Utils._.extend(DAO.prototype, Mixin.prototype)
Object.defineProperty(DAO.prototype, 'sequelize', {
......@@ -42,7 +29,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'isDeleted', {
get: function() {
var result = this.__options.timestamps && this.__options.paranoid
result = result && this[this.__options.underscored ? 'deleted_at' : 'deletedAt'] !== null
result = result && this.dataValues[this.__options.underscored ? 'deleted_at' : 'deletedAt'] !== null
return result
}
......@@ -54,7 +41,10 @@ module.exports = (function() {
, self = this
this.attributes.concat(this.__eagerlyLoadedAssociations).forEach(function(attr) {
result[attr] = self[attr]
result[attr] = self.dataValues.hasOwnProperty(attr)
? self.dataValues[attr]
: self[attr]
;
})
return result
......@@ -67,7 +57,7 @@ module.exports = (function() {
, self = this
Utils._.each(this.__factory.primaryKeys, function(_, attr) {
result[attr] = self[attr]
result[attr] = self.dataValues[attr]
})
return result
......@@ -85,13 +75,21 @@ module.exports = (function() {
}
primaryKeys.forEach(function(identifier) {
result[identifier] = self[identifier]
result[identifier] = self.dataValues[identifier]
})
return result
}
})
DAO.prototype.getDataValue = function(name) {
return this.dataValues && this.dataValues.hasOwnProperty(name) ? this.dataValues[name] : this[name]
}
DAO.prototype.setDataValue = function(name, value) {
this.dataValues[name] = value
}
// if an array with field names is passed to save()
// only those fields will be updated
DAO.prototype.save = function(fields) {
......@@ -111,9 +109,11 @@ module.exports = (function() {
}
}
var tmpVals = self.values
fields.forEach(function(field) {
if (self.values[field] !== undefined) {
values[field] = self.values[field]
if (tmpVals[field] !== undefined) {
values[field] = tmpVals[field]
}
})
}
......@@ -146,8 +146,8 @@ module.exports = (function() {
}
}
if (this.__options.timestamps && this.hasOwnProperty(updatedAtAttr)) {
this[updatedAtAttr] = values[updatedAtAttr] = Utils.now()
if (this.__options.timestamps && this.dataValues.hasOwnProperty(updatedAtAttr)) {
this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now()
}
var errors = this.validate()
......@@ -269,6 +269,15 @@ module.exports = (function() {
} // if field has validator set
}) // for each field
// for each model validator for this DAO
Utils._.each(self.__options.validate, function(validator, validatorType) {
try {
validator.apply(self)
} catch (err) {
failures[validatorType] = [err.message] // TODO: data structure needs to change for 2.0
}
})
return (Utils._.isEmpty(failures) ? null : failures)
}
......@@ -301,7 +310,7 @@ module.exports = (function() {
DAO.prototype.destroy = function() {
if (this.__options.timestamps && this.__options.paranoid) {
var attr = this.__options.underscored ? 'deleted_at' : 'deletedAt'
this[attr] = new Date()
this.dataValues[attr] = new Date()
return this.save()
} else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id
......@@ -362,7 +371,37 @@ module.exports = (function() {
}
DAO.prototype.addAttribute = function(attribute, value) {
this[attribute] = value
if (typeof this.dataValues[attribute] !== 'undefined')
return;
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1) // transform integer 0,1 into boolean
value = !!value;
var has = (function(o) {
var predef = Object.getOwnPropertyDescriptor(o, attribute);
if (predef && predef.hasOwnProperty('value'))
return true; // true here means 'this property exist as a simple value property, do not place setters or getters at all'
return {
get: (predef && predef.hasOwnProperty('get') ? predef.get : null) || o.__lookupGetter__(attribute),
set: (predef && predef.hasOwnProperty('set') ? predef.set : null) || o.__lookupSetter__(attribute)
};
})(this);
// @ node-v0.8.19:
// calling __defineGetter__ destroys any previously defined setters for the attribute in
// question *if* that property setter was defined on the object's prototype (which is what
// we do in dao-factory) ... therefore we need to [re]define both the setter and getter
// here with either the function that already existed OR the default/automatic definition
//
// (the same is true for __defineSetter and 'prototype' getters)
if (has !== true) {
this.__defineGetter__(attribute, has.get || function() { return this.dataValues[attribute]; });
this.__defineSetter__(attribute, has.set || function(v) { this.dataValues[attribute] = v; });
}
this[attribute] = value;
}
DAO.prototype.setValidators = function(attribute, validators) {
......@@ -376,8 +415,13 @@ module.exports = (function() {
// private
var initAttributes = function(values, isNewRecord) {
// set id to null if not passed as value, a newly created dao has no id
var defaults = this.hasPrimaryKeys ? {} : { id: null },
attrs = {},
key;
// add all passed values to the dao and store the attribute names in this.attributes
for (var key in values) {
for (key in values) {
if (values.hasOwnProperty(key)) {
if (typeof values[key] === "string" && !!this.__factory && !!this.__factory.rawAttributes[key] && !!this.__factory.rawAttributes[key].type && !!this.__factory.rawAttributes[key].type.type && this.__factory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
values[key] = this.QueryInterface.QueryGenerator.toHstore(values[key])
......@@ -387,10 +431,6 @@ module.exports = (function() {
}
}
// set id to null if not passed as value
// a newly created dao has no id
var defaults = this.hasPrimaryKeys ? {} : { id: null }
if (this.__options.timestamps && isNewRecord) {
defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = Utils.now()
defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = Utils.now()
......@@ -398,17 +438,38 @@ module.exports = (function() {
if (this.__options.paranoid) {
defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null
}
if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function(valueFn, key) {
if (!defaults.hasOwnProperty(key))
defaults[key] = valueFn()
})
}
}
if (Utils._.size(defaults)) {
for (var attr in defaults) {
var value = defaults[attr]
for (key in defaults) {
attrs[key] = Utils.toDefaultValue(defaults[key])
}
}
if (!this.hasOwnProperty(attr)) {
this.addAttribute(attr, Utils.toDefaultValue(value))
Utils._.each(this.attributes, function(key) {
if (!attrs.hasOwnProperty(key)) {
attrs[key] = undefined
}
})
if (values) {
for (key in values) {
if (values.hasOwnProperty(key)) {
attrs[key] = values[key]
}
}
}
for (key in attrs) {
this.addAttribute(key, attrs[key])
}
}
return DAO
......
......@@ -19,7 +19,9 @@ module.exports = (function() {
this.poolCfg = Utils._.defaults(this.config.pool, {
maxConnections: 10,
minConnections: 0,
maxIdleTime: 1000
maxIdleTime: 1000,
handleDisconnects: false,
validate: validateConnection
});
this.pendingQueries = 0;
this.useReplicaton = !!config.replication;
......@@ -83,6 +85,7 @@ module.exports = (function() {
destroy: function(client) {
disconnect.call(self, client)
},
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime
......@@ -98,6 +101,7 @@ module.exports = (function() {
destroy: function(client) {
disconnect.call(self, client)
},
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime
......@@ -115,6 +119,7 @@ module.exports = (function() {
},
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
validate: self.poolCfg.validate,
idleTimeoutMillis: self.poolCfg.maxIdleTime
})
}
......@@ -247,10 +252,25 @@ module.exports = (function() {
connection.query("SET time_zone = '+0:00'");
// client.setMaxListeners(self.maxConcurrentQueries)
this.isConnecting = false
if (config.pool.handleDisconnects) {
handleDisconnect(this.pool, connection)
}
done(null, connection)
}
var handleDisconnect = function(pool, client) {
client.on('error', function(err) {
if (err.code !== 'PROTOCOL_CONNECTION_LOST') {
throw err
}
pool.destroy(client)
})
}
var validateConnection = function(client) {
return client && client.state != 'disconnected'
}
var enqueue = function(queueItem, options) {
options = options || {}
if (this.activeQueue.length < this.maxConcurrentQueries) {
......@@ -336,4 +356,4 @@ module.exports = (function() {
}
return ConnectorManager
})()
\ No newline at end of file
})()
......@@ -186,7 +186,7 @@ module.exports = (function() {
options.attributes = options.attributes || '*'
if (options.include) {
var optAttributes = [options.table + '.*']
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
......
......@@ -193,7 +193,7 @@ module.exports = (function() {
for (var attributeName in attributes) {
attrString.push(Utils._.template('<%= before %> TO <%= after %>')({
before: QueryGenerator.addQuotes(attrBefore),
after: QueryGenerator.addQuotes(attributeName),
after: QueryGenerator.addQuotes(attributeName)
}))
}
......@@ -233,7 +233,7 @@ module.exports = (function() {
options.attributes = options.attributes || '*'
if (options.include) {
var optAttributes = [options.table + '.*']
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
......@@ -259,7 +259,7 @@ module.exports = (function() {
}
if(options.hasOwnProperty('where')) {
options.where = QueryGenerator.getWhereConditions(options.where)
options.where = QueryGenerator.getWhereConditions(options.where, tableName)
query += " WHERE <%= where %>"
}
......@@ -474,14 +474,16 @@ module.exports = (function() {
})
},
getWhereConditions: function(smth) {
getWhereConditions: function(smth, tableName) {
var result = null
if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth)
result = QueryGenerator.hashToWhereConditions(smth)
}
else if (typeof smth === "number") {
result = '\"id\"' + "=" + QueryGenerator.pgEscape(smth)
smth = Utils.prependTableNameToHash(tableName, { id: smth })
result = QueryGenerator.hashToWhereConditions(smth)
}
else if (typeof smth === "string") {
result = smth
......@@ -724,9 +726,15 @@ module.exports = (function() {
}
if (Utils._.includes(dataType, 'SERIAL')) {
dataType = dataType.replace(/INTEGER/, '')
if (Utils._.includes(dataType, 'BIGINT')) {
dataType = dataType.replace(/SERIAL/, 'BIGSERIAL')
dataType = dataType.replace(/BIGINT/, '')
tables[tableName][attr] = 'bigserial'
} else {
dataType = dataType.replace(/INTEGER/, '')
tables[tableName][attr] = 'serial'
}
dataType = dataType.replace(/NOT NULL/, '')
tables[tableName][attr] = 'serial'
}
if (dataType.match(/^ENUM\(/)) {
......
......@@ -24,8 +24,17 @@ module.exports = (function() {
var QueryGenerator = {
options: {},
addQuotes: function(s, quoteChar) {
return Utils.addTicks(s, quoteChar)
removeQuotes: function (s, quoteChar) {
quoteChar = quoteChar || '`'
return s.replace(new RegExp(quoteChar, 'g'), '')
},
addQuotes: function (s, quoteChar) {
quoteChar = quoteChar || '`'
return QueryGenerator.removeQuotes(s, quoteChar)
.split('.')
.map(function(e) { return quoteChar + String(e) + quoteChar })
.join('.')
},
addSchema: function(opts) {
......@@ -76,6 +85,10 @@ module.exports = (function() {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr]
if (Utils._.includes(dataType, 'AUTOINCREMENT')) {
dataType = dataType.replace(/BIGINT/, 'INTEGER')
}
if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) {
primaryKeys.push(attr)
attrStr.push(Utils.addTicks(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL'))
......@@ -145,6 +158,74 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
selectQuery: function(tableName, options) {
var table = null,
joinQuery = ""
options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(tbl){ return QueryGenerator.addQuotes(tbl) }).join(", ") : QueryGenerator.addQuotes(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr){
if(Array.isArray(attr) && attr.length == 2) {
return [attr[0], QueryGenerator.addQuotes(attr[1])].join(' as ')
} else {
return attr.indexOf(Utils.TICK_CHAR) < 0 ? QueryGenerator.addQuotes(attr) : attr
}
}).join(", ")
options.attributes = options.attributes || '*'
if (options.include) {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
return "`" + include.as + "`.`" + attr + "` AS `" + include.as + "." + attr + "`"
})
optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN `" + table + "` AS `" + as + "` ON `" + tableLeft + "`.`" + attrLeft + "` = `" + tableRight + "`.`" + attrRight + "`"
})
options.attributes = optAttributes.join(', ')
}
var query = "SELECT " + options.attributes + " FROM " + options.table
query += joinQuery
if (options.hasOwnProperty('where')) {
options.where = this.getWhereConditions(options.where, tableName)
query += " WHERE " + options.where
}
if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(grp){return QueryGenerator.addQuotes(grp)}).join(', ') : QueryGenerator.addQuotes(options.group)
query += " GROUP BY " + options.group
}
if (options.order) {
query += " ORDER BY " + options.order
}
if (options.limit && !(options.include && (options.limit === 1))) {
if (options.offset) {
query += " LIMIT " + options.offset + ", " + options.limit
} else {
query += " LIMIT " + options.limit
}
}
query += ";"
return query
},
updateQuery: function(tableName, attrValueHash, where) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
......
var util = require("util")
, EventEmitter = require("events").EventEmitter
, Promise = require("promise")
, proxyEventKeys = ['success', 'error', 'sql']
var bindToProcess = function(fct) {
if (fct) {
if(process.domain) {
return process.domain.bind(fct);
}
}
return fct;
};
module.exports = (function() {
var CustomEventEmitter = function(fct) {
this.fct = fct
this.fct = bindToProcess(fct);
}
util.inherits(CustomEventEmitter, EventEmitter)
......@@ -14,14 +26,14 @@ module.exports = (function() {
this.fct.call(this, this)
}
}.bind(this))
return this
}
CustomEventEmitter.prototype.success =
CustomEventEmitter.prototype.ok =
function(fct) {
this.on('success', fct)
this.on('success', bindToProcess(fct))
return this
}
......@@ -29,13 +41,14 @@ module.exports = (function() {
CustomEventEmitter.prototype.fail =
CustomEventEmitter.prototype.error =
function(fct) {
this.on('error', fct)
this.on('error', bindToProcess(fct))
return this;
}
CustomEventEmitter.prototype.done =
CustomEventEmitter.prototype.complete =
function(fct) {
fct = bindToProcess(fct);
this.on('error', function(err) { fct(err, null) })
.on('success', function(result) { fct(null, result) })
return this
......@@ -49,6 +62,18 @@ module.exports = (function() {
}.bind(this))
}
CustomEventEmitter.prototype.then =
function (onFulfilled, onRejected) {
var self = this
onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) {
self.on('error', reject)
.on('success', resolve);
}).then(onFulfilled, onRejected)
}
return CustomEventEmitter;
})()
......@@ -47,7 +47,8 @@ module.exports = (function() {
var urlParts
options = options || {}
if (arguments.length === 1) {
if (arguments.length === 1 || (arguments.length === 2 && typeof username === 'object')) {
options = username || {}
urlParts = url.parse(arguments[0])
database = urlParts.path.replace(/^\//, '')
dialect = urlParts.protocol
......
......@@ -35,16 +35,17 @@
"validator": "1.1.1",
"moment": "~1.7.0",
"commander": "~0.6.0",
"generic-pool": "1.0.9",
"dottie": "0.0.6-1",
"toposort-class": "0.1.4"
"toposort-class": "0.1.4",
"generic-pool": "2.0.3",
"promise": "~3.0.0"
},
"devDependencies": {
"jasmine-node": "1.5.0",
"sqlite3": "~2.1.5",
"mysql": "~2.0.0-alpha7",
"pg": "~0.10.2",
"buster": "~0.6.0",
"buster": "~0.6.3",
"watchr": "~2.2.0",
"yuidocjs": "~0.3.36"
},
......
......@@ -121,16 +121,16 @@ describe('QueryGenerator', function() {
expectation: "SELECT \"id\", \"name\" FROM \"myTable\";"
}, {
arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"id\"=2;"
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;"
}, {
arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"name\"='foo';"
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo';"
}, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"name\"='foo'';DROP TABLE myTable;';"
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo'';DROP TABLE myTable;';"
}, {
arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM \"myTable\" WHERE \"id\"=2;"
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;"
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as \"count\" FROM \"foo\";'
......@@ -164,7 +164,7 @@ describe('QueryGenerator', function() {
expectation: "SELECT * FROM \"mySchema\".\"myTable\";"
}, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;';"
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"name\"='foo'';DROP TABLE mySchema.myTable;';"
}
],
......
......@@ -70,6 +70,34 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
})
}.bind(this), 'Invalid DAO definition. Only one autoincrement field allowed.')
})
it('throws an error if a custom model-wide validation is not a function', function() {
Helpers.assertException(function() {
this.sequelize.define('Foo', {
field: {
type: Sequelize.INTEGER
}
}, {
validate: {
notFunction: 33
}
})
}.bind(this), 'Members of the validate option must be functions. Model: Foo, error with validate member notFunction')
})
it('throws an error if a custom model-wide validation has the same name as a field', function() {
Helpers.assertException(function() {
this.sequelize.define('Foo', {
field: {
type: Sequelize.INTEGER
}
}, {
validate: {
field: function() {}
}
})
}.bind(this), 'A model validator function must not have the same name as a field. Model: Foo, field/validation name: field')
})
})
describe('build', function() {
......@@ -100,6 +128,82 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
var user = this.User.build({ username: 'John Wayne' })
expect(user.selectedValues).toEqual({ username: 'John Wayne' })
})
it("attaches getter and setter methods from attribute definition", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters1', {
price: {
type: Sequelize.INTEGER,
get : function() {
return 'answer = ' + this.getDataValue('price');
},
set : function(v) {
return this.setDataValue('price', v + 42);
}
}
},{
});
expect(Product.build({price: 42}).price).toEqual('answer = 84');
var p = Product.build({price: 1});
expect(p.price).toEqual('answer = 43');
p.price = 0;
expect(p.price).toEqual('answer = 42'); // ah finally the right answer :-)
})
it("attaches getter and setter methods from options", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters2', {
priceInCents: {
type: Sequelize.INTEGER
}
},{
setterMethods: {
price: function(value) {
this.dataValues.priceInCents = value * 100;
}
},
getterMethods: {
price: function() {
return '$' + (this.getDataValue('priceInCents') / 100);
},
priceInCents: function() {
return this.dataValues.priceInCents;
}
}
});
expect(Product.build({price: 20}).priceInCents).toEqual(20 * 100);
expect(Product.build({priceInCents: 30 * 100}).price).toEqual('$' + 30);
})
it("attaches getter and setter methods from options only if not defined in attribute", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters3', {
price1: {
type: Sequelize.INTEGER,
set : function(v) { this.setDataValue('price1', v * 10); }
},
price2: {
type: Sequelize.INTEGER,
get : function(v) { return this.getDataValue('price2') * 10; }
}
},{
setterMethods: {
price1: function(v) { this.setDataValue('price1', v * 100); }
},
getterMethods: {
price2: function() { return '$' + this.getDataValue('price2'); }
}
});
var p = Product.build({ price1: 1, price2: 2 });
expect(p.price1).toEqual(10);
expect(p.price2).toEqual(20);
})
})
describe('findOrCreate', function () {
......@@ -763,6 +867,37 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
}.bind(this))
})
it('returns the selected fields and all fields of the included table as instance.selectedValues', function(done) {
this.Mission = this.sequelize.define('Mission', {
title: {type: Sequelize.STRING, defaultValue: 'a mission!!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
})
this.Mission.belongsTo(this.User)
this.User.hasMany(this.Mission)
this.sequelize.sync({ force: true }).complete(function() {
this.Mission.create()
.success(function(mission) {
this.User.create({
username: 'John DOE'
}).success(function(user) {
mission.setUser(user)
.success(function() {
this.User.find({
where: { username: 'John DOE' },
attributes: ['username'],
include: [this.Mission]
}).success(function(user) {
expect(user.selectedValues).toEqual({ username: 'John DOE' })
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('always honors ZERO as primary key', function(_done) {
var permutations = [
0,
......@@ -1085,7 +1220,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
done();
}.bind(this))
})
})
it("should return raw data when raw is true", function (done) {
this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, user) {
......@@ -1342,9 +1477,9 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
it("should return a DAO when queryOptions are not set", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}).done(function (err, users) {
users.forEach(function (user) {
expect(user).toHavePrototype(this.User.DAO.prototype)
expect(user).toHavePrototype(this.User.DAO.prototype)
}, this)
done();
}.bind(this))
......@@ -1353,17 +1488,17 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
it("should return a DAO when raw is false", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}, { raw: false }).done(function (err, users) {
users.forEach(function (user) {
expect(user).toHavePrototype(this.User.DAO.prototype)
expect(user).toHavePrototype(this.User.DAO.prototype)
}, this)
done();
}.bind(this))
})
})
it("should return raw data when raw is true", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, users) {
users.forEach(function (user) {
expect(user).not.toHavePrototype(this.User.DAO.prototype)
expect(user).not.toHavePrototype(this.User.DAO.prototype)
expect(users[0]).toBeObject()
}, this)
......@@ -1532,6 +1667,10 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
expect(self.UserSpecialSync.getTableName()).toEqual('"special"."UserSpecials"');
expect(UserSpecial.indexOf('INSERT INTO "special"."UserSpecials"')).toBeGreaterThan(-1)
expect(UserPublic.indexOf('INSERT INTO "UserPublics"')).toBeGreaterThan(-1)
} else if (dialect === "sqlite") {
expect(self.UserSpecialSync.getTableName()).toEqual('`special`.`UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).toBeGreaterThan(-1)
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).toBeGreaterThan(-1)
} else {
expect(self.UserSpecialSync.getTableName()).toEqual('`special.UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).toBeGreaterThan(-1)
......
......@@ -282,5 +282,58 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
var successfulUser = User.build({ name : "2" })
expect(successfulUser.validate()).toBeNull()
})
it('skips other validations if allowNull is true and the value is null', function() {
var User = this.sequelize.define('User' + Math.random(), {
age: {
type: Sequelize.INTEGER,
allowNull: true,
validate: {
min: { args: 0, msg: 'must be positive' }
}
}
})
var failingUser = User.build({ age: -1 })
, errors = failingUser.validate()
expect(errors).not.toBeNull(null)
expect(errors).toEqual({ age: ['must be positive'] })
var successfulUser1 = User.build({ age: null })
expect(successfulUser1.validate()).toBeNull()
var successfulUser2 = User.build({ age: 1 })
expect(successfulUser2.validate()).toBeNull()
})
it('validates a model with custom model-wide validation methods', function() {
var Foo = this.sequelize.define('Foo' + Math.random(), {
field1: {
type: Sequelize.INTEGER,
allowNull: true
},
field2: {
type: Sequelize.INTEGER,
allowNull: true
}
}, {
validate: {
xnor: function() {
if ((this.field1 === null) === (this.field2 === null)) {
throw new Error('xnor failed');
}
}
}
})
var failingFoo = Foo.build({ field1: null, field2: null })
, errors = failingFoo.validate()
expect(errors).not.toBeNull()
expect(errors).toEqual({ 'xnor': ['xnor failed'] })
var successfulFoo = Foo.build({ field1: 33, field2: null })
expect(successfulFoo.validate()).toBeNull()
})
})
})
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
, _ = require('lodash')
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Promise"), function () {
before(function (done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function (sequelize, DataTypes) {
self.sequelize = sequelize
self.User = sequelize.define('User', {
username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1, 20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
})
self.HistoryLog = sequelize.define('HistoryLog', {
someText: { type: DataTypes.STRING },
aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER }
})
self.ParanoidUser = sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, {
paranoid: true
})
self.ParanoidUser.hasOne(self.ParanoidUser)
},
onComplete: function () {
self.User.sync({ force: true }).then(function () {
return self.HistoryLog.sync({ force: true })
}).then(function () {
return self.ParanoidUser.sync({force: true })
})
.then(function () {done()}, done)
}
})
})
describe('increment', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment(['aNumber'], 2)
}).then(function (user2) {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(2);
done();
}, done);
});
it('should still work right with other concurrent updates', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
// Select the user again (simulating a concurrent query)
return self.User.find(1).then(function (user2) {
return user2.updateAttributes({
aNumber: user2.aNumber + 1
}).then(function (user3) {
return user1.increment(['aNumber'], 2)
}).then(function (user4) {
return self.User.find(1)
}).then(function (user5) {
expect(user5.aNumber).toBe(3);
done();
}, done)
});
});
});
it('with key value pair', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment({ 'aNumber': 1, 'bNumber': 2})
}).then(function () {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(1);
expect(user3.bNumber).toBe(2);
done();
}, done);
});
});
describe('decrement', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('with single field', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('should still work right with other concurrent decrements', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
var _done = _.after(3, function () {
self.User.find(1).then(function (user2) {
expect(user2.aNumber).toEqual(-6);
done();
})
});
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
});
});
});
describe('reload', function () {
it("should return a reference to the same DAO instead of creating a new one", function (done) {
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return originalUser.updateAttributes({ username: 'Doe John' }).then(function () {
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser === updatedUser).toBeTrue()
done();
}, done)
})
})
it("should update the values on all references to the DAO", function (done) {
var self = this
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return self.User.find(originalUser.id).then(function (updater) {
return updater.updateAttributes({ username: 'Doe John' })
}).then(function () {
// We used a different reference when calling updateAttributes, so originalUser is now out of sync
expect(originalUser.username).toEqual('John Doe')
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser.username).toEqual('Doe John')
expect(updatedUser.username).toEqual('Doe John')
done();
}, done)
})
})
it("should update the associations as well", function (done) {
var Book = this.sequelize.define('Book', { title: Helpers.Sequelize.STRING })
, Page = this.sequelize.define('Page', { content: Helpers.Sequelize.TEXT })
Book.hasMany(Page)
Page.belongsTo(Book)
this.sequelize.sync({ force: true }).then(function () {
return Book.create({ title: 'A very old book' })
}).then(function (book) {
return Page.create({ content: 'om nom nom' }).then(function (page) {
return book.setPages([ page ]).then(function () {
return Book.find({
where: (dialect === 'postgres' ? '"Books"."id"=' : '`Books`.`id`=') + book.id,
include: [Page]
}).then(function (leBook) {
return page.updateAttributes({ content: 'something totally different' }).then(function (page) {
expect(leBook.pages[0].content).toEqual('om nom nom')
expect(page.content).toEqual('something totally different')
return leBook.reload().then(function (leBook) {
expect(leBook.pages[0].content).toEqual('something totally different')
expect(page.content).toEqual('something totally different')
done()
})
})
})
})
})
}, done)
})
});
describe('complete', function () {
it("gets triggered if an error occurs", function (done) {
this.User.find({ where: "asdasdasd" }).then(null, function (err) {
expect(err).toBeDefined()
expect(err.message).toBeDefined()
done()
})
})
it("gets triggered if everything was ok", function (done) {
this.User.count().then(function (result) {
expect(result).toBeDefined()
done()
})
})
})
describe('save', function () {
it('should fail a validation upon creating', function (done) {
this.User.create({aNumber: 0, validateTest: 'hello'}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1);
done();
});
})
it('should fail a validation upon building', function (done) {
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateCustom).toBeDefined()
expect(err.validateCustom).toBeArray()
expect(err.validateCustom[0]).toBeDefined()
expect(err.validateCustom[0]).toEqual('Length failed.')
done()
})
})
it('should fail a validation when updating', function (done) {
this.User.create({aNumber: 0}).then(function (user) {
return user.updateAttributes({validateTest: 'hello'})
}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeDefined()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer:')).toBeGreaterThan(-1)
done()
})
})
})
})
......@@ -194,5 +194,29 @@ describe(Helpers.getTestDialectTeaser("Sequelize"), function() {
})
})
})
describe('table', function() {
[
{ id: { type: Helpers.Sequelize.BIGINT } },
{ id: { type: Helpers.Sequelize.STRING, allowNull: true } },
{ id: { type: Helpers.Sequelize.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } }
].forEach(function(customAttributes) {
it('should be able to override options on the default attributes', function(done) {
var Picture = this.sequelize.define('picture', Helpers.Sequelize.Utils._.cloneDeep(customAttributes))
Picture.sync({ force: true }).success(function() {
Object.keys(customAttributes).forEach(function(attribute) {
Object.keys(customAttributes[attribute]).forEach(function(option) {
var optionValue = customAttributes[attribute][option];
expect(Picture.rawAttributes[attribute][option]).toBe(optionValue)
});
})
done()
})
})
})
})
})
})
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!