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

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)
......
......@@ -636,7 +636,7 @@ module.exports = (function() {
* @param {Object} [options] A hash of options to describe the scope of the search
* @param {Object} [options.where] A hash of attributes to describe your search. See above for examples.
* @param {Array<String>} [options.attributes] A list of the attributes that you want to select
* @param {Array<Object|Model>} [options.include] A list of associations to eagerly load. Supported is either { include: [ Model1, Model2, ...] } or { include: [ { model: Model1, as: 'Alias' } ] }. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y). When using the object form, you can also specify `attributes` to specify what columns to load, `where` to limit the relations, and `include` to load further nested relations
* @param {Array<Object|Model>} [options.include] A list of associations to eagerly load. Supported is either { include: [ Model1, Model2, ...] } or { include: [ { model: Model1, as: 'Alias' } ] }. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y). When using the object form, you can also specify `attributes` to specify what columns to load, `where` to limit the relations, and `include` to load further nested relations
* @param {String|Array|Sequelize.fn} [options.order] Specifies an ordering. If a string is provided, it will be esacped. Using an array, you can provide several columns / functions to order by. Each element can be further wrapped in a two-element array. The first element is the column / function to order by, the second is the direction. For example: `order: [['name', 'DESC']]`. In this way the column will be escaped, but the direction will not.
* @param {Number} [options.limit]
* @param {Number} [options.offset]
......@@ -1266,106 +1266,65 @@ module.exports = (function() {
, query = null
, args = []
return new Utils.CustomEventEmitter(function(emitter) {
self.runHooks(self.options.hooks.beforeBulkDestroy, where, function(err, newWhere) {
if (!!err) {
return emitter.emit('error', err)
}
where = newWhere || where
return self.runHooks(self.options.hooks.beforeBulkDestroy, where).then(function(newWhere) {
where = newWhere || where
if (self._timestampAttributes.deletedAt && options.force === false) {
var attrValueHash = {}
attrValueHash[self._timestampAttributes.deletedAt] = Utils.now()
query = 'bulkUpdate'
args = [self.getTableName(), attrValueHash, where, self]
} else {
query = 'bulkDelete'
args = [self.getTableName(), where, options, self]
}
var runQuery = function(err, records) {
if (!!err) {
return emitter.emit('error', err)
}
if (self._timestampAttributes.deletedAt && options.force === false) {
var attrValueHash = {}
attrValueHash[self._timestampAttributes.deletedAt] = Utils.now()
query = 'bulkUpdate'
args = [self.getTableName(), attrValueHash, where, self]
} else {
query = 'bulkDelete'
args = [self.getTableName(), where, options, self]
}
query = self.QueryInterface[query].apply(self.QueryInterface, args)
query.on('sql', function(sql) {
emitter.emit('sql', sql)
})
.error(function(err) {
emitter.emit('error', err)
})
.success(function(results) {
var finished = function(err) {
if (!!err) {
return emitter.emit('error', err)
}
var runQuery = function(records) {
return self.QueryInterface[query].apply(self.QueryInterface, args).then(function(results) {
if (options && options.hooks === true) {
var tick = 0
var next = function(i) {
return self.runHooks(self.options.hooks.afterDestroy, records[i]).then(function(newValues) {
records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++
self.runHooks(self.options.hooks.afterBulkDestroy, where, function(err) {
if (!!err) {
return emitter.emit('error', err)
if (tick >= records.length) {
return self.runHooks(self.options.hooks.afterBulkDestroy, where).return(results)
}
emitter.emit('success', results)
return next(tick)
})
}
if (options && options.hooks === true) {
var tick = 0
var next = function(i) {
self.runHooks(self.options.hooks.afterDestroy, records[i], function(err, newValues) {
if (!!err) {
return finished(err)
}
records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++
return next(tick)
} else {
return self.runHooks(self.options.hooks.afterBulkDestroy, where).return(results)
}
})
}
if (tick >= records.length) {
return finished()
}
if (options && options.hooks === true) {
var tick = 0
return self.all({where: where}).then(function(records) {
var next = function(i) {
return self.runHooks(self.options.hooks.beforeDestroy, records[i]).then(function(newValues) {
records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++
next(tick)
})
if (tick >= records.length) {
return runQuery(records)
}
next(tick)
} else {
finished()
}
})
}
if (options && options.hooks === true) {
var tick = 0
self.all({where: where}).error(function(err) { emitter.emit('error', err) })
.success(function(records) {
var next = function(i) {
self.runHooks(self.options.hooks.beforeDestroy, records[i], function(err, newValues) {
if (!!err) {
return runQuery(err)
}
records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++
if (tick >= records.length) {
return runQuery(null, records)
}
next(tick)
})
}
return next(tick)
})
}
next(tick)
})
//
} else {
runQuery()
}
})
}).run()
return next(tick)
})
} else {
return runQuery()
}
})
}
/**
......@@ -1383,7 +1342,6 @@ module.exports = (function() {
*/
Model.prototype.update = function(attrValueHash, where, options) {
var self = this
, query = null
, tick = 0
options = options || {}
......@@ -1395,121 +1353,79 @@ module.exports = (function() {
attrValueHash[self._timestampAttributes.updatedAt] = Utils.now()
}
return new Utils.CustomEventEmitter(function(emitter) {
var runSave = function() {
self.runHooks(self.options.hooks.beforeBulkUpdate, attrValueHash, where, function(err, attributes, _where) {
if (!!err) {
return emitter.emit('error', err)
}
where = _where || where
attrValueHash = attributes || attrValueHash
var runQuery = function(err, records) {
if (!!err) {
return emitter.emit('error', err)
}
var runSave = function() {
return self.runHooks(self.options.hooks.beforeBulkUpdate, attrValueHash, where).spread(function(attributes, _where) {
where = _where || where
attrValueHash = attributes || attrValueHash
query = self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHash, where, options, self.rawAttributes)
query.on('sql', function(sql) {
emitter.emit('sql', sql)
})
.error(function(err) {
emitter.emit('error', err)
})
.success(function(results) {
var finished = function(err, records) {
if (!!err) {
return emitter.emit('error', err)
}
var runQuery = function(records) {
return self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHash, where, options, self.rawAttributes).then(function(results) {
if (options && options.hooks === true && !!records && records.length > 0) {
var tick = 0
var next = function(i) {
return self.runHooks(self.options.hooks.afterUpdate, records[i]).then(function(newValues) {
records[i].dataValues = (!!newValues && newValues.dataValues) ? newValues.dataValues : records[i].dataValues
tick++
self.runHooks(self.options.hooks.afterBulkUpdate, attrValueHash, where, function(err) {
if (!!err) {
return emitter.emit('error', err)
if (tick >= records.length) {
return self.runHooks(self.options.hooks.afterBulkUpdate, attrValueHash, where).return(records)
}
emitter.emit('success', records)
return next(tick)
})
}
if (options && options.hooks === true && !!records && records.length > 0) {
var tick = 0
var next = function(i) {
self.runHooks(self.options.hooks.afterUpdate, records[i], function(err, newValues) {
if (!!err) {
return finished(err)
}
return next(tick)
} else {
return self.runHooks(self.options.hooks.afterBulkUpdate, attrValueHash, where).return(results)
}
})
}
records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++
if (options.hooks === true) {
return self.all({where: where}).then(function(records) {
if (records === null || records.length < 1) {
return runQuery()
}
if (tick >= records.length) {
return finished(null, records)
}
var next = function(i) {
return self.runHooks(self.options.hooks.beforeUpdate, records[i]).then(function(newValues) {
records[i].dataValues = (!!newValues && newValues.dataValues) ? newValues.dataValues : records[i].dataValues
tick++
next(tick)
})
if (tick >= records.length) {
return runQuery(records)
}
next(tick)
} else {
finished(null, results)
}
})
}
if (options.hooks === true) {
self.all({where: where}).error(function(err) { emitter.emit('error', err) })
.success(function(records) {
if (records === null || records.length < 1) {
return runQuery(null)
}
var next = function(i) {
self.runHooks(self.options.hooks.beforeUpdate, records[i], function(err, newValues) {
if (!!err) {
return runQuery(err)
}
records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues
tick++
if (tick >= records.length) {
return runQuery(null, records)
}
next(tick)
})
}
return next(tick)
})
}
next(tick)
})
} else {
runQuery()
}
})
}
return next(tick)
})
} else {
return runQuery()
}
})
}
if (options.validate === true) {
var build = self.build(attrValueHash)
if (options.validate === true) {
var build = self.build(attrValueHash)
// We want to skip validations for all other fields
var updatedFields = Object.keys(attrValueHash)
var skippedFields = Utils._.difference(Object.keys(self.attributes), updatedFields)
// We want to skip validations for all other fields
var updatedFields = Object.keys(attrValueHash)
var skippedFields = Utils._.difference(Object.keys(self.attributes), updatedFields)
build.hookValidate({skip: skippedFields}).error(function(err) {
emitter.emit('error', err)
}).success(function(attributes) {
if (!!attributes && !!attributes.dataValues) {
attrValueHash = Utils._.pick.apply(Utils._, [].concat(attributes.dataValues).concat(Object.keys(attrValueHash)))
}
return build.hookValidate({skip: skippedFields}).then(function(attributes) {
if (!!attributes && !!attributes.dataValues) {
attrValueHash = Utils._.pick.apply(Utils._, [].concat(attributes.dataValues).concat(Object.keys(attrValueHash)))
}
runSave()
})
} else {
runSave()
}
}).run()
return runSave()
})
} else {
return runSave()
}
}
/**
......
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!