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

Commit 3afdc41e by Mick Hansen

Lots of refactoring - primarily that DAO now handles the construction of included models

1 parent d987f037
...@@ -168,7 +168,10 @@ module.exports = (function() { ...@@ -168,7 +168,10 @@ module.exports = (function() {
}) })
this.DAO.prototype.__factory = this this.DAO.prototype.__factory = this
this.DAO.prototype.daoFactory = this
this.DAO.prototype.Model = this
this.DAO.prototype.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues) this.DAO.prototype.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues)
this.DAO.prototype.daoFactoryName = this.name
return this return this
} }
...@@ -555,13 +558,22 @@ module.exports = (function() { ...@@ -555,13 +558,22 @@ 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 }
var self = this if (options.hasOwnProperty('include') && (!options.includeValidated || !options.includeNames)) {
, instance = new this.DAO(values, this.options, options.isNewRecord) options.includeNames = []
options.include = options.include.map(function(include) {
options.includeNames.push(include.as)
return validateIncludedElement.call(this, include)
}.bind(this))
}
if (options.includeNames) {
options.includeNames = options.includeNames.concat(options.includeNames.map(function (key) {
return key.slice(0,1).toLowerCase() + key.slice(1)
}))
}
instance.isNewRecord = options.isNewRecord var self = this
instance.daoFactoryName = this.name , instance = new this.DAO(values, options)
instance.daoFactory = this
instance.isDirty = options.isDirty
return instance return instance
} }
......
...@@ -3,16 +3,22 @@ var Utils = require("./utils") ...@@ -3,16 +3,22 @@ var Utils = require("./utils")
, DaoValidator = require("./dao-validator") , DaoValidator = require("./dao-validator")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, hstore = require('./dialects/postgres/hstore') , hstore = require('./dialects/postgres/hstore')
, _ = require('lodash')
module.exports = (function() { module.exports = (function() {
var DAO = function(values, options, isNewRecord) { var DAO = function(values, options) {
this.dataValues = {} this.dataValues = {}
this.__options = options this.__options = this.__factory.options
this.hasPrimaryKeys = options.hasPrimaryKeys this.options = options
this.selectedValues = values this.hasPrimaryKeys = this.__factory.options.hasPrimaryKeys
// What is selected values even used for?
this.selectedValues = options.include ? _.omit(values, options.includeNames) : values
this.__eagerlyLoadedAssociations = [] this.__eagerlyLoadedAssociations = []
this.isNewRecord = options.isNewRecord
initAttributes.call(this, values, isNewRecord) initAttributes.call(this, values, options)
this.isDirty = options.isDirty
} }
Utils._.extend(DAO.prototype, Mixin.prototype) Utils._.extend(DAO.prototype, Mixin.prototype)
...@@ -36,17 +42,7 @@ module.exports = (function() { ...@@ -36,17 +42,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'values', { Object.defineProperty(DAO.prototype, 'values', {
get: function() { get: function() {
var result = {} return this.dataValues
, self = this
this.attributes.concat(this.__eagerlyLoadedAssociations).forEach(function(attr) {
result[attr] = self.dataValues.hasOwnProperty(attr)
? self.dataValues[attr]
: self[attr]
;
})
return result
} }
}) })
...@@ -104,7 +100,7 @@ module.exports = (function() { ...@@ -104,7 +100,7 @@ module.exports = (function() {
options = Utils._.extend({}, options, fieldsOrOptions) options = Utils._.extend({}, options, fieldsOrOptions)
var self = this var self = this
, values = options.fields ? {} : this.dataValues , values = options.fields ? {} : (this.options.includeNames ? _.omit(this.dataValues, this.options.includeNames) : this.dataValues)
, updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored) , updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored) , createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
...@@ -214,7 +210,7 @@ module.exports = (function() { ...@@ -214,7 +210,7 @@ module.exports = (function() {
return emitter.emit('error', err) return emitter.emit('error', err)
} }
result.dataValues = newValues result.dataValues = _.extend(result.dataValues, newValues)
emitter.emit('success', result) emitter.emit('success', result)
}) })
}) })
...@@ -240,7 +236,7 @@ module.exports = (function() { ...@@ -240,7 +236,7 @@ module.exports = (function() {
this.__factory.find({ this.__factory.find({
where: where, where: where,
limit: 1, limit: 1,
include: this.__eagerlyLoadedOptions || [] include: this.options.include || []
}, options) }, options)
.on('sql', function(sql) { emitter.emit('sql', sql) }) .on('sql', function(sql) { emitter.emit('sql', sql) })
.on('error', function(error) { emitter.emit('error', error) }) .on('error', function(error) { emitter.emit('error', error) })
...@@ -474,12 +470,12 @@ module.exports = (function() { ...@@ -474,12 +470,12 @@ module.exports = (function() {
} }
DAO.prototype.toJSON = function() { DAO.prototype.toJSON = function() {
return this.values; return this.dataValues;
} }
// private // private
var initAttributes = function(values, isNewRecord) { var initAttributes = function(values, options) {
// set id to null if not passed as value, a newly created dao has no id // set id to null if not passed as value, a newly created dao has no id
var defaults = this.hasPrimaryKeys ? {} : { id: null }, var defaults = this.hasPrimaryKeys ? {} : { id: null },
attrs = {}, attrs = {},
...@@ -487,7 +483,7 @@ module.exports = (function() { ...@@ -487,7 +483,7 @@ module.exports = (function() {
// add all passed values to the dao and store the attribute names in this.attributes // add all passed values to the dao and store the attribute names in this.attributes
if (isNewRecord) { if (options.isNewRecord) {
if (this.hasDefaultValues) { if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function(valueFn, key) { Utils._.each(this.defaultValues, function(valueFn, key) {
if (!defaults.hasOwnProperty(key)) { if (!defaults.hasOwnProperty(key)) {
...@@ -532,8 +528,41 @@ module.exports = (function() { ...@@ -532,8 +528,41 @@ module.exports = (function() {
} }
for (key in attrs) { for (key in attrs) {
if (options.include && options.includeNames.indexOf(key) !== -1) {
var include = _.find(options.include, function (include) {
return include.as === key
})
var association = include.association
, self = this
var accessor = Utils._.camelize(key)
// downcase the first char
accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1)
attrs[key].forEach(function(data) {
var daoInstance = include.daoFactory.build(data, { isNewRecord: false, isDirty: false })
, isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) {
accessor = Utils.singularize(accessor, self.sequelize.language)
self.dataValues[accessor] = isEmpty ? null : daoInstance
self[accessor] = self.dataValues[accessor]
} else {
if (!self.dataValues[accessor]) {
self.dataValues[accessor] = []
self[accessor] = self.dataValues[accessor]
}
if (!isEmpty) {
self.dataValues[accessor].push(daoInstance)
}
}
}.bind(this))
} else {
this.addAttribute(key, attrs[key]) this.addAttribute(key, attrs[key])
} }
}
// this.addAttributes COMPLETELY destroys the structure of our DAO due to __defineGetter__ resetting the object // this.addAttributes COMPLETELY destroys the structure of our DAO due to __defineGetter__ resetting the object
// so now we have to rebuild for bulkInserts, bulkUpdates, etc. // so now we have to rebuild for bulkInserts, bulkUpdates, etc.
......
var Utils = require('../../utils') var Utils = require('../../utils')
, CustomEventEmitter = require("../../emitters/custom-event-emitter") , CustomEventEmitter = require("../../emitters/custom-event-emitter")
, Dot = require('dottie') , Dot = require('dottie')
, _ = require('lodash')
module.exports = (function() { module.exports = (function() {
var AbstractQuery = function(database, sequelize, callee, options) {} var AbstractQuery = function(database, sequelize, callee, options) {}
...@@ -100,39 +101,6 @@ module.exports = (function() { ...@@ -100,39 +101,6 @@ module.exports = (function() {
} }
/** /**
Shortcut methods (success, ok) for listening for success events.
Params:
- fct: A function that gets executed once the *success* event was triggered.
Result:
The function returns the instance of the query.
*/
AbstractQuery.prototype.success =
AbstractQuery.prototype.ok =
function(fct) {
this.on('success', fct)
return this
}
/**
Shortcut methods (failure, fail, error) for listening for error events.
Params:
- fct: A function that gets executed once the *error* event was triggered.
Result:
The function returns the instance of the query.
*/
AbstractQuery.prototype.failure =
AbstractQuery.prototype.fail =
AbstractQuery.prototype.error =
function(fct) {
this.on('error', fct)
return this
}
/**
* This function is a wrapper for private methods. * This function is a wrapper for private methods.
* *
* @param {String} fctName The name of the private method. * @param {String} fctName The name of the private method.
...@@ -167,10 +135,13 @@ module.exports = (function() { ...@@ -167,10 +135,13 @@ module.exports = (function() {
if (!this.options.include) { if (!this.options.include) {
return null return null
} }
if (!this.options.includeNames) {
var tableNames = this.options.include.map(function(include) { this.options.includeNames = this.options.include.map(function(include) {
return include.as return include.as
}).filter(function(include) { })
}
var tableNames = this.options.includeNames.filter(function(include) {
return attribute.indexOf(include + '.') === 0 return attribute.indexOf(include + '.') === 0
}) })
...@@ -181,20 +152,6 @@ module.exports = (function() { ...@@ -181,20 +152,6 @@ module.exports = (function() {
} }
} }
var queryResultHasJoin = function(results) {
if (!!results[0]) {
var keys = Object.keys(results[0])
for (var i = 0; i < keys.length; i++) {
if (!!findTableNameInAttribute.call(this, keys[i])) {
return true
}
}
}
return false
}
var isInsertQuery = function(results, metaData) { var isInsertQuery = function(results, metaData) {
var result = true var result = true
...@@ -244,6 +201,7 @@ module.exports = (function() { ...@@ -244,6 +201,7 @@ module.exports = (function() {
var handleSelectQuery = function(results) { var handleSelectQuery = function(results) {
var result = null var result = null
// Raw queries
if (this.options.raw) { if (this.options.raw) {
result = results.map(function(result) { result = results.map(function(result) {
var o = {} var o = {}
...@@ -258,8 +216,22 @@ module.exports = (function() { ...@@ -258,8 +216,22 @@ module.exports = (function() {
}) })
result = result.map(Dot.transform) result = result.map(Dot.transform)
// Queries with include
} else if (this.options.hasJoin === true) { } else if (this.options.hasJoin === true) {
result = transformRowsWithEagerLoadingIntoDaos.call(this, results) this.options.includeNames = this.options.include.map(function (include) {
return include.as
})
results = groupJoinData.call(this, results)
result = results.map(function(result) {
return this.callee.build(result, {
isNewRecord: false,
isDirty: false,
include:this.options.include,
includeNames: this.options.includeNames,
includeValidated: true
})
}.bind(this))
} else if (this.options.hasJoinTableModel === true) { } else if (this.options.hasJoinTableModel === true) {
result = results.map(function(result) { result = results.map(function(result) {
result = Dot.transform(result) result = Dot.transform(result)
...@@ -275,6 +247,8 @@ module.exports = (function() { ...@@ -275,6 +247,8 @@ module.exports = (function() {
return mainDao return mainDao
}.bind(this)) }.bind(this))
// Regular queries
} else { } else {
result = results.map(function(result) { result = results.map(function(result) {
return this.callee.build(result, { isNewRecord: false, isDirty: false }) return this.callee.build(result, { isNewRecord: false, isDirty: false })
...@@ -289,75 +263,6 @@ module.exports = (function() { ...@@ -289,75 +263,6 @@ module.exports = (function() {
return result return result
} }
var transformRowsWithEagerLoadingIntoDaos = function(results) {
var result = []
result = prepareJoinData.call(this, results)
result = groupDataByCalleeFactory.call(this, result).map(function(result) {
return transformRowWithEagerLoadingIntoDao.call(this, result)
}.bind(this))
return result
}
var transformRowWithEagerLoadingIntoDao = function(result, dao) {
// let's build the actual dao instance first...
dao = dao || this.callee.build(result[this.callee.tableName], { isNewRecord: false, isDirty: false })
// ... and afterwards the prefetched associations
for (var tableName in result) {
if (result.hasOwnProperty(tableName) && (tableName !== this.callee.tableName)) {
buildAssociatedDaoInstances.call(this, tableName, result[tableName], dao)
}
}
return dao
}
var buildAssociatedDaoInstances = function(tableName, associationData, dao) {
var associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(tableName, { attribute: 'tableName' })
, association = null
, self = this
if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory)
} else {
associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(Utils.pluralize(tableName, this.sequelize.language), { attribute: 'tableName' })
if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory)
} else {
association = this.callee.getAssociationByAlias(tableName)
associatedDaoFactory = association.target
}
}
var accessor = Utils._.camelize(tableName)
// downcase the first char
accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1)
associationData.forEach(function(data) {
var daoInstance = associatedDaoFactory.build(data, { isNewRecord: false, isDirty: false })
, isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) {
accessor = Utils.singularize(accessor, self.sequelize.language)
dao[accessor] = isEmpty ? null : daoInstance
} else {
dao[accessor] = dao[accessor] || []
if (!isEmpty) {
dao[accessor].push(daoInstance)
}
}
// add the accessor to the eagerly loaded associations array
dao.__eagerlyLoadedAssociations = Utils._.uniq(dao.__eagerlyLoadedAssociations.concat([accessor]))
dao.__eagerlyLoadedOptions = (this.options && this.options.include) ? this.options.include : []
}.bind(this))
}
var isShowOrDescribeQuery = function() { var isShowOrDescribeQuery = function() {
var result = false var result = false
...@@ -381,15 +286,18 @@ module.exports = (function() { ...@@ -381,15 +286,18 @@ module.exports = (function() {
the associated data by the callee. the associated data by the callee.
Example: Example:
groupDataByCalleeFactory([ groupJoinData([
{ {
callee: { some: 'data', id: 1 }, some: 'data',
id: 1,
association: { foo: 'bar', id: 1 } association: { foo: 'bar', id: 1 }
}, { }, {
callee: { some: 'data', id: 1 }, some: 'data',
id: 1,
association: { foo: 'bar', id: 2 } association: { foo: 'bar', id: 2 }
}, { }, {
callee: { some: 'data', id: 1 }, some: 'data',
id: 1,
association: { foo: 'bar', id: 3 } association: { foo: 'bar', id: 3 }
} }
]) ])
...@@ -399,7 +307,8 @@ module.exports = (function() { ...@@ -399,7 +307,8 @@ module.exports = (function() {
[ [
{ {
callee: { some: 'data', id: 1 }, some: 'data',
id: 1,
association: [ association: [
{ foo: 'bar', id: 1 }, { foo: 'bar', id: 1 },
{ foo: 'bar', id: 2 }, { foo: 'bar', id: 2 },
...@@ -408,67 +317,38 @@ module.exports = (function() { ...@@ -408,67 +317,38 @@ module.exports = (function() {
} }
] ]
*/ */
var groupDataByCalleeFactory = function(data) {
var result = []
, calleeTableName = this.callee.tableName
data.forEach(function(row) { var groupJoinData = function(data) {
var calleeData = row[calleeTableName] var self = this
, existingEntry = result.filter(function(groupedRow) { , results = []
return Utils._.isEqual(groupedRow[calleeTableName], calleeData) , existingResult
})[0] , calleeData
data.forEach(function (row) {
row = Dot.transform(row)
calleeData = _.omit(row, self.options.includeNames)
if (!existingEntry) { existingResult = _.find(results, function (result) {
existingEntry = {} return Utils._.isEqual(_.omit(result, self.options.includeNames), calleeData)
result.push(existingEntry) })
existingEntry[calleeTableName] = calleeData
if (!existingResult) {
results.push(existingResult = calleeData)
} }
for (var attrName in row) { for (var attrName in row) {
if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) { if (row.hasOwnProperty(attrName) && Object(row[attrName]) === row[attrName] && self.options.includeNames.indexOf(attrName) !== -1) {
existingEntry[attrName] = existingEntry[attrName] || [] existingResult[attrName] = existingResult[attrName] || []
var attrRowExists = existingEntry[attrName].some(function(attrRow) { var attrRowExists = existingResult[attrName].some(function(attrRow) {
return Utils._.isEqual(attrRow, row[attrName]) return Utils._.isEqual(attrRow, row[attrName])
}) })
if (!attrRowExists) { if (!attrRowExists) {
existingEntry[attrName].push(row[attrName]) existingResult[attrName].push(row[attrName])
} }
} }
} }
}) })
return results
return result
}
/**
* This function will prepare the result of select queries with joins.
*
* @param {Array} data This array contains objects.
* @return {Array} The array will have the needed format for groupDataByCalleeFactory.
*/
var prepareJoinData = function(data) {
var result = data.map(function(row) {
var nestedRow = {}
for (var key in row) {
if (row.hasOwnProperty(key)) {
var tableName = findTableNameInAttribute.call(this, key)
if (!!tableName) {
nestedRow[tableName] = nestedRow[tableName] || {}
nestedRow[tableName][key.replace(tableName + '.', '')] = row[key]
} else {
nestedRow[this.callee.tableName] = nestedRow[this.callee.tableName] || {}
nestedRow[this.callee.tableName][key] = row[key]
}
}
}
return nestedRow
}.bind(this))
return result
} }
return AbstractQuery return AbstractQuery
......
...@@ -28,6 +28,15 @@ module.exports = (function() { ...@@ -28,6 +28,15 @@ module.exports = (function() {
return this return this
} }
/**
Shortcut methods (success, ok) for listening for success events.
Params:
- fct: A function that gets executed once the *success* event was triggered.
Result:
The function returns the instance of the query.
*/
CustomEventEmitter.prototype.success = CustomEventEmitter.prototype.success =
CustomEventEmitter.prototype.ok = CustomEventEmitter.prototype.ok =
function(fct) { function(fct) {
...@@ -35,6 +44,15 @@ module.exports = (function() { ...@@ -35,6 +44,15 @@ module.exports = (function() {
return this return this
} }
/**
Shortcut methods (failure, fail, error) for listening for error events.
Params:
- fct: A function that gets executed once the *error* event was triggered.
Result:
The function returns the instance of the query.
*/
CustomEventEmitter.prototype.failure = CustomEventEmitter.prototype.failure =
CustomEventEmitter.prototype.fail = CustomEventEmitter.prototype.fail =
CustomEventEmitter.prototype.error = CustomEventEmitter.prototype.error =
......
...@@ -660,6 +660,7 @@ module.exports = (function() { ...@@ -660,6 +660,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 })
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select') return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
} }
......
...@@ -904,9 +904,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -904,9 +904,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}], }],
createTags: function (callback) { createTags: function (callback) {
self.Tag.bulkCreate([ self.Tag.bulkCreate([
{title: 'Furniture'}, {name: 'Furniture'},
{title: 'Clothing'}, {name: 'Clothing'},
{title: 'People'} {name: 'People'}
]).done(callback) ]).done(callback)
}, },
tags: ['createTags', function (callback) { tags: ['createTags', function (callback) {
......
...@@ -944,7 +944,9 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -944,7 +944,9 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
user.age = user.age + 1 // happy birthday joe user.age = user.age + 1 // happy birthday joe
user.save().success(function() { user.save().done(function(err) {
expect(err).not.to.be.ok
expect(user.username).to.equal('joe') expect(user.username).to.equal('joe')
expect(user.age).to.equal(2) expect(user.age).to.equal(2)
expect(user.projects).to.exist expect(user.projects).to.exist
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!