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

Commit 1bd525d2 by Jan Aagaard Meier

Refactored associations to use promises, and added addRelations

1 parent 9393986b
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types')
, Helpers = require('./helpers') , Helpers = require('./helpers')
, Transaction = require('../transaction') , Transaction = require('../transaction')
, _ = require('lodash')
module.exports = (function() { module.exports = (function() {
var BelongsTo = function(source, target, options) { var BelongsTo = function(source, target, options) {
this.associationType = 'BelongsTo' this.associationType = 'BelongsTo'
......
...@@ -12,76 +12,65 @@ module.exports = (function() { ...@@ -12,76 +12,65 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectGetter = function(options) { HasManyDoubleLinked.prototype.injectGetter = function(options) {
var self = this var self = this
, _options = options , through = self.association.through
, smart , queryOptions = {}
, targetAssociation = self.association.targetAssociation
var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = [] options = options || {}
, through = self.association.through
, options = _options || {} //fully qualify
, queryOptions = {} var instancePrimaryKey = self.instance.Model.primaryKeyAttribute
, targetAssociation = self.association.targetAssociation , foreignPrimaryKey = self.association.target.primaryKeyAttribute
//fully qualify options.where = new Utils.and([
var instancePrimaryKey = self.instance.Model.primaryKeyAttribute new Utils.where(
, foreignPrimaryKey = self.association.target.primaryKeyAttribute through.rawAttributes[self.association.identifier],
self.instance[instancePrimaryKey]
options.where = new Utils.and([ ),
new Utils.where( new Utils.where(
through.rawAttributes[self.association.identifier], through.rawAttributes[self.association.foreignIdentifier],
self.instance[instancePrimaryKey] {
), join: new Utils.literal([
new Utils.where( self.QueryInterface.quoteTable(self.association.target.name),
through.rawAttributes[self.association.foreignIdentifier], self.QueryInterface.quoteIdentifier(foreignPrimaryKey)
{ ].join('.'))
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)
)
})
} }
),
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) if (options.joinTableAttributes) {
.on('success', function(objects) { customEventEmitter.emit('success', objects) }) options.joinTableAttributes.forEach(function (elem) {
.on('error', function(err){ customEventEmitter.emit('error', err) }) options.attributes.push(
.on('sql', function(sql) { customEventEmitter.emit('sql', sql)}) 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 var self = this
, chainer = new Utils.QueryChainer()
, targetAssociation = self.association.targetAssociation , targetAssociation = self.association.targetAssociation
, foreignIdentifier = self.association.foreignIdentifier , foreignIdentifier = self.association.foreignIdentifier
, sourceKeys = Object.keys(self.association.source.primaryKeys) , sourceKeys = Object.keys(self.association.source.primaryKeys)
...@@ -89,6 +78,7 @@ module.exports = (function() { ...@@ -89,6 +78,7 @@ module.exports = (function() {
, obsoleteAssociations = [] , obsoleteAssociations = []
, changedAssociations = [] , changedAssociations = []
, options = {} , options = {}
, promises = []
, unassociatedObjects; , unassociatedObjects;
if ((defaultAttributes || {}).transaction instanceof Transaction) { if ((defaultAttributes || {}).transaction instanceof Transaction) {
...@@ -140,7 +130,7 @@ module.exports = (function() { ...@@ -140,7 +130,7 @@ module.exports = (function() {
where[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) where[self.association.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds where[foreignIdentifier] = foreignIds
chainer.add(self.association.through.destroy(where, options)) promises.push(self.association.through.destroy(where, options))
} }
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
...@@ -157,23 +147,19 @@ module.exports = (function() { ...@@ -157,23 +147,19 @@ module.exports = (function() {
return attributes return attributes
}) })
chainer.add(self.association.through.bulkCreate(bulk, options)) promises.push(self.association.through.bulkCreate(bulk, options))
} }
if (changedAssociations.length > 0) { if (changedAssociations.length > 0) {
changedAssociations.forEach(function (assoc) { 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 return Utils.Promise.all(promises)
.run()
.success(function() { emitterProxy.emit('success', newAssociations) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
} }
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) { HasManyDoubleLinked.prototype.injectAdder = function(newAssociation, additionalAttributes, exists) {
var attributes = {} var attributes = {}
, targetAssociation = this.association.targetAssociation , targetAssociation = this.association.targetAssociation
, foreignIdentifier = targetAssociation.identifier , foreignIdentifier = targetAssociation.identifier
...@@ -195,17 +181,14 @@ module.exports = (function() { ...@@ -195,17 +181,14 @@ module.exports = (function() {
attributes = Utils._.defaults({}, newAssociation[targetAssociation.through.name], additionalAttributes) attributes = Utils._.defaults({}, newAssociation[targetAssociation.through.name], additionalAttributes)
if (Object.keys(attributes).length) { if (Object.keys(attributes).length) {
targetAssociation.through.update(attributes, where).proxy(emitterProxy) return targetAssociation.through.update(attributes, where)
} else { } else {
emitterProxy.emit('success') return Utils.Promise.resolve()
} }
} else { } else {
attributes = Utils._.defaults(attributes, newAssociation[targetAssociation.through.name], additionalAttributes) attributes = Utils._.defaults(attributes, newAssociation[targetAssociation.through.name], additionalAttributes)
this.association.through.create(attributes, options) return 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) })
} }
} }
......
...@@ -24,12 +24,12 @@ module.exports = (function() { ...@@ -24,12 +24,12 @@ module.exports = (function() {
return this.association.target.all(options) return this.association.target.all(options)
} }
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations, defaultAttributes) { HasManySingleLinked.prototype.injectSetter = function(oldAssociations, newAssociations, defaultAttributes) {
var self = this var self = this
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {Model: {primaryKeys: {}}}).Model.primaryKeys || {}) , associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {Model: {primaryKeys: {}}}).Model.primaryKeys || {})
, associationKey = (associationKeys.length === 1) ? associationKeys[0] : 'id' , associationKey = (associationKeys.length === 1) ? associationKeys[0] : 'id'
, chainer = new Utils.QueryChainer()
, options = {} , options = {}
, promises = []
, obsoleteAssociations = oldAssociations.filter(function (old) { , obsoleteAssociations = oldAssociations.filter(function (old) {
return !Utils._.find(newAssociations, function (obj) { return !Utils._.find(newAssociations, function (obj) {
return obj[associationKey] === old[associationKey] return obj[associationKey] === old[associationKey]
...@@ -62,7 +62,7 @@ module.exports = (function() { ...@@ -62,7 +62,7 @@ module.exports = (function() {
, updateWhere = {} , updateWhere = {}
updateWhere[primaryKey] = obsoleteIds updateWhere[primaryKey] = obsoleteIds
chainer.add(this.__factory.target.update( promises.push(this.__factory.target.update(
update, update,
updateWhere, updateWhere,
Utils._.extend(options, { allowNull: [self.__factory.identifier] }) Utils._.extend(options, { allowNull: [self.__factory.identifier] })
...@@ -88,21 +88,17 @@ module.exports = (function() { ...@@ -88,21 +88,17 @@ module.exports = (function() {
update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id) update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id)
updateWhere[primaryKey] = unassociatedIds updateWhere[primaryKey] = unassociatedIds
chainer.add(this.__factory.target.update( promises.push(this.__factory.target.update(
update, update,
updateWhere, updateWhere,
Utils._.extend(options, { allowNull: [self.__factory.identifier] }) Utils._.extend(options, { allowNull: [self.__factory.identifier] })
)) ))
} }
chainer return Utils.Promise.all(promises)
.run()
.success(function() { emitter.emit('success', newAssociations) })
.error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
} }
HasManySingleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes) { HasManySingleLinked.prototype.injectAdder = function(newAssociation, additionalAttributes) {
var primaryKeys = Object.keys(this.instance.Model.primaryKeys) var primaryKeys = Object.keys(this.instance.Model.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id' , primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, options = {} , options = {}
...@@ -114,10 +110,7 @@ module.exports = (function() { ...@@ -114,10 +110,7 @@ module.exports = (function() {
newAssociation[this.__factory.identifier] = this.instance[primaryKey] newAssociation[this.__factory.identifier] = this.instance[primaryKey]
newAssociation.save(options) return 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 HasManySingleLinked return HasManySingleLinked
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types')
, Helpers = require('./helpers') , Helpers = require('./helpers')
, _ = require('lodash') , _ = require('lodash')
, Transaction = require('../transaction') , Transaction = require('../transaction')
...@@ -122,6 +121,7 @@ module.exports = (function() { ...@@ -122,6 +121,7 @@ module.exports = (function() {
this.accessors = { this.accessors = {
get: Utils._.camelize('get_' + this.as), get: Utils._.camelize('get_' + this.as),
set: Utils._.camelize('set_' + 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)), add: Utils._.camelize(Utils.singularize('add_' + this.as, this.target.options.language)),
create: Utils._.camelize(Utils.singularize('create_' + 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)), remove: Utils._.camelize(Utils.singularize('remove_' + this.as, this.target.options.language)),
...@@ -262,40 +262,26 @@ module.exports = (function() { ...@@ -262,40 +262,26 @@ module.exports = (function() {
obj[this.accessors.hasAll] = function(objects, options) { obj[this.accessors.hasAll] = function(objects, options) {
var instance = this; var instance = this;
var customEventEmitter = new Utils.CustomEventEmitter(function() { return instance[self.accessors.get](options).then(function(associatedObjects) {
instance[self.accessors.get](options) return Utils._.all(objects, function(o) {
.error(function(err) { customEventEmitter.emit('error', err) }) return Utils._.any(associatedObjects, function(associatedObject) {
.success(function(associatedObjects) { return Utils._.all(associatedObject.identifiers, function(key, identifier) {
customEventEmitter.emit('success', return o[identifier] == associatedObject[identifier];
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) { obj[this.accessors.hasSingle] = function(o, options) {
var instance = this var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() { return instance[self.accessors.get](options).then(function(associatedObjects) {
instance[self.accessors.get](options) return Utils._.any(associatedObjects, function(associatedObject) {
.error(function(err){ customEventEmitter.emit('error', err)}) return Utils._.all(associatedObject.identifiers, function(key, identifier) {
.success(function(associatedObjects) { return o[identifier] == associatedObject[identifier];
customEventEmitter.emit('success', });
Utils._.any(associatedObjects, function(associatedObject) {
return Utils._.all(associatedObject.identifiers, function(key, identifier) {
return o[identifier] == associatedObject[identifier];
});
})
)
}) })
}) })
return customEventEmitter.run()
} }
return this return this
} }
...@@ -303,93 +289,64 @@ module.exports = (function() { ...@@ -303,93 +289,64 @@ module.exports = (function() {
HasMany.prototype.injectSetter = function(obj) { HasMany.prototype.injectSetter = function(obj) {
var self = this var self = this
obj[this.accessors.set] = function(newAssociatedObjects, defaultAttributes) { obj[this.accessors.set] = function (newAssociatedObjects, additionalAttributes) {
if (newAssociatedObjects === null) { if (newAssociatedObjects === null) {
newAssociatedObjects = [] newAssociatedObjects = []
} }
var instance = this var instance = this
// define the returned customEventEmitter, which will emit the success event once everything is done return instance[self.accessors.get]({
return new Utils.CustomEventEmitter(function(emitter) { transaction: (additionalAttributes || {}).transaction
instance[self.accessors.get]({ }).then(function(oldAssociatedObjects) {
transaction: (defaultAttributes || {}).transaction var Class = Object(self.through) === self.through ? HasManyDoubleLinked : HasManySingleLinked
}) return new Class(self, instance).injectSetter(oldAssociatedObjects, newAssociatedObjects, additionalAttributes)
.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()
} }
obj[this.accessors.add] = function(newInstance, additionalAttributes) { obj[this.accessors.add] = function (newInstance, additionalAttributes) {
var instance = this var instance = this
, primaryKey = newInstance.Model.primaryKeyAttribute , primaryKey = newInstance.Model.primaryKeyAttribute
, where = new Utils.where(self.target.rawAttributes[primaryKey], newInstance[primaryKey]) , where = new Utils.where(self.target.rawAttributes[primaryKey], newInstance[primaryKey])
return new Utils.CustomEventEmitter(function(emitter) { if (Array.isArray(newInstance)) {
instance[self.accessors.get]({ return obj[self.accessors.addMultiple](newInstance, additionalAttributes)
} else {
return instance[self.accessors.get]({
where: where, where: where,
transaction: (additionalAttributes || {}).transaction 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) { obj[this.accessors.addMultiple] = function (newInstances, additionalAttributes) {
var instance = this var Class = Object(self.through) === self.through ? HasManyDoubleLinked : HasManySingleLinked
return new Utils.CustomEventEmitter(function(emitter) { return new Class(self, this).injectSetter([], newInstances, additionalAttributes)
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)
}
instance[self.accessors.set](newAssociations).proxy(emitter)
}
if (oldAssociations.length > 0) { obj[this.accessors.remove] = function (oldAssociatedObject, options) {
next(null, tick) var instance = this
} else { return instance[self.accessors.get]({
run() 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 return this
...@@ -407,16 +364,10 @@ module.exports = (function() { ...@@ -407,16 +364,10 @@ module.exports = (function() {
} }
if (Object(self.through) === self.through) { if (Object(self.through) === self.through) {
return new Utils.CustomEventEmitter(function(emitter) { // Create the related model instance
// Create the related model instance return self.target.create(values, fieldsOrOptions).then(function(newAssociatedObject) {
self.target return instance[self.accessors.add](newAssociatedObject, options)
.create(values, fieldsOrOptions) })
.proxy(emitter, { events: ['error', 'sql'] })
.success(function(newAssociatedObject) {
instance[self.accessors.add](newAssociatedObject, options)
.proxy(emitter)
})
}).run()
} else { } else {
values[self.identifier] = instance.get(self.source.primaryKeyAttribute); values[self.identifier] = instance.get(self.source.primaryKeyAttribute);
return self.target.create(values, fieldsOrOptions) return self.target.create(values, fieldsOrOptions)
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types')
, Helpers = require("./helpers") , Helpers = require("./helpers")
, Transaction = require("../transaction") , Transaction = require("../transaction")
...@@ -118,15 +117,9 @@ module.exports = (function() { ...@@ -118,15 +117,9 @@ module.exports = (function() {
options.transaction = fieldsOrOptions.transaction options.transaction = fieldsOrOptions.transaction
} }
return new Utils.CustomEventEmitter(function(emitter) { return association.target.create(values, fieldsOrOptions).then(function(associationInstance) {
association.target return instance[association.accessors.set](associationInstance, options)
.create(values, fieldsOrOptions) })
.proxy(emitter, { events: ['error', 'sql'] })
.success(function(associationInstance) {
instance[association.accessors.set](associationInstance, options)
.proxy(emitter)
})
}).run()
} }
return this return this
......
...@@ -69,11 +69,11 @@ var Mixin = module.exports = function(){} ...@@ -69,11 +69,11 @@ var Mixin = module.exports = function(){}
* * set[AS] - for example setProfile(instance, options). Options are passed to `target.save` * * 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` * * 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 {Model} target
* @param {object} [options] * @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.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.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'] * @param {string} [options.onDelete='SET NULL']
...@@ -110,11 +110,11 @@ Mixin.hasOne = function(targetModel, options) { ...@@ -110,11 +110,11 @@ Mixin.hasOne = function(targetModel, options) {
* * set[AS] - for example setUser(instance, options). Options are passed to this.save * * 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 * * 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 {Model} target
* @param {object} [options] * @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.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.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'] * @param {string} [options.onDelete='SET&nbsp;NULL']
...@@ -158,13 +158,14 @@ Mixin.belongsTo = function(targetModel, options) { ...@@ -158,13 +158,14 @@ Mixin.belongsTo = function(targetModel, options) {
* *
* * get[AS] - for example getPictures(). * * 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. * * 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. * * create[AS] - for example createPicture(values, options). Build and save a new association.
* * remove[AS] - for example removePicture(instance). Remove a single 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] - for example hasPicture(instance). Is source associated to this target?
* * has[AS] [plural] - for example hasPictures(instances). Is source associated to all these targets? * * 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 * 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: * with a join table that stores whether the project has been started yet:
...@@ -200,7 +201,7 @@ Mixin.belongsTo = function(targetModel, options) { ...@@ -200,7 +201,7 @@ Mixin.belongsTo = function(targetModel, options) {
* @param {Model} target * @param {Model} target
* @param {object} [options] * @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 {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.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.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 * @param {string} [options.onDelete='SET&nbsp;NULL|CASCADE'] Cascade if this is a n:m, and set null if it is a 1:m
......
...@@ -293,6 +293,35 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -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) { it("clears associations when passing null to the set-method with omitNull set to true", function(done) {
this.sequelize.options.omitNull = true this.sequelize.options.omitNull = true
...@@ -923,6 +952,36 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -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 () { describe('optimizations using bulk create, destroy and update', function () {
beforeEach(function (done) { beforeEach(function (done) {
this.User = this.sequelize.define('User', { username: DataTypes.STRING }, {timestamps: false}) this.User = this.sequelize.define('User', { username: DataTypes.STRING }, {timestamps: false})
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!