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

Commit 60bd58c4 by Mick Hansen

Merge pull request #1421 from overlookmotel/include-all

Eager loading all associations
2 parents 97f840e3 38e48b5c
...@@ -1345,8 +1345,38 @@ module.exports = (function() { ...@@ -1345,8 +1345,38 @@ module.exports = (function() {
options.includeMap = {} options.includeMap = {}
options.hasSingleAssociation = false options.hasSingleAssociation = false
options.hasMultiAssociation = false options.hasMultiAssociation = false
options.include = options.include.map(function(include) {
include = validateIncludedElement.call(this, include, options.daoFactory, tableNames) // if include is not an array, wrap in an array
if (!Array.isArray(options.include)) {
options.include = [options.include]
}
// convert all included elements to { daoFactory: Model } form
var includes = options.include = options.include.map(function(include) {
if (include instanceof DAOFactory) {
return { daoFactory: include }
} else if (typeof include !== 'object') {
throw new Error('Include unexpected. Element has to be either an instance of DAOFactory or an object.')
} else if (include.hasOwnProperty('model')) {
include.daoFactory = include.model
delete include.model
}
return include
})
// validate all included elements
for (var index = 0; index < includes.length; index++) {
var include = includes[index]
if (include.all) {
includes.splice(index, 1)
index--
validateIncludedAllElement.call(this, includes, include)
continue
}
include = includes[index] = validateIncludedElement.call(this, include, tableNames)
options.includeMap[include.as] = include options.includeMap[include.as] = include
options.includeNames.push(include.as) options.includeNames.push(include.as)
...@@ -1357,28 +1387,11 @@ module.exports = (function() { ...@@ -1357,28 +1387,11 @@ module.exports = (function() {
options.hasIncludeWhere = options.hasIncludeWhere || include.hasIncludeWhere || !!include.where options.hasIncludeWhere = options.hasIncludeWhere || include.hasIncludeWhere || !!include.where
options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required
return include
}.bind(this))
};
var validateIncludedElement = function(include, parent, tableNames) {
if (include instanceof DAOFactory) {
include = { daoFactory: include }
}
if (typeof parent === "undefined") {
parent = this
}
if (typeof include !== 'object') {
throw new Error('Include unexpected. Element has to be either an instance of DAOFactory or an object.')
} }
}
if (include.hasOwnProperty('model')) { var validateIncludedElement = function(include, tableNames) {
include.daoFactory = include.model if (!include.hasOwnProperty('daoFactory')) {
delete include.model
} else if (!include.hasOwnProperty('daoFactory')) {
throw new Error('Include malformed. Expected attributes: daoFactory, as!') throw new Error('Include malformed. Expected attributes: daoFactory, as!')
} }
...@@ -1403,7 +1416,7 @@ module.exports = (function() { ...@@ -1403,7 +1416,7 @@ module.exports = (function() {
if (include._pseudo) return include if (include._pseudo) return include
// check if the current daoFactory is actually associated with the passed daoFactory - or it's a pseudo include // check if the current daoFactory is actually associated with the passed daoFactory - or it's a pseudo include
var association = parent.getAssociation(include.daoFactory, include.as) var association = this.getAssociation(include.daoFactory, include.as)
if (association) { if (association) {
include.association = association include.association = association
include.as = association.as include.as = association.as
...@@ -1432,7 +1445,7 @@ module.exports = (function() { ...@@ -1432,7 +1445,7 @@ module.exports = (function() {
// Validate child includes // Validate child includes
if (include.hasOwnProperty('include')) { if (include.hasOwnProperty('include')) {
validateIncludedElements(include, tableNames) validateIncludedElements.call(include.daoFactory, include, tableNames)
} }
return include return include
...@@ -1449,6 +1462,100 @@ module.exports = (function() { ...@@ -1449,6 +1462,100 @@ module.exports = (function() {
} }
} }
var validateIncludedAllElement = function(includes, include) {
// check 'all' attribute provided is valid
var all = include.all
delete include.all
if (all !== true) {
if (!Array.isArray(all)) {
all = [all]
}
var validTypes = {
BelongsTo: true,
HasOne: true,
HasMany: true,
One: ['BelongsTo', 'HasOne'],
Has: ['HasOne', 'HasMany'],
Many: ['HasMany']
}
for (var i = 0; i < all.length; i++) {
var type = all[i]
if (type == 'All') {
all = true
break
}
var types = validTypes[type]
if (!types) {
throw new Error('include all \'' + type + '\' is not valid - must be BelongsTo, HasOne, HasMany, One, Has, Many or All')
}
if (types !== true) {
// replace type placeholder e.g. 'One' with it's constituent types e.g. 'HasOne', 'BelongsTo'
all.splice(i, 1)
i--
for (var j = 0; j < types.length; j++) {
if (all.indexOf(types[j]) == -1) {
all.unshift(types[j])
i++
}
}
}
}
}
// add all associations of types specified to includes
var nested = include.nested
if (nested) {
delete include.nested
if (!include.include) {
include.include = []
} else if (!Array.isArray(include.include)) {
include.include = [include.include]
}
}
var used = []
;(function addAllIncludes(parent, includes) {
used.push(parent)
Utils._.forEach(parent.associations, function(association) {
if (all !== true && all.indexOf(association.associationType) == -1) {
return
}
// check if model already included, and skip if so
var model = association.target
var as = association.options.as
if (Utils._.find(includes, {daoFactory: model, as: as})) {
return
}
// skip if recursing over a model already nested
if (nested && used.indexOf(model) != -1) {
return
}
// include this model
var thisInclude = optClone(include)
thisInclude.daoFactory = model
if (as) {
thisInclude.as = as
}
includes.push(thisInclude)
// run recursively if nested
if (nested) {
addAllIncludes(model, thisInclude.include)
}
})
used.pop()
})(this, includes)
}
var replaceReferencesWithTableNames = function(attributes) { var replaceReferencesWithTableNames = function(attributes) {
Object.keys(attributes).forEach(function(attrName) { Object.keys(attributes).forEach(function(attrName) {
if (attributes[attrName].references instanceof DAOFactory) { if (attributes[attrName].references instanceof DAOFactory) {
......
...@@ -830,6 +830,123 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -830,6 +830,123 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
}) })
describe('include all', function() {
beforeEach(function(done) {
var self = this
self.Continent = this.sequelize.define('Continent', { name: Sequelize.STRING })
self.Country = this.sequelize.define('Country', { name: Sequelize.STRING })
self.Industry = this.sequelize.define('Industry', { name: Sequelize.STRING })
self.Person = this.sequelize.define('Person', { name: Sequelize.STRING, lastName: Sequelize.STRING })
self.Continent.hasMany(self.Country)
self.Country.belongsTo(self.Continent)
self.Country.hasMany(self.Industry)
self.Industry.hasMany(self.Country)
self.Country.hasMany(self.Person)
self.Person.belongsTo(self.Country)
self.Country.hasMany(self.Person, { as: 'Residents', foreignKey: 'CountryResidentId' })
self.Person.belongsTo(self.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' })
async.forEach([ self.Continent, self.Country, self.Industry, self.Person ], function(model, callback) {
model.sync({ force: true }).done(callback)
}, function () {
async.parallel({
europe: function(callback) {self.Continent.create({ name: 'Europe' }).done(callback)},
england: function(callback) {self.Country.create({ name: 'England' }).done(callback)},
coal: function(callback) {self.Industry.create({ name: 'Coal' }).done(callback)},
bob: function(callback) {self.Person.create({ name: 'Bob', lastName: 'Becket' }).done(callback)}
}, function(err, r) {
if (err) throw err
_.forEach(r, function(item, itemName) {
self[itemName] = item
})
async.parallel([
function(callback) {self.england.setContinent(self.europe).done(callback)},
function(callback) {self.england.addIndustry(self.coal).done(callback)},
function(callback) {self.bob.setCountry(self.england).done(callback)},
function(callback) {self.bob.setCountryResident(self.england).done(callback)}
], function(err) {
if (err) throw err
done()
})
})
})
})
it('includes all associations', function(done) {
this.Country.findAll({ include: [ { all: true } ] }).done(function(err, countries) {
expect(err).not.to.be.ok
expect(countries).to.exist
expect(countries[0]).to.exist
expect(countries[0].continent).to.exist
expect(countries[0].industries).to.exist
expect(countries[0].persons).to.exist
expect(countries[0].residents).to.exist
done()
})
})
it('includes specific type of association', function(done) {
this.Country.findAll({ include: [ { all: 'BelongsTo' } ] }).done(function(err, countries) {
expect(err).not.to.be.ok
expect(countries).to.exist
expect(countries[0]).to.exist
expect(countries[0].continent).to.exist
expect(countries[0].industries).not.to.exist
expect(countries[0].persons).not.to.exist
expect(countries[0].residents).not.to.exist
done()
})
})
it('utilises specified attributes', function(done) {
this.Country.findAll({ include: [ { all: 'HasMany', attributes: [ 'name' ] } ] }).done(function(err, countries) {
expect(err).not.to.be.ok
expect(countries).to.exist
expect(countries[0]).to.exist
expect(countries[0].industries).to.exist
expect(countries[0].persons).to.exist
expect(countries[0].persons[0]).to.exist
expect(countries[0].persons[0].name).not.to.be.undefined
expect(countries[0].persons[0].lastName).to.be.undefined
expect(countries[0].residents).to.exist
expect(countries[0].residents[0]).to.exist
expect(countries[0].residents[0].name).not.to.be.undefined
expect(countries[0].residents[0].lastName).to.be.undefined
done()
})
})
it('is over-ruled by specified include', function(done) {
this.Country.findAll({ include: [ { all: true }, { model: this.Continent, attributes: [] } ] }).done(function(err, countries) {
expect(err).not.to.be.ok
expect(countries).to.exist
expect(countries[0]).to.exist
expect(countries[0].continent).to.exist
expect(countries[0].continent.name).to.be.undefined
done()
})
})
it('includes all nested associations', function(done) {
this.Continent.findAll({ include: [ { all: true, nested: true } ] }).done(function(err, continents) {
expect(err).not.to.be.ok
expect(continents).to.exist
expect(continents[0]).to.exist
expect(continents[0].countries).to.exist
expect(continents[0].countries[0]).to.exist
expect(continents[0].countries[0].industries).to.exist
expect(continents[0].countries[0].persons).to.exist
expect(continents[0].countries[0].residents).to.exist
expect(continents[0].countries[0].continent).not.to.exist
done()
})
})
})
}) })
describe('order by eager loaded tables', function() { describe('order by eager loaded tables', function() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!