var Utils = require("./utils") , DAO = require("./dao") , DataTypes = require("./data-types") , Util = require('util') , sql = require('sql') , SqlString = require('./sql-string') , Transaction = require('./transaction') , QueryTypes = require('./query-types') module.exports = (function() { var DAOFactory = function(name, attributes, options) { this.options = Utils._.extend({ timestamps: true, createdAt: 'createdAt', updatedAt: 'updatedAt', deletedAt: 'deletedAt', instanceMethods: {}, classMethods: {}, validate: {}, freezeTableName: false, underscored: false, syncOnAssociation: true, paranoid: false, whereCollection: null, schema: null, schemaDelimiter: '', language: 'en', defaultScope: null, scopes: null, hooks: { beforeCreate: [], afterCreate: [] } }, options || {}) // error check options Utils._.each(options.validate, function(validator, validatorType) { if (Utils._.contains(Utils._.keys(attributes), validatorType)) { throw new Error("A model validator function must not have the same name as a field. Model: " + name + ", field/validation name: " + validatorType) } if (!Utils._.isFunction(validator)) { throw new Error("Members of the validate option must be functions. Model: " + name + ", error with validate member " + validatorType) } }) this.name = name if (!this.options.tableName) { this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name, this.options.language) } else { this.tableName = this.options.tableName } attributes = replaceReferencesWithTableNames(attributes) this.options.hooks = this.replaceHookAliases(this.options.hooks) this.rawAttributes = attributes this.daoFactoryManager = null // defined in init function this.associations = {} this.scopeObj = {} } Object.defineProperty(DAOFactory.prototype, 'attributes', { get: function() { return this.QueryGenerator.attributesToSQL(this.rawAttributes) } }) Object.defineProperty(DAOFactory.prototype, 'sequelize', { get: function() { return this.daoFactoryManager.sequelize } }) Object.defineProperty(DAOFactory.prototype, 'QueryInterface', { get: function() { return this.daoFactoryManager.sequelize.getQueryInterface() } }) Object.defineProperty(DAOFactory.prototype, 'QueryGenerator', { get: function() { return this.QueryInterface.QueryGenerator } }) // inject the node-sql methods to the dao factory in order to // receive the syntax sugar ... ;(function() { var instance = sql.define({ name: "dummy", columns: [] }) for (var methodName in instance) { ;(function(methodName) { DAOFactory.prototype[methodName] = function() { var dataset = this.dataset() , result = dataset[methodName].apply(dataset, arguments) , dialect = this.daoFactoryManager.sequelize.options.dialect , self = this result.toSql = function() { var query = result.toQuery() return SqlString.format(query.text.replace(/(\$\d)/g, '?'), query.values, null, dialect) + ';' } result.exec = function(options) { options = Utils._.extend({ transaction: null, type: QueryTypes.SELECT }, options || {}) return self.QueryInterface.queryAndEmit([result.toSql(), self, options], 'snafu') } return result } })(methodName) } })() DAOFactory.prototype.init = function(daoFactoryManager) { var self = this this.daoFactoryManager = daoFactoryManager this.primaryKeys = {} self.options.uniqueKeys = {} Utils._.each(this.rawAttributes, function(columnValues, columnName) { if (columnValues.hasOwnProperty('unique') && columnValues.unique !== true && columnValues.unique !== false) { var idxName = columnValues.unique if (typeof columnValues.unique === "object") { idxName = columnValues.unique.name } self.options.uniqueKeys[idxName] = self.options.uniqueKeys[idxName] || {fields: [], msg: null} self.options.uniqueKeys[idxName].fields.push(columnName) self.options.uniqueKeys[idxName].msg = self.options.uniqueKeys[idxName].msg || columnValues.unique.msg || null } }) Utils._.each(this.attributes, function(dataTypeString, attributeName) { if (dataTypeString.indexOf('PRIMARY KEY') !== -1) { self.primaryKeys[attributeName] = dataTypeString } }) this.primaryKeyAttributes = Object.keys(this.primaryKeys) this.primaryKeyCount = this.primaryKeyAttributes.length this.options.hasPrimaryKeys = this.hasPrimaryKeys = this.primaryKeyCount > 0 if (typeof this.options.defaultScope === "object") { Utils.injectScope.call(this, this.options.defaultScope) } // DAO prototype // WTF ... ? this.DAO = function() { DAO.apply(this, arguments); } Util.inherits(this.DAO, DAO); this._timestampAttributes = {} if (this.options.timestamps) { if (this.options.createdAt) { this._timestampAttributes.createdAt = Utils._.underscoredIf(this.options.createdAt, this.options.underscored) } if (this.options.updatedAt) { this._timestampAttributes.updatedAt = Utils._.underscoredIf(this.options.updatedAt, this.options.underscored) } if (this.options.paranoid && this.options.deletedAt) { this._timestampAttributes.deletedAt = Utils._.underscoredIf(this.options.deletedAt, this.options.underscored) } this.DAO.prototype._readOnlyAttributes = Object.keys(this._timestampAttributes) } this.DAO.prototype._hasReadOnlyAttributes = this.DAO.prototype._readOnlyAttributes && this.DAO.prototype._readOnlyAttributes.length this.DAO.prototype._isReadOnlyAttribute = Utils._.memoize(function (key) { return self.DAO.prototype._hasReadOnlyAttributes && self.DAO.prototype._readOnlyAttributes.indexOf(key) !== -1 }) addDefaultAttributes.call(this) addOptionalClassMethods.call(this) findAutoIncrementField.call(this) this.DAO.prototype.rawAttributes = this.rawAttributes; this.DAO.prototype._hasPrimaryKeys = this.options.hasPrimaryKeys this.DAO.prototype._isPrimaryKey = Utils._.memoize(function (key) { return self.primaryKeyAttributes.indexOf(key) !== -1 && key !== 'id' }) if (this.options.instanceMethods) { Utils._.each(this.options.instanceMethods, function(fct, name) { self.DAO.prototype[name] = fct }) } this.refreshAttributes(); this.DAO.prototype.booleanValues = [] this.DAO.prototype.dateAttributes = [] this.DAO.prototype.defaultValues = {} this.DAO.prototype.validators = {} Utils._.each(this.rawAttributes, function (definition, name) { if (((definition === DataTypes.BOOLEAN) || (definition.type === DataTypes.BOOLEAN))) { self.DAO.prototype.booleanValues.push(name); } if (((definition === DataTypes.DATE) || (definition.type === DataTypes.DATE) || (definition.originalType === DataTypes.DATE))) { self.DAO.prototype.dateAttributes.push(name); } if (definition.hasOwnProperty('defaultValue')) { self.DAO.prototype.defaultValues[name] = Utils._.partial( Utils.toDefaultValue, definition.defaultValue) } if (definition.hasOwnProperty('validate')) { self.DAO.prototype.validators[name] = definition.validate; } }) this.DAO.prototype._hasBooleanAttributes = !!this.DAO.prototype.booleanValues.length this.DAO.prototype._isBooleanAttribute = Utils._.memoize(function (key) { return self.DAO.prototype.booleanValues.indexOf(key) !== -1 }) this.DAO.prototype._hasDateAttributes = !!this.DAO.prototype.dateAttributes.length this.DAO.prototype._isDateAttribute = Utils._.memoize(function (key) { return self.DAO.prototype.dateAttributes.indexOf(key) !== -1 }) 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 } DAOFactory.prototype.refreshAttributes = function() { var self = this , attributeManipulation = {}; this.DAO.prototype._customGetters = {} this.DAO.prototype._customSetters = {} Utils._.each(['get', 'set'], function(type) { var opt = type + 'terMethods' , funcs = Utils._.clone(Utils._.isObject(self.options[opt]) ? self.options[opt] : {}) , _custom = type === 'get' ? self.DAO.prototype._customGetters : self.DAO.prototype._customSetters Utils._.each(funcs, function (method, attribute) { _custom[attribute] = method if (type === 'get') { funcs[attribute] = function() { return this.get(attribute) } } if (type === 'set') { funcs[attribute] = function(value) { return this.set(attribute, value) } } }) Utils._.each(self.rawAttributes, function(options, attribute) { if (options.hasOwnProperty(type)) { _custom[attribute] = options[type] } if (type === 'get') { funcs[attribute] = function() { return this.get(attribute) } } if (type === 'set') { funcs[attribute] = function(value) { return this.set(attribute, value) } } }) Utils._.each(funcs, function(fct, name) { if (!attributeManipulation[name]) { attributeManipulation[name] = { configurable: true } } attributeManipulation[name][type] = fct }) }) this.DAO.prototype._hasCustomGetters = Object.keys(this.DAO.prototype._customGetters).length this.DAO.prototype._hasCustomSetters = Object.keys(this.DAO.prototype._customSetters).length Object.defineProperties(this.DAO.prototype, attributeManipulation) this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes) this.DAO.prototype._isAttribute = Utils._.memoize(function (key) { return self.DAO.prototype.attributes.indexOf(key) !== -1 }) } DAOFactory.prototype.sync = function(options) { options = Utils._.extend({}, this.options, options || {}) var self = this return new Utils.CustomEventEmitter(function(emitter) { var doQuery = function() { self .QueryInterface .createTable(self.getTableName(), self.attributes, options) .proxy(emitter, {events: ['error', 'sql']}) .success(function() { emitter.emit('success', self) }) } if (options.force) { self .drop(options) .proxy(emitter, {events: ['error', 'sql']}) .success(doQuery) } else { doQuery() } }).run() } DAOFactory.prototype.drop = function(options) { // Only Postgres' QueryGenerator.dropTableQuery() will add schema manually var isPostgres = this.options.dialect === "postgres" || (!!this.daoFactoryManager && this.daoFactoryManager.sequelize.options.dialect === "postgres") , tableName = isPostgres ? this.tableName : this.getTableName() return this.QueryInterface.dropTable(tableName, options) } DAOFactory.prototype.dropSchema = function(schema) { return this.QueryInterface.dropSchema(schema) } DAOFactory.prototype.schema = function(schema, options) { this.options.schema = schema if (!!options) { if (typeof options === "string") { this.options.schemaDelimiter = options } else { if (!!options.schemaDelimiter) { this.options.schemaDelimiter = options.schemaDelimiter } } } return this } DAOFactory.prototype.getTableName = function() { return this.QueryGenerator.addSchema(this) } DAOFactory.prototype.scope = function(option) { var self = Object.create(this) , type , options , merge , i , scope , scopeName , scopeOptions , argLength = arguments.length , lastArg = arguments[argLength-1] // Set defaults scopeOptions = (typeof lastArg === "object" && !Array.isArray(lastArg) ? lastArg : {}) || {} // <-- for no arguments scopeOptions.silent = (scopeOptions !== null && scopeOptions.hasOwnProperty('silent') ? scopeOptions.silent : true) // Clear out any predefined scopes... self.scopeObj = {} // Possible formats for option: // String of arguments: 'hello', 'world', 'etc' // Array: ['hello', 'world', 'etc'] // Object: {merge: 'hello'}, {method: ['scopeName' [, args1, args2..]]}, {merge: true, method: ...} if (argLength < 1 || !option) { return self } for (i = 0; i < argLength; i++) { options = Array.isArray(arguments[i]) ? arguments[i] : [arguments[i]] options.forEach(function(o){ type = typeof o scope = null merge = false scopeName = null if (type === "object") { // Right now we only support a merge functionality for objects if (!!o.merge) { merge = true scopeName = o.merge[0] if (Array.isArray(o.merge) && !!self.options.scopes[scopeName]) { scope = self.options.scopes[scopeName].apply(self, o.merge.splice(1)) } else if (typeof o.merge === "string") { scopeName = o.merge scope = self.options.scopes[scopeName] } } if (!!o.method) { if (Array.isArray(o.method) && !!self.options.scopes[o.method[0]]) { scopeName = o.method[0] scope = self.options.scopes[scopeName].apply(self, o.method.splice(1)) merge = !!o.merge } else if (!!self.options.scopes[o.method]) { scopeName = o.method scope = self.options.scopes[scopeName].apply(self) } } else { scopeName = o scope = self.options.scopes[scopeName] } } else { scopeName = o scope = self.options.scopes[scopeName] } if (!!scope) { Utils.injectScope.call(self, scope, merge) } else if (scopeOptions.silent !== true && !!scopeName) { throw new Error("Invalid scope " + scopeName + " called.") } }) } return self } // alias for findAll DAOFactory.prototype.all = function(options, queryOptions) { return this.findAll(options, queryOptions) } DAOFactory.prototype.findAll = function(options, queryOptions) { var hasJoin = false , tableNames = { } tableNames[this.tableName] = true options = optClone(options) if (typeof options === 'object') { if (options.hasOwnProperty('include') && options.include) { hasJoin = true validateIncludedElements.call(this, options, tableNames) } // whereCollection is used for non-primary key updates this.options.whereCollection = options.where || null } options = paranoidClause.call(this, options) return this.QueryInterface.select(this, this.getTableName(), options, Utils._.defaults({ type: QueryTypes.SELECT, hasJoin: hasJoin, tableNames: Object.keys(tableNames) }, queryOptions, { transaction: (options || {}).transaction })) } //right now, the caller (has-many-double-linked) is in charge of the where clause DAOFactory.prototype.findAllJoin = function(joinTableName, options, queryOptions) { var optcpy = Utils._.clone(options) optcpy.attributes = optcpy.attributes || [this.QueryInterface.quoteIdentifier(this.tableName)+".*"] // whereCollection is used for non-primary key updates this.options.whereCollection = optcpy.where || null; return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, Utils._.defaults({ type: QueryTypes.SELECT }, queryOptions, { transaction: (options || {}).transaction })) } /** * Search for an instance. * * @param {Object} options Options to describe the scope of the search. * @param {Array} include A list of associations which shall get eagerly loaded. Supported is either { include: [ DaoFactory1, DaoFactory2, ...] } or { include: [ { daoFactory: DaoFactory1, as: 'Alias' } ] }. * @param {Object} set the query options, e.g. raw, specifying that you want raw data instead of built DAOs * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. */ DAOFactory.prototype.find = function(options, queryOptions) { var hasJoin = false // no options defined? // return an emitter which emits null if ([null, undefined].indexOf(options) !== -1) { return new Utils.CustomEventEmitter(function(emitter) { setTimeout(function() { emitter.emit('success', null) }, 10) }).run() } var primaryKeys = this.primaryKeys , keys = Object.keys(primaryKeys) , keysLength = keys.length , tableNames = { } tableNames[this.tableName] = true // options is not a hash but an id if (typeof options === 'number') { var oldOption = options options = { where: {} } if (keysLength === 1) { options.where[keys[0]] = oldOption } else { options.where.id = oldOption } } else if (Utils._.size(primaryKeys) && Utils.argsArePrimaryKeys(arguments, primaryKeys)) { var where = {} Utils._.each(arguments, function(arg, i) { var key = keys[i] where[key] = arg }) options = { where: where } } else if (typeof options === 'string' && parseInt(options, 10).toString() === options) { var parsedId = parseInt(options, 10) if (!Utils._.isFinite(parsedId)) { throw new Error('Invalid argument to find(). Must be an id or an options object.') } options = { where: parsedId } } else if (typeof options === 'object') { options = Utils._.clone(options, function(thing) { if (Buffer.isBuffer(thing)) { return thing } return undefined; }) if (options.hasOwnProperty('include') && options.include) { hasJoin = true validateIncludedElements.call(this, options, tableNames) } // whereCollection is used for non-primary key updates this.options.whereCollection = options.where || null } else if (typeof options === "string") { var where = {} if (this.primaryKeyCount === 1) { where[primaryKeys[keys[0]]] = options; options = where; } else if (this.primaryKeyCount < 1) { // Revert to default behavior which is {where: [int]} options = {where: parseInt(Number(options) || 0, 0)} } } options = paranoidClause.call(this, options) if (options.limit === undefined) { options.limit = 1 } return this.QueryInterface.select(this, this.getTableName(), options, Utils._.defaults({ plain: true, type: QueryTypes.SELECT, hasJoin: hasJoin, tableNames: Object.keys(tableNames) }, queryOptions, { transaction: (options || {}).transaction })) } DAOFactory.prototype.aggregate = function(field, aggregateFunction, options) { var tableField; if (field == '*') { tableField = field } else { tableField = this.QueryInterface.QueryGenerator.quoteIdentifier(field) } options = Utils._.extend({ attributes: [] }, options || {}) options.attributes.push([aggregateFunction + '(' + tableField + ')', aggregateFunction]) if (!options.dataType) { if (this.rawAttributes[field]) { options.dataType = this.rawAttributes[field] } else { // Use FLOAT as fallback options.dataType = DataTypes.FLOAT } } options = paranoidClause.call(this, options) return this.QueryInterface.rawSelect(this.getTableName(), options, aggregateFunction) } DAOFactory.prototype.count = function(options) { options = Utils._.clone(options || {}) return new Utils.CustomEventEmitter(function (emitter) { var col = this.sequelize.col('*') if (options.include) { col = this.sequelize.col(this.tableName+'.'+(this.primaryKeyAttributes[0] || 'id')) } options.attributes = [ [this.sequelize.fn('COUNT', col), 'count'] ] options.includeIgnoreAttributes = false options.limit = null this.find(options, {raw: true, transaction: options.transaction}).proxy(emitter, {events: ['sql', 'error']}).success(function (result) { var count = (result && result.count) ? parseInt(result.count, 10) : 0 emitter.emit('success', count) }) }.bind(this)).run() } DAOFactory.prototype.findAndCountAll = function(findOptions, queryOptions) { var self = this // no limit, offset, order, attributes for the options given to count() , countOptions = Utils._.omit(findOptions ? Utils._.merge({}, findOptions) : {}, ['offset', 'limit', 'order', 'attributes']) return new Utils.CustomEventEmitter(function (emitter) { var emit = { okay : function(count, results) { // emit success emitter.emit('success', { count: count || 0, rows : (results && Array.isArray(results) ? results : []) }) } } self.count(countOptions) .proxy(emitter, {events: ['sql', 'error']}) .success(function(count) { if (count === 0) { return emit.okay(count) // no records, no need for another query } self.findAll(findOptions, queryOptions) .proxy(emitter, {events: ['sql', 'error']}) .success(function(results) { emit.okay(count, results) }) }) }).run() } DAOFactory.prototype.max = function(field, options) { return this.aggregate(field, 'max', options) } DAOFactory.prototype.min = function(field, options) { return this.aggregate(field, 'min', options) } DAOFactory.prototype.sum = function(field, options) { return this.aggregate(field, 'sum', options) } DAOFactory.prototype.build = function(values, options) { options = options || { isNewRecord: true, isDirty: true } if (options.hasOwnProperty('include') && options.include && !options.includeValidated) { validateIncludedElements.call(this, options) } return new this.DAO(values, options) } DAOFactory.prototype.create = function(values, fieldsOrOptions) { Utils.validateParameter(values, Object, { optional: true }) Utils.validateParameter(fieldsOrOptions, Object, { deprecated: Array, optional: true, index: 2, method: 'DAOFactory#create' }) if (fieldsOrOptions instanceof Array) { fieldsOrOptions = { fields: fieldsOrOptions } } fieldsOrOptions = Utils._.extend({ transaction: null }, fieldsOrOptions || {}) return this.build(values).save(fieldsOrOptions) } DAOFactory.prototype.findOrInitialize = DAOFactory.prototype.findOrBuild = function (params, defaults, options) { defaults = defaults || {} options = options || {} var self = this , defaultKeys = Object.keys(defaults) , defaultLength = defaultKeys.length if (!options.transaction && defaults.transaction && (defaults.transaction instanceof Transaction)) { options.transaction = defaults.transaction delete defaults.transaction } return new Utils.CustomEventEmitter(function (emitter) { self.find({ where: params }, options).success(function (instance) { if (instance === null) { var i = 0 for (i = 0; i < defaultLength; i++) { params[defaultKeys[i]] = defaults[defaultKeys[i]] } var build = self.build(params) build.hookValidate({skip: Object.keys(params)}).success(function (instance) { emitter.emit('success', build, true) }) .error(function (error) { emitter.emit('error', error) }) } else { emitter.emit('success', instance, false) } }).error(function (error) { emitter.emit('error', error) }) }).run() } DAOFactory.prototype.findOrCreate = function (where, defaults, options) { var self = this , values = {} options = Utils._.extend({ transaction: null }, options || {}) if (!(where instanceof Utils.or) && !(where instanceof Utils.and) && !Array.isArray(where)) { for (var attrname in where) { values[attrname] = where[attrname] } } return new Utils.CustomEventEmitter(function (emitter) { self.find({ where: where }, { transaction: options.transaction }).success(function (instance) { if (instance === null) { for (var attrname in defaults) { values[attrname] = defaults[attrname] } self .create(values, options) .success(function (instance) { emitter.emit('success', instance, true) }) .error( function (error) { emitter.emit('error', error) }) } else { emitter.emit('success', instance, false) } }).error(function (error) { emitter.emit('error', error) }); }).run() } /** * Create and insert multiple instances * * @param {Array} records List of objects (key/value pairs) to create instances from * @param {Array} fields Fields to insert (defaults to all fields) * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. * * Note: the `success` handler is not passed any arguments. To obtain DAOs for * the newly created values, you will need to query for them again. This is * because MySQL and SQLite do not make it easy to obtain back automatically * generated IDs and other default values in a way that can be mapped to * multiple records */ DAOFactory.prototype.bulkCreate = function(records, fieldsOrOptions, options) { Utils.validateParameter(fieldsOrOptions, Object, { deprecated: Array, optional: true, index: 2, method: 'DAOFactory#bulkCreate' }) Utils.validateParameter(options, 'undefined', { deprecated: Object, optional: true, index: 3, method: 'DAOFactory#bulkCreate' }) if (!records.length) { return new Utils.CustomEventEmitter(function(emitter) { emitter.emit('success', []) }).run(); } options = Utils._.extend({ validate: false, hooks: false, ignoreDuplicates: false }, options || {}) if (fieldsOrOptions instanceof Array) { options.fields = fieldsOrOptions } else { options.fields = options.fields || [] options = Utils._.extend(options, fieldsOrOptions) } if(this.daoFactoryManager.sequelize.options.dialect === 'postgres' && options.ignoreDuplicates ) { return new Utils.CustomEventEmitter(function(emitter) { emitter.emit('error', new Error('Postgres does not support the \'ignoreDuplicates\' option.')) }).run(); } var self = this , updatedAtAttr = this._timestampAttributes.updatedAt , createdAtAttr = this._timestampAttributes.createdAt , errors = [] , daos = records.map(function(v) { return self.build(v) }) return new Utils.CustomEventEmitter(function(emitter) { var done = function() { self.runHooks('afterBulkCreate', daos, options.fields, function(err, newRecords, newFields) { if (!!err) { return emitter.emit('error', err) } daos = newRecords || daos options.fields = newFields || options.fields emitter.emit('success', daos, options.fields) }) } var next = function() { if (options.hooks === false) { return runQuery() } var i = 0 var iterate = function(i) { self.runHooks('beforeCreate', daos[i], function(err, newValues) { if (!!err) { return emitter.emit('error', err) } daos[i] = newValues || daos[i] daos[i].save({ transaction: options.transaction }).error(function(err) { emitter.emit('error', err) }).success(function() { self.runHooks('afterCreate', daos[i], function(err, newValues) { if (!!err) { return emitter.emit('error', err) } daos[i] = newValues || daos[i] i++ if (i >= daos.length) { return done() } iterate(i) }) }) }) } iterate(i) } var runQuery = function() { // we will re-create from DAOs, which may have set up default attributes records = [] daos.forEach(function(dao) { var values = options.fields.length > 0 ? {} : dao.dataValues options.fields.forEach(function(field) { values[field] = dao.dataValues[field] }) if (createdAtAttr && !values[createdAtAttr]) { values[createdAtAttr] = Utils.now(self.daoFactoryManager.sequelize.options.dialect) } if (updatedAtAttr && !values[updatedAtAttr]) { values[updatedAtAttr] = Utils.now(self.daoFactoryManager.sequelize.options.dialect) } records.push(values) }) self.QueryInterface.bulkInsert(self.tableName, records, options) .on('sql', function(sql) { emitter.emit('sql', sql) }) .error(function(err) { emitter.emit('error', err) }).success(function(rows) { done() }) } self.runHooks('beforeBulkCreate', daos, options.fields, function(err, newRecords, newFields) { if (!!err) { return emitter.emit('error', err) } daos = newRecords || daos options.fields = newFields || options.fields if (options.validate === true) { if (options.fields.length) { var skippedFields = Utils._.difference(Object.keys(self.attributes), options.fields); } if (options.hooks === true) { var iterate = function(i) { daos[i].hookValidate({skip: skippedFields}).error(function(err) { errors[errors.length] = {record: v, errors: err} i++ if (i > daos.length) { if (errors.length > 0) { return emitter.emit('error', errors) } return next() } iterate(i) }) } } else { daos.forEach(function(v) { var valid = v.validate({skip: skippedFields}) if (valid !== null) { errors[errors.length] = {record: v, errors: valid} } }) if (errors.length > 0) { return emitter.emit('error', errors) } next() } } else { next() } }) }).run() } /** * Delete multiple instances * * @param {Object} where Options to describe the scope of the search. * @param {Object} options Possible options are: - hooks: If set to true, destroy will find all records within the where parameter and will execute before/afterDestroy hooks on each row - limit: How many rows to delete - truncate: If set to true, dialects that support it will use TRUNCATE instead of DELETE FROM. If a table is truncated the where and limit options are ignored * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. */ DAOFactory.prototype.destroy = function(where, options) { options = options || {} options.force = options.force === undefined ? false : Boolean(options.force) options.type = QueryTypes.BULKDELETE var self = this , query = null , args = [] return new Utils.CustomEventEmitter(function(emitter) { self.runHooks(self.options.hooks.beforeBulkDestroy, where, function(err, newWhere) { if (!!err) { return emitter.emit('error', err) } where = newWhere || where if (self._timestampAttributes.deletedAt && options.force === false) { var attrValueHash = {} attrValueHash[self._timestampAttributes.deletedAt] = Utils.now() query = 'bulkUpdate' args = [self.tableName, attrValueHash, where] } else { query = 'bulkDelete' args = [self.tableName, where, options] } var runQuery = function(err, records) { if (!!err) { return emitter.emit('error', err) } query = self.QueryInterface[query].apply(self.QueryInterface, args) query.on('sql', function(sql) { emitter.emit('sql', sql) }) .error(function(err) { emitter.emit('error', err) }) .success(function(results) { var finished = function(err) { if (!!err) { return emitter.emit('error', err) } self.runHooks(self.options.hooks.afterBulkDestroy, where, function(err) { if (!!err) { return emitter.emit('error', err) } emitter.emit('success', results) }) } if (options && options.hooks === true) { var tick = 0 var next = function(i) { self.runHooks(self.options.hooks.afterDestroy, records[i], function(err, newValues) { if (!!err) { return finished(err) } records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues tick++ if (tick >= records.length) { return finished() } next(tick) }) } next(tick) } else { finished() } }) } if (options && options.hooks === true) { var tick = 0 self.all({where: where}).error(function(err) { emitter.emit('error', err) }) .success(function(records) { var next = function(i) { self.runHooks(self.options.hooks.beforeDestroy, records[i], function(err, newValues) { if (!!err) { return runQuery(err) } records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues tick++ if (tick >= records.length) { return runQuery(null, records) } next(tick) }) } next(tick) }) // } else { runQuery() } }) }).run() } /** * Update multiple instances * * @param {Object} attrValueHash A hash of fields to change and their new values * @param {Object} where Options to describe the scope of the search. * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. */ DAOFactory.prototype.update = function(attrValueHash, where, options) { var self = this , query = null , tick = 0 options = options || {} options.validate = options.validate === undefined ? true : Boolean(options.validate) options.hooks = options.hooks === undefined ? false : Boolean(options.hooks) options.type = QueryTypes.BULKUPDATE if (self._timestampAttributes.updatedAt) { attrValueHash[self._timestampAttributes.updatedAt] = Utils.now() } return new Utils.CustomEventEmitter(function(emitter) { var runSave = function() { self.runHooks(self.options.hooks.beforeBulkUpdate, attrValueHash, where, function(err, attributes, _where) { if (!!err) { return emitter.emit('error', err) } where = _where || where attrValueHash = attributes || attrValueHash var runQuery = function(err, records) { if (!!err) { return emitter.emit('error', err) } query = self.QueryInterface.bulkUpdate(self.tableName, attrValueHash, where, options) query.on('sql', function(sql) { emitter.emit('sql', sql) }) .error(function(err) { emitter.emit('error', err) }) .success(function(results) { var finished = function(err, records) { if (!!err) { return emitter.emit('error', err) } self.runHooks(self.options.hooks.afterBulkUpdate, attrValueHash, where, function(err) { if (!!err) { return emitter.emit('error', err) } emitter.emit('success', records) }) } if (options && options.hooks === true && !!records && records.length > 0) { var tick = 0 var next = function(i) { self.runHooks(self.options.hooks.afterUpdate, records[i], function(err, newValues) { if (!!err) { return finished(err) } records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues tick++ if (tick >= records.length) { return finished(null, records) } next(tick) }) } next(tick) } else { finished(null, results) } }) } if (options.hooks === true) { self.all({where: where}).error(function(err) { emitter.emit('error', err) }) .success(function(records) { if (records === null || records.length < 1) { return runQuery(null) } var next = function(i) { self.runHooks(self.options.hooks.beforeUpdate, records[i], function(err, newValues) { if (!!err) { return runQuery(err) } records[i].dataValues = !!newValues ? newValues.dataValues : records[i].dataValues tick++ if (tick >= records.length) { return runQuery(null, records) } next(tick) }) } next(tick) }) } else { runQuery() } }) } if (options.validate === true) { var build = self.build(attrValueHash) // We want to skip validations for all other fields var updatedFields = Object.keys(attrValueHash) var skippedFields = Utils._.difference(Object.keys(self.attributes), updatedFields) build.hookValidate({skip: skippedFields}).error(function(err) { emitter.emit('error', err) }).success(function(attributes) { if (!!attributes && !!attributes.dataValues) { attrValueHash = Utils._.pick.apply(Utils._, [].concat(attributes.dataValues).concat(Object.keys(attrValueHash))) } runSave() }) } else { runSave() } }).run() } DAOFactory.prototype.describe = function(schema) { return this.QueryInterface.describeTable(this.tableName, schema || this.options.schema || undefined) } DAOFactory.prototype.dataset = function() { if (!this.__sql) { this.__setSqlDialect() } var instance = this.__sql.define({ name: this.tableName, columns: [] }) , attributes = this.attributes Object.keys(attributes).forEach(function(key) { instance.addColumn(key, attributes[key]) }) return instance } DAOFactory.prototype.__setSqlDialect = function() { var dialect = this.daoFactoryManager.sequelize.options.dialect this.__sql = sql.setDialect(dialect === 'mariadb' ? 'mysql' : dialect) } // private var paranoidClause = function(options) { if (this.options.paranoid === true) { options = options || {} options.where = options.where || {} var deletedAtCol = this._timestampAttributes.deletedAt , quoteIdentifiedDeletedAtCol = this.QueryInterface.quoteIdentifier(deletedAtCol) // Don't overwrite our explicit deletedAt search value if we provide one if (!!options.where[deletedAtCol]) { return options } if(this.tableName) { quoteIdentifiedDeletedAtCol = this.QueryInterface.quoteIdentifier(this.tableName) + '.' + quoteIdentifiedDeletedAtCol } if (typeof options.where === "string") { options.where += ' AND ' + quoteIdentifiedDeletedAtCol + ' IS NULL ' } else if (Array.isArray(options.where)) { // Don't overwrite our explicit deletedAt search value if we provide one if(options.where[0].indexOf(deletedAtCol) !== -1) { return options } options.where[0] += ' AND ' + quoteIdentifiedDeletedAtCol + ' IS NULL ' } else { options.where[deletedAtCol] = null } } return options } var addOptionalClassMethods = function() { var self = this Utils._.each(this.options.classMethods || {}, function(fct, name) { self[name] = fct }) } var addDefaultAttributes = function() { var self = this , tail = {} , head = { id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true, _autoGenerated: true } } if (this.hasPrimaryKeys) { head = {} } if (this._timestampAttributes.createdAt) { tail[this._timestampAttributes.createdAt] = {type: DataTypes.DATE, allowNull: false} } if (this._timestampAttributes.updatedAt) { tail[this._timestampAttributes.updatedAt] = {type: DataTypes.DATE, allowNull: false} } if (this._timestampAttributes.deletedAt) { tail[this._timestampAttributes.deletedAt] = {type: DataTypes.DATE} } var existingAttributes = Utils._.clone(self.rawAttributes) self.rawAttributes = {} Utils._.each(head, function(value, attr) { self.rawAttributes[attr] = value }) Utils._.each(existingAttributes, function(value, attr) { self.rawAttributes[attr] = value }) Utils._.each(tail, function(value, attr) { if (Utils._.isUndefined(self.rawAttributes[attr])) { self.rawAttributes[attr] = value } }) } var findAutoIncrementField = function() { var fields = this.QueryGenerator.findAutoIncrementField(this) this.autoIncrementField = null fields.forEach(function(field) { if (this.autoIncrementField) { throw new Error('Invalid DAO definition. Only one autoincrement field allowed.') } else { this.autoIncrementField = field } }.bind(this)) } var validateIncludedElements = function(options, tableNames) { tableNames = tableNames || {} options.includeNames = [] options.includeMap = {} options.hasSingleAssociation = false options.hasMultiAssociation = false // 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) include.parent = options // associations that are required or have a required child as is not a ?:M association are candidates for the subquery include.subQuery = !include.association.isMultiAssociation && (include.hasIncludeRequired || include.required) include.hasParentWhere = options.hasParentWhere || !!options.where include.hasParentRequired = options.hasParentRequired || !!options.required options.includeMap[include.as] = include options.includeNames.push(include.as) options.includeNames.push(include.as.substr(0,1).toLowerCase() + include.as.substr(1)) if (include.association.isMultiAssociation || include.hasMultiAssociation) options.hasMultiAssociation = true if (include.association.isSingleAssociation || include.hasSingleAssociation) options.hasSingleAssociation = true options.hasIncludeWhere = options.hasIncludeWhere || include.hasIncludeWhere || !!include.where options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required } } var validateIncludedElement = function(include, tableNames) { if (!include.hasOwnProperty('daoFactory')) { throw new Error('Include malformed. Expected attributes: daoFactory, as!') } tableNames[include.daoFactory.tableName] = true if (include.hasOwnProperty('attributes')) { var primaryKeys; if (include.daoFactory.hasPrimaryKeys) { primaryKeys = [] for (var field_name in include.daoFactory.primaryKeys) { primaryKeys.push(field_name) } } else { primaryKeys = ['id'] } include.attributes = include.attributes.concat(primaryKeys) } else { include.attributes = Object.keys(include.daoFactory.attributes) } // pseudo include just needed the attribute logic, return if (include._pseudo) return include // check if the current daoFactory is actually associated with the passed daoFactory - or it's a pseudo include var association = this.getAssociation(include.daoFactory, include.as) if (association) { include.association = association include.as = association.as // If through, we create a pseudo child include, to ease our parsing later on if (Object(include.association.through) === include.association.through) { if (!include.include) include.include = [] var through = include.association.through include.through = { daoFactory: through, as: Utils.singularize(through.tableName, through.options.language), association: { isSingleAssociation: true }, _pseudo: true } include.include.push(include.through) tableNames[through.tableName] = true } if (include.required === undefined) { include.required = !!include.where } // Validate child includes if (include.hasOwnProperty('include')) { validateIncludedElements.call(include.daoFactory, include, tableNames) } return include } else { var msg = include.daoFactory.name if (include.as) { msg += " (" + include.as + ")" } msg += " is not associated to " + this.name + "!" throw new Error(msg) } } 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) { Object.keys(attributes).forEach(function(attrName) { if (attributes[attrName].references instanceof DAOFactory) { attributes[attrName].references = attributes[attrName].references.tableName } }) return attributes } var optClone = function (options) { return Utils._.cloneDeep(options, function (elem) { // The DAOFactories used for include are pass by ref, so don't clone them. if (elem instanceof DAOFactory || elem instanceof Utils.col || elem instanceof Utils.literal || elem instanceof Utils.cast || elem instanceof Utils.fn || elem instanceof Utils.and || elem instanceof Utils.or) { return elem } // Unfortunately, lodash.cloneDeep doesn't preserve Buffer.isBuffer, which we have to rely on for binary data if (Buffer.isBuffer(elem)) { return elem; } // Otherwise return undefined, meaning, 'handle this lodash' return undefined }) } Utils._.extend(DAOFactory.prototype, require("./associations/mixin")) Utils._.extend(DAOFactory.prototype, require(__dirname + '/hooks')) return DAOFactory })()