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

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() {
})
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.daoFactoryName = this.name
return this
}
......@@ -555,13 +558,22 @@ module.exports = (function() {
DAOFactory.prototype.build = function(values, options) {
options = options || { isNewRecord: true, isDirty: true }
var self = this
, instance = new this.DAO(values, this.options, options.isNewRecord)
if (options.hasOwnProperty('include') && (!options.includeValidated || !options.includeNames)) {
options.includeNames = []
options.include = options.include.map(function(include) {
options.includeNames.push(include.as)
return validateIncludedElement.call(this, include)
}.bind(this))
}
instance.isNewRecord = options.isNewRecord
instance.daoFactoryName = this.name
instance.daoFactory = this
instance.isDirty = options.isDirty
if (options.includeNames) {
options.includeNames = options.includeNames.concat(options.includeNames.map(function (key) {
return key.slice(0,1).toLowerCase() + key.slice(1)
}))
}
var self = this
, instance = new this.DAO(values, options)
return instance
}
......
......@@ -3,16 +3,22 @@ var Utils = require("./utils")
, DaoValidator = require("./dao-validator")
, DataTypes = require("./data-types")
, hstore = require('./dialects/postgres/hstore')
, _ = require('lodash')
module.exports = (function() {
var DAO = function(values, options, isNewRecord) {
var DAO = function(values, options) {
this.dataValues = {}
this.__options = options
this.hasPrimaryKeys = options.hasPrimaryKeys
this.selectedValues = values
this.__options = this.__factory.options
this.options = options
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.isNewRecord = options.isNewRecord
initAttributes.call(this, values, isNewRecord)
initAttributes.call(this, values, options)
this.isDirty = options.isDirty
}
Utils._.extend(DAO.prototype, Mixin.prototype)
......@@ -36,17 +42,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'values', {
get: function() {
var result = {}
, self = this
this.attributes.concat(this.__eagerlyLoadedAssociations).forEach(function(attr) {
result[attr] = self.dataValues.hasOwnProperty(attr)
? self.dataValues[attr]
: self[attr]
;
})
return result
return this.dataValues
}
})
......@@ -104,7 +100,7 @@ module.exports = (function() {
options = Utils._.extend({}, options, fieldsOrOptions)
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)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
......@@ -214,7 +210,7 @@ module.exports = (function() {
return emitter.emit('error', err)
}
result.dataValues = newValues
result.dataValues = _.extend(result.dataValues, newValues)
emitter.emit('success', result)
})
})
......@@ -240,7 +236,7 @@ module.exports = (function() {
this.__factory.find({
where: where,
limit: 1,
include: this.__eagerlyLoadedOptions || []
include: this.options.include || []
}, options)
.on('sql', function(sql) { emitter.emit('sql', sql) })
.on('error', function(error) { emitter.emit('error', error) })
......@@ -474,12 +470,12 @@ module.exports = (function() {
}
DAO.prototype.toJSON = function() {
return this.values;
return this.dataValues;
}
// 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
var defaults = this.hasPrimaryKeys ? {} : { id: null },
attrs = {},
......@@ -487,7 +483,7 @@ module.exports = (function() {
// add all passed values to the dao and store the attribute names in this.attributes
if (isNewRecord) {
if (options.isNewRecord) {
if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function(valueFn, key) {
if (!defaults.hasOwnProperty(key)) {
......@@ -532,7 +528,40 @@ module.exports = (function() {
}
for (key in attrs) {
this.addAttribute(key, attrs[key])
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.addAttributes COMPLETELY destroys the structure of our DAO due to __defineGetter__ resetting the object
......
var Utils = require('../../utils')
, CustomEventEmitter = require("../../emitters/custom-event-emitter")
, Dot = require('dottie')
, _ = require('lodash')
module.exports = (function() {
var AbstractQuery = function(database, sequelize, callee, options) {}
......@@ -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.
*
* @param {String} fctName The name of the private method.
......@@ -167,10 +135,13 @@ module.exports = (function() {
if (!this.options.include) {
return null
}
if (!this.options.includeNames) {
this.options.includeNames = this.options.include.map(function(include) {
return include.as
})
}
var tableNames = this.options.include.map(function(include) {
return include.as
}).filter(function(include) {
var tableNames = this.options.includeNames.filter(function(include) {
return attribute.indexOf(include + '.') === 0
})
......@@ -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 result = true
......@@ -244,6 +201,7 @@ module.exports = (function() {
var handleSelectQuery = function(results) {
var result = null
// Raw queries
if (this.options.raw) {
result = results.map(function(result) {
var o = {}
......@@ -258,8 +216,22 @@ module.exports = (function() {
})
result = result.map(Dot.transform)
// Queries with include
} 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) {
result = results.map(function(result) {
result = Dot.transform(result)
......@@ -275,6 +247,8 @@ module.exports = (function() {
return mainDao
}.bind(this))
// Regular queries
} else {
result = results.map(function(result) {
return this.callee.build(result, { isNewRecord: false, isDirty: false })
......@@ -289,75 +263,6 @@ module.exports = (function() {
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 result = false
......@@ -381,15 +286,18 @@ module.exports = (function() {
the associated data by the callee.
Example:
groupDataByCalleeFactory([
groupJoinData([
{
callee: { some: 'data', id: 1 },
some: 'data',
id: 1,
association: { foo: 'bar', id: 1 }
}, {
callee: { some: 'data', id: 1 },
some: 'data',
id: 1,
association: { foo: 'bar', id: 2 }
}, {
callee: { some: 'data', id: 1 },
some: 'data',
id: 1,
association: { foo: 'bar', id: 3 }
}
])
......@@ -399,7 +307,8 @@ module.exports = (function() {
[
{
callee: { some: 'data', id: 1 },
some: 'data',
id: 1,
association: [
{ foo: 'bar', id: 1 },
{ foo: 'bar', id: 2 },
......@@ -408,67 +317,38 @@ module.exports = (function() {
}
]
*/
var groupDataByCalleeFactory = function(data) {
var result = []
, calleeTableName = this.callee.tableName
data.forEach(function(row) {
var calleeData = row[calleeTableName]
, existingEntry = result.filter(function(groupedRow) {
return Utils._.isEqual(groupedRow[calleeTableName], calleeData)
})[0]
if (!existingEntry) {
existingEntry = {}
result.push(existingEntry)
existingEntry[calleeTableName] = calleeData
var groupJoinData = function(data) {
var self = this
, results = []
, existingResult
, calleeData
data.forEach(function (row) {
row = Dot.transform(row)
calleeData = _.omit(row, self.options.includeNames)
existingResult = _.find(results, function (result) {
return Utils._.isEqual(_.omit(result, self.options.includeNames), calleeData)
})
if (!existingResult) {
results.push(existingResult = calleeData)
}
for (var attrName in row) {
if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) {
existingEntry[attrName] = existingEntry[attrName] || []
var attrRowExists = existingEntry[attrName].some(function(attrRow) {
if (row.hasOwnProperty(attrName) && Object(row[attrName]) === row[attrName] && self.options.includeNames.indexOf(attrName) !== -1) {
existingResult[attrName] = existingResult[attrName] || []
var attrRowExists = existingResult[attrName].some(function(attrRow) {
return Utils._.isEqual(attrRow, row[attrName])
})
if (!attrRowExists) {
existingEntry[attrName].push(row[attrName])
existingResult[attrName].push(row[attrName])
}
}
}
}
})
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 results
}
return AbstractQuery
......
......@@ -28,6 +28,15 @@ module.exports = (function() {
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.ok =
function(fct) {
......@@ -35,6 +44,15 @@ module.exports = (function() {
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.fail =
CustomEventEmitter.prototype.error =
......
......@@ -660,6 +660,7 @@ module.exports = (function() {
var sql = this.QueryGenerator.selectQuery(tableName, options, factory)
queryOptions = Utils._.extend({}, queryOptions, { include: options.include })
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
}
......
......@@ -904,9 +904,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}],
createTags: function (callback) {
self.Tag.bulkCreate([
{title: 'Furniture'},
{title: 'Clothing'},
{title: 'People'}
{name: 'Furniture'},
{name: 'Clothing'},
{name: 'People'}
]).done(callback)
},
tags: ['createTags', function (callback) {
......
......@@ -944,7 +944,9 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
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.age).to.equal(2)
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!