Skip to content

Commit

Permalink
fix(model): destroying paranoid models with custom deletedAt (#11255)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabegorelick authored and sushantdhiman committed Jul 31, 2019
1 parent c32ac01 commit d041e77
Show file tree
Hide file tree
Showing 4 changed files with 384 additions and 30 deletions.
30 changes: 10 additions & 20 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -4052,29 +4052,19 @@ class Model {
const where = this.where(true);

if (this.constructor._timestampAttributes.deletedAt && options.force === false) {
const attribute = this.constructor.rawAttributes[this.constructor._timestampAttributes.deletedAt];
const field = attribute.field || this.constructor._timestampAttributes.deletedAt;
const values = Utils.mapValueFieldNames(this.dataValues, this.changed() || [], this.constructor);

values[field] = new Date();
where[field] = Object.prototype.hasOwnProperty.call(attribute, 'defaultValue')
const attributeName = this.constructor._timestampAttributes.deletedAt;
const attribute = this.constructor.rawAttributes[attributeName];
const defaultValue = Object.prototype.hasOwnProperty.call(attribute, 'defaultValue')
? attribute.defaultValue
: null;
const currentValue = this.getDataValue(attributeName);
const undefinedOrNull = currentValue == null && defaultValue == null;
if (undefinedOrNull || _.isEqual(currentValue, defaultValue)) {
// only update timestamp if it wasn't already set
this.setDataValue(attributeName, new Date());
}

this.setDataValue(field, values[field]);

return this.constructor.QueryInterface.update(
this, this.constructor.getTableName(options), values, where, _.defaults({ hooks: false, model: this.constructor }, options)
).then(([results, rowsUpdated]) => {
if (this.constructor._versionAttribute && rowsUpdated < 1) {
throw new sequelizeErrors.OptimisticLockError({
modelName: this.constructor.name,
values,
where
});
}
return results;
});
return this.save(_.defaults({ hooks: false }, options));
}
return this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, Object.assign({ type: QueryTypes.DELETE, limit: null }, options));
}).tap(() => {
Expand Down
104 changes: 104 additions & 0 deletions test/integration/instance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,5 +685,109 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
expect(user.username).to.equal('Peter');
});
});

it('supports custom deletedAt field', function() {
const ParanoidUser = this.sequelize.define('ParanoidUser', {
username: DataTypes.STRING,
destroyTime: DataTypes.DATE
}, { paranoid: true, deletedAt: 'destroyTime' });

return ParanoidUser.sync({ force: true }).then(() => {
return ParanoidUser.create({
username: 'username'
});
}).then(user => {
return user.destroy();
}).then(user => {
expect(user.destroyTime).to.be.ok;
expect(user.deletedAt).to.not.be.ok;
return user.restore();
}).then(user => {
expect(user.destroyTime).to.not.be.ok;
return ParanoidUser.findOne({ where: { username: 'username' } });
}).then(user => {
expect(user).to.be.ok;
expect(user.destroyTime).to.not.be.ok;
expect(user.deletedAt).to.not.be.ok;
});
});

it('supports custom deletedAt field name', function() {
const ParanoidUser = this.sequelize.define('ParanoidUser', {
username: DataTypes.STRING,
deletedAt: { type: DataTypes.DATE, field: 'deleted_at' }
}, { paranoid: true });

return ParanoidUser.sync({ force: true }).then(() => {
return ParanoidUser.create({
username: 'username'
});
}).then(user => {
return user.destroy();
}).then(user => {
expect(user.dataValues.deletedAt).to.be.ok;
expect(user.dataValues.deleted_at).to.not.be.ok;
return user.restore();
}).then(user => {
expect(user.dataValues.deletedAt).to.not.be.ok;
expect(user.dataValues.deleted_at).to.not.be.ok;
return ParanoidUser.findOne({ where: { username: 'username' } });
}).then(user => {
expect(user).to.be.ok;
expect(user.deletedAt).to.not.be.ok;
expect(user.deleted_at).to.not.be.ok;
});
});

it('supports custom deletedAt field and database column', function() {
const ParanoidUser = this.sequelize.define('ParanoidUser', {
username: DataTypes.STRING,
destroyTime: { type: DataTypes.DATE, field: 'destroy_time' }
}, { paranoid: true, deletedAt: 'destroyTime' });

return ParanoidUser.sync({ force: true }).then(() => {
return ParanoidUser.create({
username: 'username'
});
}).then(user => {
return user.destroy();
}).then(user => {
expect(user.dataValues.destroyTime).to.be.ok;
expect(user.dataValues.deletedAt).to.not.be.ok;
expect(user.dataValues.destroy_time).to.not.be.ok;
return user.restore();
}).then(user => {
expect(user.dataValues.destroyTime).to.not.be.ok;
expect(user.dataValues.destroy_time).to.not.be.ok;
return ParanoidUser.findOne({ where: { username: 'username' } });
}).then(user => {
expect(user).to.be.ok;
expect(user.destroyTime).to.not.be.ok;
expect(user.destroy_time).to.not.be.ok;
});
});

it('supports custom default value', function() {
const ParanoidUser = this.sequelize.define('ParanoidUser', {
username: DataTypes.STRING,
deletedAt: { type: DataTypes.DATE, defaultValue: new Date(0) }
}, { paranoid: true });

return ParanoidUser.sync({ force: true }).then(() => {
return ParanoidUser.create({
username: 'username'
});
}).then(user => {
return user.destroy();
}).then(user => {
return user.restore();
}).then(user => {
expect(user.dataValues.deletedAt.toISOString()).to.equal(new Date(0).toISOString());
return ParanoidUser.findOne({ where: { username: 'username' } });
}).then(user => {
expect(user).to.be.ok;
expect(user.deletedAt.toISOString()).to.equal(new Date(0).toISOString());
});
});
});
});
Loading

0 comments on commit d041e77

Please sign in to comment.