openrecord
Version:
Active record like ORM for nodejs
253 lines (218 loc) • 7.32 kB
JavaScript
const debug = require('debug')('openrecord:save')
/*
* RECORD
*/
exports.record = {
_create_or_update: function(options) {
var self = this
if (this.__chainedModel) {
var transaction = this.__chainedModel.getInternal('transaction')
if (transaction) options.transaction = transaction
}
return this.store.startTransaction(options, function() {
return self
.callInterceptors('beforeSave', [self, options])
.then(function() {
if (self.__exists) return self._update(options)
return self._create(options)
})
.then(function() {
return self.callInterceptors('afterSave', [self, options])
})
})
},
_update: function(options) {
const Store = require('../../store')
var self = this
var query = this.definition.query(options)
var primaryKeys = this.definition.primaryKeys
var condition = {}
for (var i = 0; i < primaryKeys.length; i++) {
condition[primaryKeys[i]] = this[primaryKeys[i]]
}
return this.callInterceptors('beforeUpdate', [self, options]).then(
function() {
var values = {}
var changes = self.getChangedValues()
var hasChanges = false
for (var name in self.model.definition.attributes) {
var attr = self.model.definition.attributes[name]
if (attr.persistent && changes.hasOwnProperty(name)) {
// if attribute has `getChangedValues` (e.g. composite type)
if (
changes[name] &&
typeof changes[name].getChangedValues === 'function' &&
changes[name].definition
) {
var subchanges = changes[name].getChangedValues()
for (var subname in subchanges) {
if (subchanges.hasOwnProperty(subname)) {
values[name + '.' + subname] = changes[name].definition.cast(
subname,
subchanges[subname],
'write',
changes[name]
)
hasChanges = true
}
}
} else {
values[name] = self.model.definition.cast(
name,
changes[name],
'write',
self
)
hasChanges = true
}
}
}
var afterUpdate = function() {
return self.callInterceptors('afterUpdate', [self, options])
}
// call afterUpdate hook even there was nothing to save for the current record!
if (!hasChanges) return afterUpdate()
return query
.where(condition)
.update(values)
.catch(function(error) {
debug(query.toString())
throw new Store.SQLError(error)
})
.then(function(result) {
debug(query.toString())
return afterUpdate()
})
}
)
},
_create: function(options) {
const Store = require('../../store')
var self = this
var query = this.definition.query(options)
var primaryKeys = this.definition.primaryKeys
var primaryKey = primaryKeys[0]
return this.callInterceptors('beforeCreate', [self, options]).then(
function() {
var values = {}
var changes = self.getChangedValues()
for (var name in self.model.definition.attributes) {
var attr = self.model.definition.attributes[name]
if (attr.persistent && changes.hasOwnProperty(name)) {
values[name] = self.model.definition.cast(
name,
changes[name],
'write',
self
)
}
}
if (primaryKey && self.definition.store.supportsReturning) {
query.returning(primaryKey)
}
return query
.insert(values)
.catch(function(error) {
debug(query.toString())
throw new Store.SQLError(error)
})
.then(function(result) {
debug(query.toString())
var idTmp = {}
idTmp[primaryKey] = result[0]
self._exists({ relations: false, changes: false })
self.set(idTmp, 'read')
return self.callInterceptors('afterCreate', [self, options])
})
}
)
}
}
exports.model = {
/**
* Updates all records which match the conditions. beforeSave, afterSave, beforeUpdate and afterUpdate want be called!
* @class Model
* @method updateAll
* @alias update
* @return {Promise}
*/
update: function(attributes, options) {
return this.updateAll(attributes, options)
},
updateAll: function(attributes, options) {
const Store = require('../../store')
var self = this.chain()
var limit = self.getInternal('limit')
var offset = self.getInternal('offset')
var joins = self.getInternal('joins')
var singleResult = self.getInternal('single_result')
var query = self.query(options)
var findQuery = query
var withSubquery = false
if(!singleResult && (limit || offset || joins)){ // other possible query options?
if(self.definition.primaryKeys.length > 1) throw new Error('Updates with limit, offset, joins ... are only supported for tables with a single primary key!')
withSubquery = true
}
if(withSubquery){
findQuery = self.query(Object.assign({force: true}, options))
}
var attrs = {}
Object.keys(attributes).forEach(function(attribute) {
var field = self.store.toInternalAttributeName(attribute)
attrs[field] = attributes[attribute]
})
return self
.callInterceptors('beforeFind', [findQuery])
.then(function() {
if(withSubquery){
query.whereIn(self.definition.primaryKeys[0], findQuery.select(self.definition.primaryKeys))
}
return query.update(attrs)
})
.catch(function(error) {
debug(query.toString())
throw new Store.SQLError(error)
})
.then(function(response) {
debug(query.toString())
})
}
}
/*
* CHAIN
*/
exports.chain = {
// create multiple records at once
save: function save(options) {
const self = this
options = options || {}
if (self.options.polymorph) return this.callParent(options)
return Promise.all(
this.map(function(record) {
// validate all records at once.
return record.validate()
})
)
.then(function() {
// if validation succeeded, start the transaction
return self.store.startTransaction(options, function() {
return self
._runLazyOperation(options)
.then(function() {
// inside the transaction create all new records and afterwards save all the others
return self._create(options)
})
.then(function() {
// will save all existing records with changes...
return self.callParent(options, save)
})
})
})
.then(function() {
return self
})
},
_create: function() {
return Promise.resolve() // mysql and sqlite do not support multi INSERT with returning. Postgres will overwrite this method
}
}