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

Commit 1c597126 by Seth Samuel

Undestroy method for paranoid models

1 parent c6982f2c
...@@ -239,8 +239,9 @@ module.exports = (function() { ...@@ -239,8 +239,9 @@ module.exports = (function() {
*/ */
updateQuery: function(tableName, attrValueHash, where, options, attributes) { updateQuery: function(tableName, attrValueHash, where, options, attributes) {
options = options || {}; options = options || {};
_.defaults(options, this.options);
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options); attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
var query var query
, values = []; , values = [];
...@@ -1024,7 +1025,7 @@ module.exports = (function() { ...@@ -1024,7 +1025,7 @@ module.exports = (function() {
} }
} }
joinOn = self.quoteTable(tableLeft) + '.' + self.quoteIdentifier(attrLeft); joinOn = self.quoteTable(tableLeft) + '.' + self.quoteIdentifier(attrLeft);
} }
joinOn += ' = ' + self.quoteTable(tableRight) + '.' + self.quoteIdentifier(attrRight); joinOn += ' = ' + self.quoteTable(tableRight) + '.' + self.quoteIdentifier(attrRight);
......
...@@ -4,7 +4,8 @@ var Utils = require('../../utils') ...@@ -4,7 +4,8 @@ var Utils = require('../../utils')
, DataTypes = require('../../data-types') , DataTypes = require('../../data-types')
, SqlString = require('../../sql-string') , SqlString = require('../../sql-string')
, Transaction = require('../../transaction') , Transaction = require('../../transaction')
, util = require('util'); , util = require('util')
, _ = require("lodash");
var MySqlQueryGenerator = Utils._.extend( var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require('../abstract/query-generator')), Utils._.clone(require('../abstract/query-generator')),
...@@ -212,7 +213,10 @@ module.exports = (function() { ...@@ -212,7 +213,10 @@ module.exports = (function() {
}, },
updateQuery: function(tableName, attrValueHash, where, options) { updateQuery: function(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options); options = options || {};
_.defaults(options, this.options);
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>" var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
, values = []; , values = [];
......
...@@ -759,6 +759,46 @@ module.exports = (function() { ...@@ -759,6 +759,46 @@ module.exports = (function() {
}; };
/** /**
* Undestroy the row corresponding to this instance. Only available for paranoid models.
*
* @param {Object} [options={}]
*
* @return {Promise<undefined>}
*/
Instance.prototype.undestroy = function(options) {
options = Utils._.extend({
hooks: true,
force: false
}, options || {});
var self = this;
// This semi awkward syntax where we can't return the chain directly but have to return the last .then() call is to allow sql proxying
return Promise.try(function() {
// Run before hook
if (options.hooks) {
return self.Model.runHooks('beforeUndestroy', self, options);
}
}).then(function() {
var identifier;
if (!self.Model._timestampAttributes.deletedAt) {
return Promise.reject(new Error("Model is not paranoid"));
} else {
self.dataValues[self.Model._timestampAttributes.deletedAt] = null;
return self.save(_.extend(_.clone(options), {hooks : false, omitNull : false}));
}
}).tap(function(result) {
// Run after hook
if (options.hooks) {
return self.Model.runHooks('afterUndestroy', self, options);
}
}).then(function(result) {
return result;
});
};
/**
* Increment the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The increment is done using a * Increment the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The increment is done using a
* ```sql * ```sql
* SET column = column + X * SET column = column + X
......
...@@ -1389,6 +1389,72 @@ module.exports = (function() { ...@@ -1389,6 +1389,72 @@ module.exports = (function() {
}; };
/** /**
* Undestroy multiple instances if `paranoid` is enabled.
*
* @param {Object} [options.where] Filter the undestroy
* @param {Boolean} [options.hooks=true] Run before / after bulk undestroy hooks?
* @param {Boolean} [options.individualHooks=false] If set to true, undestroy will find all records within the where parameter and will execute before / after bulkUndestroy hooks on each row
* @param {Number} [options.limit] How many rows to undelete
*
* @return {Promise<undefined>}
*/
Model.prototype.undestroy = function(options) {
options = Utils._.extend({
hooks: true,
individualHooks: false
}, options || {});
options.type = QueryTypes.BULKUNDELETE;
var self = this
, instances;
mapFieldNames.call(this, options, this);
return Promise.try(function() {
// Run before hook
if (options.hooks) {
return self.runHooks('beforeBulkUndestroy', options);
}
}).then(function() {
// Get daos and run beforeDestroy hook on each record individually
if (options.individualHooks) {
return self.findAll({where: options.where}, {transaction: options.transaction}).map(function(instance) {
return self.runHooks('beforeUndestroy', instance, options).then(function() {
return instance;
});
}).then(function(_instances) {
instances = _instances;
});
}
}).then(function() {
// Run undelete query
if (!self._timestampAttributes.deletedAt) {
return Promise.reject(new Error("Model is not paranoid"));
} else {
var attrValueHash = {};
attrValueHash[self._timestampAttributes.deletedAt] = null;
options.omitNull = false;
return self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHash, options.where, options, self._timestampAttributes.deletedAt);
}
}).tap(function() {
// Run afterDestroy hook on each record individually
if (options.individualHooks) {
return Promise.map(instances, function(instance) {
return self.runHooks('afterUndestroy', instance, options);
});
}
}).tap(function() {
// Run after hook
if (options.hooks) {
return self.runHooks('afterBulkUndestroy', options);
}
}).then(function(affectedRows) {
return affectedRows;
});
};
/**
* Update multiple instances that match the where options. The promise returns an array with one or two elements. The first element is always the number * Update multiple instances that match the where options. The promise returns an array with one or two elements. The first element is always the number
* of affected rows, while the second element is the actual affected rows (only supported in postgres with `options.returning` true.) * of affected rows, while the second element is the actual affected rows (only supported in postgres with `options.returning` true.)
* *
......
...@@ -707,14 +707,14 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -707,14 +707,14 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
sequelize.transaction().then(function(t) { sequelize.transaction().then(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() { User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.findOrInitialize({ User.findOrInitialize({
where: {username: 'foo'} where: {username: 'foo'}
}).spread(function(user1) { }).spread(function(user1) {
User.findOrInitialize({ User.findOrInitialize({
where: {username: 'foo'}, where: {username: 'foo'},
transaction: t transaction: t
}).spread(function(user2) { }).spread(function(user2) {
User.findOrInitialize({ User.findOrInitialize({
where: {username: 'foo'}, where: {username: 'foo'},
defaults: { foo: 'asd' }, defaults: { foo: 'asd' },
transaction: t transaction: t
...@@ -1304,6 +1304,58 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1304,6 +1304,58 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
describe("undestroy", function(){
it("returns an error if the model is not paranoid", function(done){
var self = this;
this.User.create({username : "Peter", secretValue : "42"})
.success(function(user){
user.destroy()
.success(function(){
self.User.undestroy({where : {username : "Peter"}})
.success(function(){
done("Should not return success for unparanoid model");
})
.error(function(err){
done()
})
})
})
})
it("restores a previously deleted model", function(done){
var self = this
, ParanoidUser = self.sequelize.define('ParanoidUser', {
username: Sequelize.STRING,
secretValue: Sequelize.STRING,
data: Sequelize.STRING,
intVal: { type: Sequelize.INTEGER, defaultValue: 1}
}, {
paranoid: true
})
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
ParanoidUser.sync({ force: true }).success(function() {
ParanoidUser.bulkCreate(data).success(function() {
ParanoidUser.destroy({where: {secretValue: '42'}})
.success(function() {
ParanoidUser.undestroy({where : {secretValue: '42'}})
.success(function() {
ParanoidUser.find({where : {secretValue : "42"}})
.success(function(user){
expect(user).to.be.ok
expect(user.username).to.equal("Peter")
done()
})
})
})
})
})
})
})
describe('equals', function() { describe('equals', function() {
it("correctly determines equality of objects", function(done) { it("correctly determines equality of objects", function(done) {
this.User.create({username: 'hallo', data: 'welt'}).success(function(u) { this.User.create({username: 'hallo', data: 'welt'}).success(function(u) {
......
...@@ -1797,4 +1797,59 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -1797,4 +1797,59 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
}) })
}) })
}) })
describe("undestroy", function(){
it("returns an error if the model is not paranoid", function(done){
var self = this;
this.User.create({username : "Peter", secretValue : "42"})
.success(function(user){
user.destroy()
.success(function(){
user.undestroy()
.success(function(){
done("Should not return success for unparanoid model");
})
.error(function(err){
done()
})
})
})
})
it("restores a previously deleted model", function(done){
var self = this
, ParanoidUser = self.sequelize.define('ParanoidUser', {
username: DataTypes.STRING,
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: { type: DataTypes.INTEGER, defaultValue: 1}
}, {
paranoid: true
})
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
ParanoidUser.sync({ force: true }).success(function() {
ParanoidUser.bulkCreate(data).success(function() {
ParanoidUser.find({where : {secretValue : "42"}})
.success(function(user){
user.destroy()
.success(function() {
user.undestroy()
.success(function() {
ParanoidUser.find({where : {secretValue : "42"}})
.success(function(user){
expect(user).to.be.ok
expect(user.username).to.equal("Peter")
done()
})
})
})
})
})
})
})
})
}) })
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!