123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691 |
- var Utils = require("./utils")
- , Mixin = require("./associations/mixin")
- , DaoValidator = require("./dao-validator")
- , DataTypes = require("./data-types")
- , hstore = require('./dialects/postgres/hstore')
- , _ = require('lodash')
- module.exports = (function() {
- var DAO = function(values, options) {
- this.dataValues = {}
- this._previousDataValues = {}
- 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
- initValues.call(this, values, options);
- }
- Utils._.extend(DAO.prototype, Mixin.prototype)
- Object.defineProperty(DAO.prototype, 'sequelize', {
- get: function(){ return this.__factory.daoFactoryManager.sequelize }
- })
- Object.defineProperty(DAO.prototype, 'QueryInterface', {
- get: function(){ return this.sequelize.getQueryInterface() }
- })
- Object.defineProperty(DAO.prototype, 'isDeleted', {
- get: function() {
- return this.Model._timestampAttributes.deletedAt && this.dataValues[this.Model._timestampAttributes.deletedAt] !== null
- }
- })
- Object.defineProperty(DAO.prototype, 'values', {
- get: function() {
- return this.get()
- }
- })
- Object.defineProperty(DAO.prototype, 'isDirty', {
- get: function() {
- return !!this.changed()
- }
- })
- Object.defineProperty(DAO.prototype, 'primaryKeyValues', {
- get: function() {
- var result = {}
- , self = this
- Utils._.each(this.__factory.primaryKeys, function(_, attr) {
- result[attr] = self.dataValues[attr]
- })
- return result
- }
- })
- Object.defineProperty(DAO.prototype, "identifiers", {
- get: function() {
- var primaryKeys = Object.keys(this.__factory.primaryKeys)
- , result = {}
- , self = this
- if (!this.__factory.hasPrimaryKeys) {
- primaryKeys = ['id']
- }
- primaryKeys.forEach(function(identifier) {
- result[identifier] = self.dataValues[identifier]
- })
- return result
- }
- })
- DAO.prototype.getDataValue = function(key) {
- return this.dataValues[key]
- }
- DAO.prototype.setDataValue = function(key, value) {
- this.dataValues[key] = value
- }
- DAO.prototype.get = function (key) {
- if (key) {
- if (this._customGetters[key]) {
- return this._customGetters[key].call(this, key)
- }
- return this.dataValues[key]
- }
- if (this._hasCustomGetters) {
- var values = {}
- , key
- for (key in this._customGetters) {
- if (this._customGetters.hasOwnProperty(key)) {
- values[key] = this.get(key)
- }
- }
- for (key in this.dataValues) {
- if (!values.hasOwnProperty(key) && this.dataValues.hasOwnProperty(key)) {
- values[key] = this.dataValues[key]
- }
- }
- return values
- }
- return this.dataValues
- }
- DAO.prototype.set = function (key, value, options) {
- var values
- , originalValue
- if (typeof key === "object") {
- values = key
- options = value
- options || (options = {})
- if (options.reset) {
- this.dataValues = {}
- }
- // If raw, and we're not dealing with includes, just set it straight on the dataValues object
- if (options.raw && !(this.options && this.options.include) && !this._hasBooleanAttributes) {
- if (Object.keys(this.dataValues).length) {
- this.dataValues = _.extend(this.dataValues, values)
- } else {
- this.dataValues = values
- }
- // If raw, .changed() shouldn't be true
- this._previousDataValues = _.clone(this.dataValues)
- } else {
- // Loop and call set
- for (key in values) {
- this.set(key, values[key], options)
- }
- if (options.raw) {
- // If raw, .changed() shouldn't be true
- this._previousDataValues = _.clone(this.dataValues)
- }
- }
- } else {
- options || (options = {})
- originalValue = this.dataValues[key]
- // If not raw, and there's a customer setter
- if (!options.raw && this._customSetters[key]) {
- this._customSetters[key].call(this, value, key)
- } else {
- // Check if we have included models, and if this key matches the include model names/aliases
- if (this.options && this.options.include && this.options.includeNames.indexOf(key) !== -1) {
- // Pass it on to the include handler
- this._setInclude(key, value, options)
- return
- } else {
- // If not raw, and attribute is not in model definition, return
- if (!options.raw && !this._isAttribute(key)) {
- return;
- }
- // If attempting to set primary key and primary key is already defined, return
- if (this._hasPrimaryKeys && originalValue && this._isPrimaryKey(key)) {
- return
- }
- // If attempting to set generated id and id is already defined, return
- // This is hack since generated id is not in primaryKeys, although it should be
- if (originalValue && key === "id") {
- return
- }
- // If attempting to set read only attributes, return
- if (!options.raw && this._hasReadOnlyAttributes && this._isReadOnlyAttribute(key)) {
- return
- }
- // Convert boolean-ish values to booleans
- if (this._hasBooleanAttributes && this._isBooleanAttribute(key) && value !== null && value !== undefined) {
- value = !!value
- }
- // Convert date fields to real date objects
- if (this._hasDateAttributes && this._isDateAttribute(key) && value !== null && !(value instanceof Date)) {
- value = new Date(value)
- }
- if (originalValue !== value) {
- this._previousDataValues[key] = originalValue
- }
- this.dataValues[key] = value
- }
- }
- }
- }
- DAO.prototype.changed = function(key) {
- if (key) {
- if (this._isDateAttribute(key) && this._previousDataValues[key] && this.dataValues[key]) {
- return this._previousDataValues[key].valueOf() !== this.dataValues[key].valueOf()
- }
- return this._previousDataValues[key] !== this.dataValues[key]
- }
- var changed = Object.keys(this.dataValues).filter(function (key) {
- return this.changed(key)
- }.bind(this))
- return changed.length ? changed : false
- }
- DAO.prototype.previous = function(key) {
- return this._previousDataValues[key]
- }
- DAO.prototype._setInclude = function(key, value, options) {
- if (!Array.isArray(value)) value = [value]
- if (value[0] instanceof DAO) {
- value = value.map(function (instance) {
- return instance.dataValues
- })
- }
- var include = _.find(this.options.include, function (include) {
- return include.as === key || (include.as.slice(0,1).toLowerCase() + include.as.slice(1)) === 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)
- value.forEach(function(data) {
- var daoInstance = include.daoFactory.build(data, {
- isNewRecord: false,
- isDirty: false,
- include: include.include,
- includeNames: include.includeNames,
- includeMap: include.includeMap,
- includeValidated: true,
- raw: options.raw
- })
- , isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
- if (association.isSingleAssociation) {
- 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))
- };
- // if an array with field names is passed to save()
- // only those fields will be updated
- DAO.prototype.save = function(fieldsOrOptions, options) {
- if (fieldsOrOptions instanceof Array) {
- fieldsOrOptions = { fields: fieldsOrOptions }
- }
- options = Utils._.extend({}, options, fieldsOrOptions)
- if (!options.fields) {
- options.fields = Object.keys(this.Model.attributes)
- }
- if (options.returning === undefined) {
- if (options.association) {
- options.returning = false
- } else {
- options.returning = true
- }
- }
- var self = this
- , values = {}
- , updatedAtAttr = this.Model._timestampAttributes.updatedAt
- , createdAtAttr = this.Model._timestampAttributes.createdAt
- if (options.fields) {
- if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) === -1) {
- options.fields.push(updatedAtAttr)
- }
- if (createdAtAttr && options.fields.indexOf(createdAtAttr) === -1 && this.isNewRecord === true) {
- options.fields.push(createdAtAttr)
- }
- }
- return new Utils.CustomEventEmitter(function(emitter) {
- self.hookValidate().error(function(err) {
- emitter.emit('error', err)
- }).success(function() {
- options.fields.forEach(function(field) {
- if (self.dataValues[field] !== undefined) {
- values[field] = self.dataValues[field]
- }
- })
- for (var attrName in self.daoFactory.rawAttributes) {
- if (self.daoFactory.rawAttributes.hasOwnProperty(attrName)) {
- var definition = self.daoFactory.rawAttributes[attrName]
- , isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type
- , isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
- , isMySQL = ['mysql', 'mariadb'].indexOf(self.daoFactory.daoFactoryManager.sequelize.options.dialect) !== -1
- , ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i)
- , valueOutOfScope
- // Unfortunately for MySQL CI collation we need to map/lowercase values again
- if (isEnum && isMySQL && ciCollation && (attrName in values) && values[attrName]) {
- var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase())
- valueOutOfScope = scopeIndex === -1
- // We'll return what the actual case will be, since a simple SELECT query would do the same...
- if (!valueOutOfScope) {
- values[attrName] = definition.values[scopeIndex]
- }
- }
- if (isHstore) {
- if (typeof values[attrName] === "object") {
- values[attrName] = hstore.stringify(values[attrName])
- }
- }
- }
- }
- if (updatedAtAttr) {
- values[updatedAtAttr] = (
- (
- self.isNewRecord
- && !!self.daoFactory.rawAttributes[updatedAtAttr]
- && !!self.daoFactory.rawAttributes[updatedAtAttr].defaultValue
- )
- ? self.daoFactory.rawAttributes[updatedAtAttr].defaultValue
- : Utils.now(self.sequelize.options.dialect))
- }
-
- if (self.isNewRecord && createdAtAttr && !values[createdAtAttr]) {
- values[createdAtAttr] = (
- (
- !!self.daoFactory.rawAttributes[createdAtAttr]
- && !!self.daoFactory.rawAttributes[createdAtAttr].defaultValue
- )
- ? self.daoFactory.rawAttributes[createdAtAttr].defaultValue
- : values[updatedAtAttr])
- }
- var query = null
- , args = []
- , hook = ''
- if (self.isNewRecord) {
- query = 'insert'
- args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values, options]
- hook = 'Create'
- } else {
- var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id }
- if (identifier === null && self.__options.whereCollection !== null) {
- identifier = self.__options.whereCollection;
- }
- query = 'update'
- args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values, identifier, options]
- hook = 'Update'
- }
- // Add the values to the DAO
- self.dataValues = _.extend(self.dataValues, values)
- // Run the beforeCreate / beforeUpdate hook
- self.__factory.runHooks('before' + hook, self, function(err) {
- if (!!err) {
- return emitter.emit('error', err)
- }
- // dataValues might have changed inside the hook, rebuild
- // the values hash
- values = {}
- options.fields.forEach(function(field) {
- if (self.dataValues[field] !== undefined) {
- values[field] = self.dataValues[field]
- }
- })
- args[2] = values
- self.QueryInterface[query].apply(self.QueryInterface, args)
- .proxy(emitter, {events: ['sql']})
- .error(function(err) {
- if (!!self.__options.uniqueKeys && err.code && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.code) {
- var fields = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.toString())
- if (fields !== false) {
- fields = fields.filter(function(f) { return f !== self.daoFactory.tableName; })
- Utils._.each(self.__options.uniqueKeys, function(value, key) {
- if (Utils._.isEqual(value.fields, fields) && !!value.msg) {
- err = value.msg
- }
- })
- }
- }
- emitter.emit('error', err)
- })
- .success(function(result) {
- // Transfer database generated values (defaults, autoincrement, etc)
- values = _.extend(values, result.dataValues)
- // Ensure new values are on DAO, and reset previousDataValues
- result.dataValues = _.extend(result.dataValues, values)
- result._previousDataValues = _.clone(result.dataValues)
- self.__factory.runHooks('after' + hook, result, function(err) {
- if (!!err) {
- return emitter.emit('error', err)
- }
- emitter.emit('success', result)
- })
- })
- })
- })
- }).run()
- }
- /*
- * Refresh the current instance in-place, i.e. update the object with current data from the DB and return the same object.
- * This is different from doing a `find(DAO.id)`, because that would create and return a new object. With this method,
- * all references to the DAO are updated with the new data and no new objects are created.
- *
- * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
- */
- DAO.prototype.reload = function(options) {
- var where = [
- this.QueryInterface.quoteIdentifier(this.Model.getTableName()) + '.' + this.QueryInterface.quoteIdentifier(this.Model.primaryKeyAttributes[0] || 'id') + '=?',
- this.get(this.Model.primaryKeyAttributes[0] || 'id', {raw: true})
- ]
- return new Utils.CustomEventEmitter(function(emitter) {
- this.__factory.find({
- where: where,
- limit: 1,
- include: this.options.include || null
- }, options)
- .on('sql', function(sql) { emitter.emit('sql', sql) })
- .on('error', function(error) { emitter.emit('error', error) })
- .on('success', function(obj) {
- this.set(obj.dataValues, {raw: true, reset: true})
- this.isDirty = false
- emitter.emit('success', this)
- }.bind(this))
- }.bind(this)).run()
- }
- /*
- * Validate this dao's attribute values according to validation rules set in the dao definition.
- *
- * @return null if and only if validation successful; otherwise an object containing { field name : [error msgs] } entries.
- */
- DAO.prototype.validate = function(object) {
- var validator = new DaoValidator(this, object)
- , errors = validator.validate()
- return (Utils._.isEmpty(errors) ? null : errors)
- }
- /*
- * Validate this dao's attribute values according to validation rules set in the dao definition.
- *
- * @return CustomEventEmitter with null if validation successful; otherwise an object containing { field name : [error msgs] } entries.
- */
- DAO.prototype.hookValidate = function(object) {
- var validator = new DaoValidator(this, object)
- return validator.hookValidate()
- }
- DAO.prototype.updateAttributes = function(updates, options) {
- if (options instanceof Array) {
- options = { fields: options }
- }
- this.set(updates)
- return this.save(options)
- }
- DAO.prototype.setAttributes = function(updates) {
- this.set(updates)
- }
- DAO.prototype.destroy = function(options) {
- options = options || {}
- options.force = options.force === undefined ? false : Boolean(options.force)
- var self = this
- , query = null
- return new Utils.CustomEventEmitter(function(emitter) {
- self.daoFactory.runHooks(self.daoFactory.options.hooks.beforeDestroy, self, function(err) {
- if (!!err) {
- return emitter.emit('error', err)
- }
- if (self.Model._timestampAttributes.deletedAt && options.force === false) {
- self.dataValues[self.Model._timestampAttributes.deletedAt] = new Date()
- query = self.save(options)
- } else {
- var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id };
- query = self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.__factory.tableName, self.__factory.options.schema), identifier, options)
- }
- query.on('sql', function(sql) {
- emitter.emit('sql', sql)
- })
- .error(function(err) {
- emitter.emit('error', err)
- })
- .success(function(results) {
- self.daoFactory.runHooks(self.daoFactory.options.hooks.afterDestroy, self, function(err) {
- if (!!err) {
- return emitter.emit('error', err)
- }
- emitter.emit('success', results)
- })
- })
- })
- }).run()
- }
- DAO.prototype.increment = function(fields, countOrOptions) {
- Utils.validateParameter(countOrOptions, Object, {
- optional: true,
- deprecated: 'number',
- deprecationWarning: "Increment expects an object as second parameter. Please pass the incrementor as option! ~> instance.increment(" + JSON.stringify(fields) + ", { by: " + countOrOptions + " })"
- })
- var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }
- , updatedAtAttr = this.Model._timestampAttributes.updatedAt
- , values = {}
- if (countOrOptions === undefined) {
- countOrOptions = { by: 1, transaction: null }
- } else if (typeof countOrOptions === 'number') {
- countOrOptions = { by: countOrOptions, transaction: null }
- }
- countOrOptions = Utils._.extend({
- by: 1,
- attributes: {}
- }, countOrOptions)
- if (Utils._.isString(fields)) {
- values[fields] = countOrOptions.by
- } else if (Utils._.isArray(fields)) {
- Utils._.each(fields, function (field) {
- values[field] = countOrOptions.by
- })
- } else { // Assume fields is key-value pairs
- values = fields
- }
- if (updatedAtAttr && !values[updatedAtAttr]) {
- countOrOptions.attributes[updatedAtAttr] = Utils.now(this.daoFactory.daoFactoryManager.sequelize.options.dialect)
- }
- return this.QueryInterface.increment(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory.tableName, this.__factory.options.schema), values, identifier, countOrOptions)
- }
- DAO.prototype.decrement = function (fields, countOrOptions) {
- Utils.validateParameter(countOrOptions, Object, {
- optional: true,
- deprecated: 'number',
- deprecationWarning: "Decrement expects an object as second parameter. Please pass the decrementor as option! ~> instance.decrement(" + JSON.stringify(fields) + ", { by: " + countOrOptions + " })"
- })
- if (countOrOptions === undefined) {
- countOrOptions = { by: 1, transaction: null }
- } else if (typeof countOrOptions === 'number') {
- countOrOptions = { by: countOrOptions, transaction: null }
- }
- if (countOrOptions.by === undefined) {
- countOrOptions.by = 1
- }
- if (!Utils._.isString(fields) && !Utils._.isArray(fields)) { // Assume fields is key-value pairs
- Utils._.each(fields, function (value, field) {
- fields[field] = -value
- })
- }
- countOrOptions.by = 0 - countOrOptions.by
- return this.increment(fields, countOrOptions)
- }
- DAO.prototype.equals = function(other) {
- var result = true
- Utils._.each(this.dataValues, function(value, key) {
- if(Utils._.isDate(value) && Utils._.isDate(other[key])) {
- result = result && (value.getTime() == other[key].getTime())
- } else {
- result = result && (value == other[key])
- }
- })
- return result
- }
- DAO.prototype.equalsOneOf = function(others) {
- var result = false
- , self = this
- others.forEach(function(other) { result = result || self.equals(other) })
- return result
- }
- DAO.prototype.setValidators = function(attribute, validators) {
- this.validators[attribute] = validators
- }
- DAO.prototype.toJSON = function() {
- return this.get();
- }
- // private
- var initValues = 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 },
- key;
- values = values && _.clone(values) || {}
- if (options.isNewRecord) {
- if (this.hasDefaultValues) {
- Utils._.each(this.defaultValues, function(valueFn, key) {
- if (!defaults.hasOwnProperty(key)) {
- defaults[key] = valueFn()
- }
- })
- }
- if (this.Model._timestampAttributes.createdAt && defaults[this.Model._timestampAttributes.createdAt]) {
- this.dataValues[this.Model._timestampAttributes.createdAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.createdAt]);
- delete defaults[this.Model._timestampAttributes.createdAt];
- }
- if (this.Model._timestampAttributes.updatedAt && defaults[this.Model._timestampAttributes.updatedAt]) {
- this.dataValues[this.Model._timestampAttributes.updatedAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.updatedAt]);
- delete defaults[this.Model._timestampAttributes.updatedAt];
- }
- if (this.Model._timestampAttributes.createdAt && defaults[this.Model._timestampAttributes.deletedAt]) {
- this.dataValues[this.Model._timestampAttributes.deletedAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.deletedAt]);
- delete defaults[this.Model._timestampAttributes.deletedAt];
- }
- }
- if (Object.keys(defaults).length) {
- for (key in defaults) {
- if (!values.hasOwnProperty(key)) {
- values[key] = Utils.toDefaultValue(defaults[key])
- }
- }
- }
- this.set(values, options)
- }
- return DAO
- })()
|