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

Commit c20fa816 by Mick Hansen

Merge pull request #1744 from sequelize/addRelations

addRelations feature
2 parents 7e9679be b67058a6
var Utils = require("./../utils")
, DataTypes = require('./../data-types')
, Helpers = require('./helpers')
, Transaction = require('../transaction')
, _ = require('lodash')
module.exports = (function() {
var BelongsTo = function(source, target, options) {
this.associationType = 'BelongsTo'
......
......@@ -12,76 +12,65 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectGetter = function(options) {
var self = this
, _options = options
, smart
var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = []
, through = self.association.through
, options = _options || {}
, queryOptions = {}
, targetAssociation = self.association.targetAssociation
//fully qualify
var instancePrimaryKey = self.instance.Model.primaryKeyAttribute
, foreignPrimaryKey = self.association.target.primaryKeyAttribute
options.where = new Utils.and([
new Utils.where(
through.rawAttributes[self.association.identifier],
self.instance[instancePrimaryKey]
),
new Utils.where(
through.rawAttributes[self.association.foreignIdentifier],
{
join: new Utils.literal([
self.QueryInterface.quoteTable(self.association.target.name),
self.QueryInterface.quoteIdentifier(foreignPrimaryKey)
].join('.'))
}
),
options.where
])
if (Object(targetAssociation.through) === targetAssociation.through) {
queryOptions.hasJoinTableModel = true
queryOptions.joinTableModel = through
if (!options.attributes) {
options.attributes = [
self.QueryInterface.quoteTable(self.association.target.name)+".*"
]
}
if (options.joinTableAttributes) {
options.joinTableAttributes.forEach(function (elem) {
options.attributes.push(
self.QueryInterface.quoteTable(through.name) + '.' + self.QueryInterface.quoteIdentifier(elem) + ' as ' +
self.QueryInterface.quoteIdentifier(through.name + '.' + elem, true)
)
})
} else {
Utils._.forOwn(through.rawAttributes, function (elem, key) {
options.attributes.push(
self.QueryInterface.quoteTable(through.name) + '.' + self.QueryInterface.quoteIdentifier(key) + ' as ' +
self.QueryInterface.quoteIdentifier(through.name + '.' + key, true)
)
})
, through = self.association.through
, queryOptions = {}
, targetAssociation = self.association.targetAssociation
options = options || {}
//fully qualify
var instancePrimaryKey = self.instance.Model.primaryKeyAttribute
, foreignPrimaryKey = self.association.target.primaryKeyAttribute
options.where = new Utils.and([
new Utils.where(
through.rawAttributes[self.association.identifier],
self.instance[instancePrimaryKey]
),
new Utils.where(
through.rawAttributes[self.association.foreignIdentifier],
{
join: new Utils.literal([
self.QueryInterface.quoteTable(self.association.target.name),
self.QueryInterface.quoteIdentifier(foreignPrimaryKey)
].join('.'))
}
),
options.where
])
if (Object(targetAssociation.through) === targetAssociation.through) {
queryOptions.hasJoinTableModel = true
queryOptions.joinTableModel = through
if (!options.attributes) {
options.attributes = [
self.QueryInterface.quoteTable(self.association.target.name)+".*"
]
}
self.association.target.findAllJoin([through.getTableName(), through.name], options, queryOptions)
.on('success', function(objects) { customEventEmitter.emit('success', objects) })
.on('error', function(err){ customEventEmitter.emit('error', err) })
.on('sql', function(sql) { customEventEmitter.emit('sql', sql)})
})
if (options.joinTableAttributes) {
options.joinTableAttributes.forEach(function (elem) {
options.attributes.push(
self.QueryInterface.quoteTable(through.name) + '.' + self.QueryInterface.quoteIdentifier(elem) + ' as ' +
self.QueryInterface.quoteIdentifier(through.name + '.' + elem, true)
)
})
} else {
Utils._.forOwn(through.rawAttributes, function (elem, key) {
options.attributes.push(
self.QueryInterface.quoteTable(through.name) + '.' + self.QueryInterface.quoteIdentifier(key) + ' as ' +
self.QueryInterface.quoteIdentifier(through.name + '.' + key, true)
)
})
}
}
return customEventEmitter.run()
return self.association.target.findAllJoin([through.getTableName(), through.name], options, queryOptions)
}
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) {
HasManyDoubleLinked.prototype.injectSetter = function(oldAssociations, newAssociations, defaultAttributes) {
var self = this
, chainer = new Utils.QueryChainer()
, targetAssociation = self.association.targetAssociation
, foreignIdentifier = self.association.foreignIdentifier
, sourceKeys = Object.keys(self.association.source.primaryKeys)
......@@ -89,6 +78,7 @@ module.exports = (function() {
, obsoleteAssociations = []
, changedAssociations = []
, options = {}
, promises = []
, unassociatedObjects;
if ((defaultAttributes || {}).transaction instanceof Transaction) {
......@@ -140,7 +130,7 @@ module.exports = (function() {
where[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds
chainer.add(self.association.through.destroy(where, options))
promises.push(self.association.through.destroy(where, options))
}
if (unassociatedObjects.length > 0) {
......@@ -157,23 +147,19 @@ module.exports = (function() {
return attributes
})
chainer.add(self.association.through.bulkCreate(bulk, options))
promises.push(self.association.through.bulkCreate(bulk, options))
}
if (changedAssociations.length > 0) {
changedAssociations.forEach(function (assoc) {
chainer.add(self.association.through.update(assoc.attributes, assoc.where, options))
promises.push(self.association.through.update(assoc.attributes, assoc.where, options))
})
}
chainer
.run()
.success(function() { emitterProxy.emit('success', newAssociations) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
return Utils.Promise.all(promises)
}
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) {
HasManyDoubleLinked.prototype.injectAdder = function(newAssociation, additionalAttributes, exists) {
var attributes = {}
, targetAssociation = this.association.targetAssociation
, foreignIdentifier = targetAssociation.identifier
......@@ -195,17 +181,14 @@ module.exports = (function() {
attributes = Utils._.defaults({}, newAssociation[targetAssociation.through.name], additionalAttributes)
if (Object.keys(attributes).length) {
targetAssociation.through.update(attributes, where).proxy(emitterProxy)
return targetAssociation.through.update(attributes, where)
} else {
emitterProxy.emit('success')
return Utils.Promise.resolve()
}
} else {
attributes = Utils._.defaults(attributes, newAssociation[targetAssociation.through.name], additionalAttributes)
this.association.through.create(attributes, options)
.success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
return this.association.through.create(attributes, options)
}
}
......
......@@ -24,12 +24,12 @@ module.exports = (function() {
return this.association.target.all(options)
}
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations, defaultAttributes) {
HasManySingleLinked.prototype.injectSetter = function(oldAssociations, newAssociations, defaultAttributes) {
var self = this
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {Model: {primaryKeys: {}}}).Model.primaryKeys || {})
, associationKey = (associationKeys.length === 1) ? associationKeys[0] : 'id'
, chainer = new Utils.QueryChainer()
, options = {}
, promises = []
, obsoleteAssociations = oldAssociations.filter(function (old) {
return !Utils._.find(newAssociations, function (obj) {
return obj[associationKey] === old[associationKey]
......@@ -62,7 +62,7 @@ module.exports = (function() {
, updateWhere = {}
updateWhere[primaryKey] = obsoleteIds
chainer.add(this.__factory.target.update(
promises.push(this.__factory.target.update(
update,
updateWhere,
Utils._.extend(options, { allowNull: [self.__factory.identifier] })
......@@ -88,21 +88,17 @@ module.exports = (function() {
update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id)
updateWhere[primaryKey] = unassociatedIds
chainer.add(this.__factory.target.update(
promises.push(this.__factory.target.update(
update,
updateWhere,
Utils._.extend(options, { allowNull: [self.__factory.identifier] })
))
}
chainer
.run()
.success(function() { emitter.emit('success', newAssociations) })
.error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
return Utils.Promise.all(promises)
}
HasManySingleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes) {
HasManySingleLinked.prototype.injectAdder = function(newAssociation, additionalAttributes) {
var primaryKeys = Object.keys(this.instance.Model.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, options = {}
......@@ -114,10 +110,7 @@ module.exports = (function() {
newAssociation[this.__factory.identifier] = this.instance[primaryKey]
newAssociation.save(options)
.success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
return newAssociation.save(options)
}
return HasManySingleLinked
......
var Utils = require("./../utils")
, DataTypes = require('./../data-types')
, Helpers = require('./helpers')
, _ = require('lodash')
, Transaction = require('../transaction')
......@@ -122,6 +121,7 @@ module.exports = (function() {
this.accessors = {
get: Utils._.camelize('get_' + this.as),
set: Utils._.camelize('set_' + this.as),
addMultiple: Utils._.camelize('add_' + this.as, this.target.options.language),
add: Utils._.camelize(Utils.singularize('add_' + this.as, this.target.options.language)),
create: Utils._.camelize(Utils.singularize('create_' + this.as, this.target.options.language)),
remove: Utils._.camelize(Utils.singularize('remove_' + this.as, this.target.options.language)),
......@@ -262,40 +262,26 @@ module.exports = (function() {
obj[this.accessors.hasAll] = function(objects, options) {
var instance = this;
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get](options)
.error(function(err) { customEventEmitter.emit('error', err) })
.success(function(associatedObjects) {
customEventEmitter.emit('success',
Utils._.all(objects, function(o) {
return Utils._.any(associatedObjects, function(associatedObject) {
return Utils._.all(associatedObject.identifiers, function(key, identifier) {
return o[identifier] == associatedObject[identifier];
});
})
})
)
return instance[self.accessors.get](options).then(function(associatedObjects) {
return Utils._.all(objects, function(o) {
return Utils._.any(associatedObjects, function(associatedObject) {
return Utils._.all(associatedObject.identifiers, function(key, identifier) {
return o[identifier] == associatedObject[identifier];
});
})
})
})
return customEventEmitter.run()
}
obj[this.accessors.hasSingle] = function(o, options) {
var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get](options)
.error(function(err){ customEventEmitter.emit('error', err)})
.success(function(associatedObjects) {
customEventEmitter.emit('success',
Utils._.any(associatedObjects, function(associatedObject) {
return Utils._.all(associatedObject.identifiers, function(key, identifier) {
return o[identifier] == associatedObject[identifier];
});
})
)
var instance = this
return instance[self.accessors.get](options).then(function(associatedObjects) {
return Utils._.any(associatedObjects, function(associatedObject) {
return Utils._.all(associatedObject.identifiers, function(key, identifier) {
return o[identifier] == associatedObject[identifier];
});
})
})
return customEventEmitter.run()
}
return this
}
......@@ -303,93 +289,64 @@ module.exports = (function() {
HasMany.prototype.injectSetter = function(obj) {
var self = this
obj[this.accessors.set] = function(newAssociatedObjects, defaultAttributes) {
obj[this.accessors.set] = function (newAssociatedObjects, additionalAttributes) {
if (newAssociatedObjects === null) {
newAssociatedObjects = []
}
var instance = this
// define the returned customEventEmitter, which will emit the success event once everything is done
return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]({
transaction: (defaultAttributes || {}).transaction
})
.success(function(oldAssociatedObjects) {
var Class = Object(self.through) === self.through ? HasManyDoubleLinked : HasManySingleLinked
new Class(self, instance).injectSetter(emitter, oldAssociatedObjects, newAssociatedObjects, defaultAttributes)
})
.proxy(emitter, {events: ['error', 'sql']})
}).run()
return instance[self.accessors.get]({
transaction: (additionalAttributes || {}).transaction
}).then(function(oldAssociatedObjects) {
var Class = Object(self.through) === self.through ? HasManyDoubleLinked : HasManySingleLinked
return new Class(self, instance).injectSetter(oldAssociatedObjects, newAssociatedObjects, additionalAttributes)
})
}
obj[this.accessors.add] = function(newInstance, additionalAttributes) {
obj[this.accessors.add] = function (newInstance, additionalAttributes) {
var instance = this
, primaryKey = newInstance.Model.primaryKeyAttribute
, where = new Utils.where(self.target.rawAttributes[primaryKey], newInstance[primaryKey])
return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]({
if (Array.isArray(newInstance)) {
return obj[self.accessors.addMultiple](newInstance, additionalAttributes)
} else {
return instance[self.accessors.get]({
where: where,
transaction: (additionalAttributes || {}).transaction
}).then(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0 || Object(self.through) === self.through) {
var Class = Object(self.through) === self.through ? HasManyDoubleLinked : HasManySingleLinked
return new Class(self, instance).injectAdder(newInstance, additionalAttributes, !!currentAssociatedObjects.length)
} else {
return Utils.Promise.resolve(newInstance)
}
})
.proxy(emitter, {events: ['error', 'sql']})
.success(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0 || Object(self.through) === self.through) {
var Class = Object(self.through) === self.through ? HasManyDoubleLinked : HasManySingleLinked
new Class(self, instance).injectAdder(emitter, newInstance, additionalAttributes, !!currentAssociatedObjects.length)
} else {
emitter.emit('success', newInstance);
}
})
}).run()
}
}
obj[this.accessors.remove] = function(oldAssociatedObject, options) {
var instance = this
return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]({
transaction: (options || {}).transaction
}).success(function(currentAssociatedObjects) {
var newAssociations = []
, oldAssociations = []
currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) {
newAssociations.push(association)
}
})
var tick = 0
var next = function(err, i) {
if (!!err || i >= oldAssociations.length) {
return run(err)
}
oldAssociations[i].destroy().error(function(err) {
next(err)
})
.success(function() {
tick++
next(null, tick)
})
}
var run = function(err) {
if (!!err) {
return emitter.emit('error', err)
}
obj[this.accessors.addMultiple] = function (newInstances, additionalAttributes) {
var Class = Object(self.through) === self.through ? HasManyDoubleLinked : HasManySingleLinked
return new Class(self, this).injectSetter([], newInstances, additionalAttributes)
}
instance[self.accessors.set](newAssociations).proxy(emitter)
}
if (oldAssociations.length > 0) {
next(null, tick)
} else {
run()
obj[this.accessors.remove] = function (oldAssociatedObject, options) {
var instance = this
return instance[self.accessors.get]({
transaction: (options || {}).transaction
}).then(function(currentAssociatedObjects) {
var newAssociations = []
currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) {
newAssociations.push(association)
}
})
}).run()
return instance[self.accessors.set](newAssociations)
})
}
return this
......@@ -407,16 +364,10 @@ module.exports = (function() {
}
if (Object(self.through) === self.through) {
return new Utils.CustomEventEmitter(function(emitter) {
// Create the related model instance
self.target
.create(values, fieldsOrOptions)
.proxy(emitter, { events: ['error', 'sql'] })
.success(function(newAssociatedObject) {
instance[self.accessors.add](newAssociatedObject, options)
.proxy(emitter)
})
}).run()
// Create the related model instance
return self.target.create(values, fieldsOrOptions).then(function(newAssociatedObject) {
return instance[self.accessors.add](newAssociatedObject, options)
})
} else {
values[self.identifier] = instance.get(self.source.primaryKeyAttribute);
return self.target.create(values, fieldsOrOptions)
......
var Utils = require("./../utils")
, DataTypes = require('./../data-types')
, Helpers = require("./helpers")
, Transaction = require("../transaction")
......@@ -118,15 +117,9 @@ module.exports = (function() {
options.transaction = fieldsOrOptions.transaction
}
return new Utils.CustomEventEmitter(function(emitter) {
association.target
.create(values, fieldsOrOptions)
.proxy(emitter, { events: ['error', 'sql'] })
.success(function(associationInstance) {
instance[association.accessors.set](associationInstance, options)
.proxy(emitter)
})
}).run()
return association.target.create(values, fieldsOrOptions).then(function(associationInstance) {
return instance[association.accessors.set](associationInstance, options)
})
}
return this
......
......@@ -69,11 +69,11 @@ var Mixin = module.exports = function(){}
* * set[AS] - for example setProfile(instance, options). Options are passed to `target.save`
* * create[AS] - for example createProfile(value, options). Builds and saves a new instance of the associated model. Values and options are passed on to `target.create`
*
* All methods return an event emitter.
* All methods return a promise
*
* @param {Model} target
* @param {object} [options]
* @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
* @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
* @param {string} [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
* @param {string} [options.foreignKey] The name of the foreign key in the target table. Defaults to the name of source + primary key of source
* @param {string} [options.onDelete='SET NULL']
......@@ -110,11 +110,11 @@ Mixin.hasOne = function(targetModel, options) {
* * set[AS] - for example setUser(instance, options). Options are passed to this.save
* * create[AS] - for example createUser(value, options). Builds and saves a new instance of the associated model. Values and options are passed on to target.create
*
* All methods return an event emitter.
* All methods return a promise
*
* @param {Model} target
* @param {object} [options]
* @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
* @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
* @param {string} [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
* @param {string} [options.foreignKey] The name of the foreign key in the source table. Defaults to the name of target + primary key of target
* @param {string} [options.onDelete='SET&nbsp;NULL']
......@@ -158,13 +158,14 @@ Mixin.belongsTo = function(targetModel, options) {
*
* * get[AS] - for example getPictures().
* * set[AS] - for example setPictures(instances, defaultAttributes|options). Update the associations. All currently associated models that are not in instances will be removed.
* * add[AS] - for example addPicture(instance, defaultAttributes|options). Add another association.
* * add[AS] - for example addPicture(instance, defaultAttributes|options). Add another associated object.
* * add[AS] [plural] - for example addPictures([instance1, instance2], defaultAttributes|options). Add some more associated objects.
* * create[AS] - for example createPicture(values, options). Build and save a new association.
* * remove[AS] - for example removePicture(instance). Remove a single association
* * has[AS] - for example hasPicture(instance). Is source associated to this target?
* * has[AS] [plural] - for example hasPictures(instances). Is source associated to all these targets?
*
* All methods return an event emitter.
* All methods return a promise
*
* If you use a through model with custom attributes, these attributes can be set when adding / setting new associations in two ways. Consider users and projects from before
* with a join table that stores whether the project has been started yet:
......@@ -200,7 +201,7 @@ Mixin.belongsTo = function(targetModel, options) {
* @param {Model} target
* @param {object} [options]
* @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
* @param {Model|string} [options.through] The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it.
* @param {Model|string} [options.through] The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it.
* @param {string} [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
* @param {string} [options.foreignKey] The name of the foreign key in the source table. Defaults to the name of target + primary key of target
* @param {string} [options.onDelete='SET&nbsp;NULL|CASCADE'] Cascade if this is a n:m, and set null if it is a 1:m
......
......@@ -92,7 +92,9 @@ Hooks.runHooks = function() {
if (!!arguments[0]) {
return reject(arguments[0])
}
resolveArgs = Array.prototype.slice.call(arguments, 1)
if (arguments.length) {
resolveArgs = Array.prototype.slice.call(arguments, 1)
}
return run(hooks[tick])
}))
......@@ -101,7 +103,9 @@ Hooks.runHooks = function() {
maybePromise.spread(function () {
tick++
resolveArgs = Array.prototype.slice.call(arguments)
if (arguments.length) {
resolveArgs = Array.prototype.slice.call(arguments)
}
return run(hooks[tick])
}, reject)
......
var util = require("util")
, Promise
var Promise
, EventEmitter = require("events").EventEmitter
, proxyEventKeys = ['success', 'error', 'sql']
, Utils = require('./utils')
......@@ -12,7 +11,7 @@ var util = require("util")
* @mixes https://github.com/petkaantonov/bluebird/blob/master/API.md
* @class Promise
*/
var SequelizePromise = Promise = require('bluebird')
var SequelizePromise = Promise = require('sequelize-bluebird')
/**
* Listen for events, event emitter style. Mostly for backwards compat. with EventEmitter
......
......@@ -51,7 +51,7 @@
"generic-pool": "2.0.4",
"sql": "~0.35.0",
"circular-json": "~0.1.5",
"bluebird": "git://github.com/sequelize/bluebird.git",
"sequelize-bluebird": "git://github.com/sequelize/bluebird.git",
"node-uuid": "~1.4.1"
},
"devDependencies": {
......
......@@ -293,6 +293,35 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
describe('addMultipleAssociations', function () {
it('adds associations without removing the current ones', function () {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
, Task = this.sequelize.define('Task', { title: DataTypes.STRING })
Task.hasMany(User)
return this.sequelize.sync({ force: true }).then(function() {
return User.bulkCreate([
{ username: 'foo '},
{ username: 'bar '},
{ username: 'baz '}
]).then(function () {
return Task.create({ title: 'task' }).then(function (task) {
return User.findAll().then(function(users) {
return task.setUsers([users[0]]).then(function () {
return task.addUsers([users[1], users[2]]).then(function () {
return task.getUsers().then(function (users) {
expect(users).to.have.length(3)
})
})
})
})
})
})
})
})
})
it("clears associations when passing null to the set-method with omitNull set to true", function(done) {
this.sequelize.options.omitNull = true
......@@ -923,6 +952,36 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
describe('addMultipleAssociations', function () {
it('adds associations without removing the current ones', function () {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
, Task = this.sequelize.define('Task', { title: DataTypes.STRING })
User.hasMany(Task)
Task.hasMany(User)
return this.sequelize.sync({ force: true }).then(function() {
return User.bulkCreate([
{ username: 'foo '},
{ username: 'bar '},
{ username: 'baz '}
]).then(function () {
return Task.create({ title: 'task' }).then(function (task) {
return User.findAll().then(function(users) {
return task.setUsers([users[0]]).then(function () {
return task.addUsers([users[1], users[2]]).then(function () {
return task.getUsers().then(function (users) {
expect(users).to.have.length(3)
})
})
})
})
})
})
})
})
})
describe('optimizations using bulk create, destroy and update', function () {
beforeEach(function (done) {
this.User = this.sequelize.define('User', { username: DataTypes.STRING }, {timestamps: false})
......
......@@ -3,7 +3,6 @@ var chai = require('chai')
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, SequelizePromise = require(__dirname + "/../lib/promise")
, Promise = require('bluebird')
, dialect = Support.getTestDialect()
, _ = require('lodash')
, sinon = require('sinon')
......@@ -389,7 +388,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
it('should still work with .done() when resolving multiple results', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve(Promise.all(['MyModel', true]));
resolve(SequelizePromise.all(['MyModel', true]));
});
promise.spread(spy);
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!