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

Commit 061004f0 by Mick Hansen

Merge pull request #1178 from mickhansen/nested-include

Support nested includes
2 parents cfc08647 e67f378e
...@@ -376,9 +376,7 @@ module.exports = (function() { ...@@ -376,9 +376,7 @@ module.exports = (function() {
if (options.hasOwnProperty('include')) { if (options.hasOwnProperty('include')) {
hasJoin = true hasJoin = true
options.include = options.include.map(function(include) { validateIncludedElements.call(this, options)
return validateIncludedElement.call(this, include)
}.bind(this))
} }
// whereCollection is used for non-primary key updates // whereCollection is used for non-primary key updates
...@@ -461,9 +459,7 @@ module.exports = (function() { ...@@ -461,9 +459,7 @@ module.exports = (function() {
if (options.hasOwnProperty('include')) { if (options.hasOwnProperty('include')) {
hasJoin = true hasJoin = true
options.include = options.include.map(function(include) { validateIncludedElements.call(this, options)
return validateIncludedElement.call(this, include)
}.bind(this))
} }
// whereCollection is used for non-primary key updates // whereCollection is used for non-primary key updates
...@@ -558,7 +554,7 @@ module.exports = (function() { ...@@ -558,7 +554,7 @@ module.exports = (function() {
DAOFactory.prototype.build = function(values, options) { DAOFactory.prototype.build = function(values, options) {
options = options || { isNewRecord: true, isDirty: true } options = options || { isNewRecord: true, isDirty: true }
if (options.hasOwnProperty('include') && (!options.includeValidated || !options.includeNames)) { if (options.hasOwnProperty('include') && options.include && (!options.includeValidated || !options.includeNames)) {
options.includeNames = [] options.includeNames = []
options.include = options.include.map(function(include) { options.include = options.include.map(function(include) {
include = validateIncludedElement.call(this, include) include = validateIncludedElement.call(this, include)
...@@ -790,7 +786,7 @@ module.exports = (function() { ...@@ -790,7 +786,7 @@ module.exports = (function() {
}) })
.error(function(err) { .error(function(err) {
emitter.emit('error', err) emitter.emit('error', err)
}).success(function() { }).success(function(rows) {
done() done()
}) })
} }
...@@ -1194,17 +1190,37 @@ module.exports = (function() { ...@@ -1194,17 +1190,37 @@ module.exports = (function() {
}.bind(this)) }.bind(this))
} }
var validateIncludedElement = function(include) { var validateIncludedElements = function(options) {
options.includeNames = []
options.includeMap = {}
options.include = options.include.map(function(include) {
include = validateIncludedElement.call(this, include, options.daoFactory)
options.includeMap[include.as] = include
options.includeNames.push(include.as)
return include
}.bind(this))
};
var validateIncludedElement = function(include, parent) {
if (include instanceof DAOFactory) { if (include instanceof DAOFactory) {
include = { daoFactory: include, as: include.tableName } include = { daoFactory: include, as: include.tableName }
} }
if (typeof parent === "undefined") {
parent = this
}
if (typeof include === 'object') { if (typeof include === 'object') {
if (include.hasOwnProperty('model')) { if (include.hasOwnProperty('model')) {
include.daoFactory = include.model include.daoFactory = include.model
delete include.model delete include.model
} }
if (!include.hasOwnProperty('as')) {
include.as = include.daoFactory.tableName
}
if (include.hasOwnProperty('attributes')) { if (include.hasOwnProperty('attributes')) {
var primaryKeys; var primaryKeys;
if (include.daoFactory.hasPrimaryKeys) { if (include.daoFactory.hasPrimaryKeys) {
...@@ -1222,7 +1238,7 @@ module.exports = (function() { ...@@ -1222,7 +1238,7 @@ module.exports = (function() {
if (include.hasOwnProperty('daoFactory') && (include.hasOwnProperty('as'))) { if (include.hasOwnProperty('daoFactory') && (include.hasOwnProperty('as'))) {
var usesAlias = (include.as !== include.daoFactory.tableName) var usesAlias = (include.as !== include.daoFactory.tableName)
, association = (usesAlias ? this.getAssociationByAlias(include.as) : this.getAssociation(include.daoFactory)) , association = (usesAlias ? parent.getAssociationByAlias(include.as) : parent.getAssociation(include.daoFactory))
// If single (1:1) association, we singularize the alias, so it will match the automatically generated alias of belongsTo/HasOne // If single (1:1) association, we singularize the alias, so it will match the automatically generated alias of belongsTo/HasOne
if (association && !usesAlias && association.isSingleAssociation) { if (association && !usesAlias && association.isSingleAssociation) {
...@@ -1233,6 +1249,10 @@ module.exports = (function() { ...@@ -1233,6 +1249,10 @@ module.exports = (function() {
if (!!association && (!association.options.as || (association.options.as === include.as))) { if (!!association && (!association.options.as || (association.options.as === include.as))) {
include.association = association include.association = association
if (include.hasOwnProperty('include')) {
validateIncludedElements(include)
}
return include return include
} else { } else {
var msg = include.daoFactory.name var msg = include.daoFactory.name
......
...@@ -543,7 +543,14 @@ module.exports = (function() { ...@@ -543,7 +543,14 @@ module.exports = (function() {
accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1) accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1)
attrs[key].forEach(function(data) { attrs[key].forEach(function(data) {
var daoInstance = include.daoFactory.build(data, { isNewRecord: false, isDirty: false }) var daoInstance = include.daoFactory.build(data, {
isNewRecord: false,
isDirty: false,
include: include.include,
includeNames: include.includeNames,
includeMap: include.includeMap,
includeValidated: true
})
, isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers) , isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
if (association.isSingleAssociation) { if (association.isSingleAssociation) {
......
...@@ -361,8 +361,9 @@ module.exports = (function() { ...@@ -361,8 +361,9 @@ module.exports = (function() {
- offset -> An offset value to start from. Only useable with limit! - offset -> An offset value to start from. Only useable with limit!
*/ */
selectQuery: function(tableName, options, factory) { selectQuery: function(tableName, options, factory) {
var table = null, var table = null
joinQuery = "" , self = this
, joinQuery = ""
options = options || {} options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(", ") : this.quoteIdentifiers(tableName) options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(", ") : this.quoteIdentifiers(tableName)
...@@ -378,39 +379,59 @@ module.exports = (function() { ...@@ -378,39 +379,59 @@ module.exports = (function() {
if (options.include) { if (options.include) {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes] var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) { var generateJoinQuery = function(include, parentTable) {
var attributes = include.attributes.map(function(attr) { var table = include.daoFactory.tableName
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr) , as = include.as
}.bind(this)) , joinQueryItem = ""
, attributes
optAttributes = optAttributes.concat(attributes) if (tableName !== parentTable) as = parentTable+'.'+include.as
attributes = include.attributes.map(function(attr) {
return self.quoteIdentifier(as) + "." + self.quoteIdentifier(attr) + " AS " + self.quoteIdentifier(as + "." + attr)
})
var table = include.daoFactory.tableName optAttributes = optAttributes.concat(attributes)
, as = include.as
if (!(Object(include.association.through) === include.association.through)) {
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
, tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
, tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
, attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) if (include.association.doubleLinked) {
} else {
var primaryKeysSource = Object.keys(include.association.source.primaryKeys) var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
, tableSource = tableName , tableSource = parentTable
, identSource = include.association.identifier , identSource = include.association.identifier
, attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0]) , attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var primaryKeysTarget = Object.keys(include.association.target.primaryKeys) var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
, tableTarget = include.as , tableTarget = as
, identTarget = include.association.foreignIdentifier , identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0]) , attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
var tableJunction = include.association.through.tableName var tableJunction = include.association.through.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableSource) + "." + this.quoteIdentifier(attrSource) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identSource) joinQueryItem += " LEFT OUTER JOIN " + self.quoteIdentifier(tableJunction) + " ON "
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget) joinQueryItem += self.quoteIdentifier(tableSource) + "." + self.quoteIdentifier(attrSource) + " = "
joinQueryItem += self.quoteIdentifier(tableJunction) + "." + self.quoteIdentifier(identSource)
joinQueryItem += " LEFT OUTER JOIN " + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON "
joinQueryItem += self.quoteIdentifier(tableTarget) + "." + self.quoteIdentifier(attrTarget) + " = "
joinQueryItem += self.quoteIdentifier(tableJunction) + "." + self.quoteIdentifier(identTarget)
} else {
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
, tableLeft = ((include.association.associationType === 'BelongsTo') ? as : parentTable)
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
, tableRight = ((include.association.associationType === 'BelongsTo') ? parentTable : as)
, attrRight = include.association.identifier
joinQueryItem += " LEFT OUTER JOIN " + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON " + self.quoteIdentifier(tableLeft) + "." + self.quoteIdentifier(attrLeft) + " = " + self.quoteIdentifier(tableRight) + "." + self.quoteIdentifier(attrRight)
}
if (include.include) {
include.include.forEach(function(childInclude) {
joinQueryItem += generateJoinQuery(childInclude, as)
}.bind(this))
} }
return joinQueryItem
}
options.include.forEach(function(include) {
joinQuery += generateJoinQuery(include, tableName)
}.bind(this)) }.bind(this))
options.attributes = optAttributes.join(', ') options.attributes = optAttributes.join(', ')
......
...@@ -191,7 +191,7 @@ module.exports = (function() { ...@@ -191,7 +191,7 @@ module.exports = (function() {
} }
var isSelectQuery = function() { var isSelectQuery = function() {
return this.options.type === 'SELECT'; return this.options.type === 'SELECT'
} }
var isUpdateQuery = function() { var isUpdateQuery = function() {
...@@ -219,16 +219,14 @@ module.exports = (function() { ...@@ -219,16 +219,14 @@ module.exports = (function() {
// Queries with include // Queries with include
} else if (this.options.hasJoin === true) { } else if (this.options.hasJoin === true) {
this.options.includeNames = this.options.include.map(function (include) { results = groupJoinData(results, this.options)
return include.as
})
results = groupJoinData.call(this, results)
result = results.map(function(result) { result = results.map(function(result) {
return this.callee.build(result, { return this.callee.build(result, {
isNewRecord: false, isNewRecord: false,
isDirty: false, isDirty: false,
include:this.options.include, include:this.options.include,
includeNames: this.options.includeNames, includeNames: this.options.includeNames,
includeMap: this.options.includeMap,
includeValidated: true includeValidated: true
}) })
}.bind(this)) }.bind(this))
...@@ -318,18 +316,21 @@ module.exports = (function() { ...@@ -318,18 +316,21 @@ module.exports = (function() {
] ]
*/ */
var groupJoinData = function(data) { var groupJoinData = function(data, options) {
var self = this var results = []
, results = []
, existingResult , existingResult
, calleeData , calleeData
, child
data.forEach(function (row) { data.forEach(function (row) {
row = Dot.transform(row) row = Dot.transform(row)
calleeData = _.omit(row, self.options.includeNames) calleeData = _.omit(row, options.includeNames)
existingResult = _.find(results, function (result) { existingResult = _.find(results, function (result) {
return Utils._.isEqual(_.omit(result, self.options.includeNames), calleeData) if (options.includeNames) {
return Utils._.isEqual(_.omit(result, options.includeNames.concat(['__children'])), calleeData)
}
return Utils._.isEqual(result, calleeData)
}) })
if (!existingResult) { if (!existingResult) {
...@@ -337,16 +338,23 @@ module.exports = (function() { ...@@ -337,16 +338,23 @@ module.exports = (function() {
} }
for (var attrName in row) { for (var attrName in row) {
if (row.hasOwnProperty(attrName) && Object(row[attrName]) === row[attrName] && self.options.includeNames.indexOf(attrName) !== -1) { if (row.hasOwnProperty(attrName)) {
existingResult[attrName] = existingResult[attrName] || [] child = Object(row[attrName]) === row[attrName] && options.includeMap && options.includeMap[attrName]
var attrRowExists = existingResult[attrName].some(function(attrRow) { if (child) {
return Utils._.isEqual(attrRow, row[attrName]) if (!existingResult.__children) existingResult.__children = {}
}) if (!existingResult.__children[attrName]) existingResult.__children[attrName] = []
if (!attrRowExists) {
existingResult[attrName].push(row[attrName]) existingResult.__children[attrName].push(row[attrName])
} }
} }
} }
})
results.forEach(function (result) {
_.each(result.__children, function (children, key) {
result[key] = groupJoinData(children, options.includeMap[key])
})
delete result.__children
}) })
return results return results
} }
......
...@@ -37,6 +37,7 @@ module.exports = (function() { ...@@ -37,6 +37,7 @@ module.exports = (function() {
query.on('error', function(err) { query.on('error', function(err) {
receivedError = true receivedError = true
err.sql = sql err.sql = sql
self.emit('sql', sql)
self.emit('error', err, self.callee) self.emit('error', err, self.callee)
}) })
...@@ -125,6 +126,7 @@ module.exports = (function() { ...@@ -125,6 +126,7 @@ module.exports = (function() {
}) })
}) })
} }
this.emit('success', this.send('handleSelectQuery', rows)) this.emit('success', this.send('handleSelectQuery', rows))
} }
} else if (this.send('isShowOrDescribeQuery')) { } else if (this.send('isShowOrDescribeQuery')) {
...@@ -140,9 +142,8 @@ module.exports = (function() { ...@@ -140,9 +142,8 @@ module.exports = (function() {
this.callee[key] = record this.callee[key] = record
} }
} }
} }
this.emit('success', this.callee)
this.emit('success', this.callee)
} else if (this.send('isUpdateQuery')) { } else if (this.send('isUpdateQuery')) {
if(this.callee !== null) { // may happen for bulk updates if(this.callee !== null) { // may happen for bulk updates
for (var key in rows[0]) { for (var key in rows[0]) {
......
...@@ -659,7 +659,7 @@ module.exports = (function() { ...@@ -659,7 +659,7 @@ module.exports = (function() {
} }
var sql = this.QueryGenerator.selectQuery(tableName, options, factory) var sql = this.QueryGenerator.selectQuery(tableName, options, factory)
queryOptions = Utils._.extend({}, queryOptions, { include: options.include }) queryOptions = Utils._.extend({}, queryOptions, { include: options.include, includeNames: options.includeNames, includeMap: options.includeMap })
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select') return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
} }
......
...@@ -367,14 +367,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -367,14 +367,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done() done()
}) })
it('throws an error about missing attributes if include contains an object with daoFactory', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Worker } ] })
}).to.throw(Error, 'Include malformed. Expected attributes: daoFactory, as!')
done()
})
it('throws an error if included DaoFactory is not associated', function(done) { it('throws an error if included DaoFactory is not associated', function(done) {
var self = this var self = this
expect(function() { expect(function() {
......
...@@ -482,14 +482,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -482,14 +482,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done() done()
}) })
it('throws an error about missing attributes if include contains an object with daoFactory', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ { daoFactory: self.Worker } ] })
}).to.throw(Error, 'Include malformed. Expected attributes: daoFactory, as!')
done()
})
it('throws an error if included DaoFactory is not associated', function(done) { it('throws an error if included DaoFactory is not associated', function(done) {
var self = this var self = this
expect(function() { expect(function() {
......
/* jshint camelcase: false */
/* jshint expr: true */
var chai = require('chai')
, Sequelize = require('../index')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, _ = require('lodash')
, moment = require('moment')
, async = require('async')
chai.use(datetime)
chai.Assertion.includeStack = true
var sortById = function(a, b) {
return a.id < b.id ? -1 : 1
}
describe(Support.getTestDialectTeaser("Include"), function () {
describe('find', function () {
it('should support a simple nested belongsTo -> belongsTo include', function (done) {
var Task = this.sequelize.define('Task', {})
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
Task.belongsTo(User)
User.belongsTo(Group)
this.sequelize.sync({force: true}).done(function () {
async.auto({
task: function (callback) {
Task.create().done(callback)
},
user: function (callback) {
User.create().done(callback)
},
group: function (callback) {
Group.create().done(callback)
},
taskUser: ['task', 'user', function (callback, results) {
results.task.setUser(results.user).done(callback)
}],
userGroup: ['user', 'group', function (callback, results) {
results.user.setGroup(results.group).done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
Task.find({
where: {
id: results.task.id
},
include: [
{model: User, include: [
{model: Group}
]}
]
}).done(function (err, task) {
expect(err).not.to.be.ok
expect(task.user).to.be.ok
expect(task.user.group).to.be.ok
done()
})
})
})
})
it('should support a simple nested hasOne -> hasOne include', function (done) {
var Task = this.sequelize.define('Task', {})
, User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
User.hasOne(Task)
Group.hasOne(User)
this.sequelize.sync({force: true}).done(function () {
async.auto({
task: function (callback) {
Task.create().done(callback)
},
user: function (callback) {
User.create().done(callback)
},
group: function (callback) {
Group.create().done(callback)
},
userTask: ['user', 'task', function (callback, results) {
results.user.setTask(results.task).done(callback)
}],
groupUser: ['group', 'user', function (callback, results) {
results.group.setUser(results.user).done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
Group.find({
where: {
id: results.group.id
},
include: [
{model: User, include: [
{model: Task}
]}
]
}).done(function (err, group) {
expect(err).not.to.be.ok
expect(group.user).to.be.ok
expect(group.user.task).to.be.ok
done()
})
})
})
})
it('should support a simple nested hasMany -> belongsTo include', function (done) {
var Task = this.sequelize.define('Task', {})
, User = this.sequelize.define('User', {})
, Project = this.sequelize.define('Project', {})
User.hasMany(Task)
Task.belongsTo(Project)
this.sequelize.sync({force: true}).done(function () {
async.auto({
user: function (callback) {
User.create().done(callback)
},
projects: function (callback) {
Project.bulkCreate([{}, {}]).done(function () {
Project.findAll().done(callback)
})
},
tasks: ['projects', function(callback, results) {
Task.bulkCreate([
{ProjectId: results.projects[0].id},
{ProjectId: results.projects[1].id},
{ProjectId: results.projects[0].id},
{ProjectId: results.projects[1].id}
]).done(function () {
Task.findAll().done(callback)
})
}],
userTasks: ['user', 'tasks', function (callback, results) {
results.user.setTasks(results.tasks).done(callback)
}]
}, function (err, results) {
User.find({
where: {
id: results.user.id
},
include: [
{model: Task, include: [
{model: Project}
]}
]
}).done(function (err, user) {
expect(err).not.to.be.ok
expect(user.tasks).to.be.ok
expect(user.tasks.length).to.equal(4)
user.tasks.forEach(function (task) {
expect(task.project).to.be.ok
})
done()
})
})
})
})
it('should support a simple nested belongsTo -> hasMany include', function (done) {
var Task = this.sequelize.define('Task', {})
, Worker = this.sequelize.define('Worker', {})
, Project = this.sequelize.define('Project', {})
Worker.belongsTo(Project)
Project.hasMany(Task)
this.sequelize.sync({force: true}).done(function () {
async.auto({
worker: function (callback) {
Worker.create().done(callback)
},
project: function (callback) {
Project.create().done(callback)
},
tasks: function(callback) {
Task.bulkCreate([
{},
{},
{},
{}
]).done(function () {
Task.findAll().done(callback)
})
},
projectTasks: ['project', 'tasks', function (callback, results) {
results.project.setTasks(results.tasks).done(callback)
}],
projectWorker: ['project', 'worker', function (callback, results) {
results.worker.setProject(results.project).done(callback)
}]
}, function (err, results) {
Worker.find({
where: {
id: results.worker.id
},
include: [
{model: Project, include: [
{model: Task}
]}
]
}).done(function (err, worker) {
expect(err).not.to.be.ok
expect(worker.project).to.be.ok
expect(worker.project.tasks).to.be.ok
expect(worker.project.tasks.length).to.equal(4)
done()
})
})
})
})
it('should support a simple nested hasMany <-> hasMany include', function (done) {
var User = this.sequelize.define('User', {})
, Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
User.hasMany(Product)
Product.hasMany(Tag)
Tag.hasMany(Product)
this.sequelize.sync({force: true}).done(function () {
async.auto({
user: function (callback) {
User.create().done(callback)
},
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Dress'},
{title: 'Bed'}
]).done(function () {
Product.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
userProducts: ['user', 'products', function (callback, results) {
results.user.setProducts(results.products).done(callback)
}],
productTags: ['products', 'tags', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[0].setTags([results.tags[0], results.tags[2]]))
chainer.add(results.products[1].setTags([results.tags[1]]))
chainer.add(results.products[2].setTags([results.tags[0], results.tags[1], results.tags[2]]))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.find({
where: {
id: results.user.id
},
include: [
{model: Product, include: [
{model: Tag}
]}
]
}).done(function (err, user) {
expect(err).not.to.be.ok
expect(user.products.length).to.equal(4)
expect(user.products[0].tags.length).to.equal(2)
expect(user.products[1].tags.length).to.equal(1)
expect(user.products[2].tags.length).to.equal(3)
expect(user.products[3].tags.length).to.equal(0)
done()
})
})
})
})
it('should support an include with multiple different association types', function (done) {
var User = this.sequelize.define('User', {})
, Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, Price = this.sequelize.define('Price', {
value: DataTypes.FLOAT
})
, Customer = this.sequelize.define('Customer', {
name: DataTypes.STRING
})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
, GroupMember = this.sequelize.define('GroupMember', {
})
, Rank = this.sequelize.define('Rank', {
name: DataTypes.STRING,
canInvite: {
type: DataTypes.INTEGER,
defaultValue: 0
},
canRemove: {
type: DataTypes.INTEGER,
defaultValue: 0
}
})
User.hasMany(Product)
Product.belongsTo(User)
Product.hasMany(Tag)
Tag.hasMany(Product)
Product.belongsTo(Tag, {as: 'Category'})
Product.hasMany(Price)
Price.belongsTo(Product)
User.hasMany(GroupMember, {as: 'Memberships'})
GroupMember.belongsTo(User)
GroupMember.belongsTo(Rank)
GroupMember.belongsTo(Group)
Group.hasMany(GroupMember, {as: 'Memberships'})
this.sequelize.sync({force: true}).done(function () {
async.auto({
user: function (callback) {
User.create().done(callback)
},
groups: function(callback) {
Group.bulkCreate([
{name: 'Developers'},
{name: 'Designers'}
]).done(function () {
Group.findAll().done(callback)
})
},
ranks: function(callback) {
Rank.bulkCreate([
{name: 'Admin', canInvite: 1, canRemove: 1},
{name: 'Member', canInvite: 1}
]).done(function () {
Rank.findAll().done(callback)
})
},
memberships: ['user', 'groups', 'ranks', function (callback, results) {
GroupMember.bulkCreate([
{UserId: results.user.id, GroupId: results.groups[0].id, RankId: results.ranks[0].id},
{UserId: results.user.id, GroupId: results.groups[1].id, RankId: results.ranks[1].id}
]).done(callback)
}],
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'}
]).done(function () {
Product.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
userProducts: ['user', 'products', function (callback, results) {
results.user.setProducts(results.products).done(callback)
}],
productTags: ['products', 'tags', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[0].setTags([results.tags[0], results.tags[2]]))
chainer.add(results.products[1].setTags([results.tags[1]]))
chainer.add(results.products[0].setCategory(results.tags[1]))
chainer.run().done(callback)
}],
prices: ['products', function (callback, results) {
Price.bulkCreate([
{ProductId: results.products[0].id, value: 5},
{ProductId: results.products[0].id, value: 10},
{ProductId: results.products[1].id, value: 5},
{ProductId: results.products[1].id, value: 10},
{ProductId: results.products[1].id, value: 15},
{ProductId: results.products[1].id, value: 20}
]).done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.find({
where: {id: results.user.id},
include: [
{model: GroupMember, as: 'Memberships', include: [
Group,
Rank
]},
{model: Product, include: [
Tag,
{model: Tag, as: 'Category'},
Price
]}
]
}).done(function (err, user) {
user.memberships.sort(sortById)
expect(user.memberships.length).to.equal(2)
expect(user.memberships[0].group.name).to.equal('Developers')
expect(user.memberships[0].rank.canRemove).to.equal(1)
expect(user.memberships[1].group.name).to.equal('Designers')
expect(user.memberships[1].rank.canRemove).to.equal(0)
user.products.sort(sortById)
expect(user.products.length).to.equal(2)
expect(user.products[0].tags.length).to.equal(2)
expect(user.products[1].tags.length).to.equal(1)
expect(user.products[0].category).to.be.ok
expect(user.products[1].category).not.to.be.ok
expect(user.products[0].prices.length).to.equal(2)
expect(user.products[1].prices.length).to.equal(4)
done()
})
})
})
})
})
describe('findAll', function () {
it('should support an include with multiple different association types', function (done) {
var User = this.sequelize.define('User', {})
, Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, Price = this.sequelize.define('Price', {
value: DataTypes.FLOAT
})
, Customer = this.sequelize.define('Customer', {
name: DataTypes.STRING
})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
, GroupMember = this.sequelize.define('GroupMember', {
})
, Rank = this.sequelize.define('Rank', {
name: DataTypes.STRING,
canInvite: {
type: DataTypes.INTEGER,
defaultValue: 0
},
canRemove: {
type: DataTypes.INTEGER,
defaultValue: 0
}
})
User.hasMany(Product)
Product.belongsTo(User)
Product.hasMany(Tag)
Tag.hasMany(Product)
Product.belongsTo(Tag, {as: 'Category'})
Product.hasMany(Price)
Price.belongsTo(Product)
User.hasMany(GroupMember, {as: 'Memberships'})
GroupMember.belongsTo(User)
GroupMember.belongsTo(Rank)
GroupMember.belongsTo(Group)
Group.hasMany(GroupMember, {as: 'Memberships'})
this.sequelize.sync({force: true}).done(function () {
var count = 4
, i = -1
async.auto({
groups: function(callback) {
Group.bulkCreate([
{name: 'Developers'},
{name: 'Designers'}
]).done(function () {
Group.findAll().done(callback)
})
},
ranks: function(callback) {
Rank.bulkCreate([
{name: 'Admin', canInvite: 1, canRemove: 1},
{name: 'Member', canInvite: 1}
]).done(function () {
Rank.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
loop: ['groups', 'ranks', 'tags', function (done, results) {
var groups = results.groups
, ranks = results.ranks
, tags = results.tags
async.whilst(
function () { return i < count; },
function (callback) {
i++
async.auto({
user: function (callback) {
User.create().done(callback)
},
memberships: ['user', function (callback, results) {
GroupMember.bulkCreate([
{UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
{UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id}
]).done(callback)
}],
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'}
]).done(function () {
Product.findAll().done(callback)
})
},
userProducts: ['user', 'products', function (callback, results) {
results.user.setProducts([
results.products[(i * 2)+0],
results.products[(i * 2)+1]
]).done(callback)
}],
productTags: ['products', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[(i * 2) + 0].setTags([
tags[0],
tags[2]
]))
chainer.add(results.products[(i * 2) + 1].setTags([
tags[1]
]))
chainer.add(results.products[(i * 2) + 0].setCategory(tags[1]))
chainer.run().done(callback)
}],
prices: ['products', function (callback, results) {
Price.bulkCreate([
{ProductId: results.products[(i * 2) + 0].id, value: 5},
{ProductId: results.products[(i * 2) + 0].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 5},
{ProductId: results.products[(i * 2) + 1].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 15},
{ProductId: results.products[(i * 2) + 1].id, value: 20}
]).done(callback)
}]
}, callback)
},
function (err) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: GroupMember, as: 'Memberships', include: [
Group,
Rank
]},
{model: Product, include: [
Tag,
{model: Tag, as: 'Category'},
Price
]}
],
order: 'id ASC'
}).done(function (err, users) {
expect(err).not.to.be.ok
users.forEach(function (user, i) {
user.memberships.sort(sortById)
expect(user.memberships.length).to.equal(2)
expect(user.memberships[0].group.name).to.equal('Developers')
expect(user.memberships[0].rank.canRemove).to.equal(1)
expect(user.memberships[1].group.name).to.equal('Designers')
expect(user.memberships[1].rank.canRemove).to.equal(0)
user.products.sort(sortById)
expect(user.products.length).to.equal(2)
expect(user.products[0].tags.length).to.equal(2)
expect(user.products[1].tags.length).to.equal(1)
expect(user.products[0].category).to.be.ok
expect(user.products[1].category).not.to.be.ok
expect(user.products[0].prices.length).to.equal(2)
expect(user.products[1].prices.length).to.equal(4)
done()
})
})
}
)
}]
}, done)
})
})
it('should support many levels of belongsTo', function (done) {
var A = this.sequelize.define('A', {})
, B = this.sequelize.define('B', {})
, C = this.sequelize.define('C', {})
, D = this.sequelize.define('D', {})
, E = this.sequelize.define('E', {})
, F = this.sequelize.define('F', {})
, G = this.sequelize.define('G', {})
, H = this.sequelize.define('H', {})
A.belongsTo(B)
B.belongsTo(C)
C.belongsTo(D)
D.belongsTo(E)
E.belongsTo(F)
F.belongsTo(G)
G.belongsTo(H)
var b, singles = [
B,
C,
D,
E,
F,
G,
H
]
this.sequelize.sync().done(function () {
async.auto({
as: function (callback) {
A.bulkCreate([
{},
{},
{},
{},
{},
{},
{},
{}
]).done(function () {
A.findAll().done(callback)
})
},
singleChain: function (callback) {
var previousInstance
, previousModel
async.eachSeries(singles, function (model, callback, i) {
model.create({}).done(function (err, instance) {
if (previousInstance) {
previousInstance["set"+model.name](instance).done(function () {
previousInstance = instance
callback()
})
} else {
previousInstance = b = instance
callback()
}
})
}, callback)
},
abs: ['as', 'singleChain', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
results.as.forEach(function (a) {
chainer.add(a.setB(b))
})
chainer.run().done(callback)
}]
}, function () {
A.findAll({
include: [
{model: B, include: [
{model: C, include: [
{model: D, include: [
{model: E, include: [
{model: F, include: [
{model: G, include: [
{model: H}
]}
]}
]}
]}
]}
]}
]
}).done(function (err, as) {
expect(err).not.to.be.ok
expect(as.length).to.be.ok
as.forEach(function (a) {
expect(a.b.c.d.e.f.g.h).to.be.ok
})
done()
})
})
})
})
})
})
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!