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

Commit 787f3b3c by Jesse Atkinson Committed by Sushant

Add `isSoftDeleted` helper method to model instances (#7531)

* Add `isSoftDeleted` helper method to model instances

Since users can rename the `deletedAt` field when creating a model it
is helpful to have a `isSoftDeleted` so other users of the DB don't
have to hard code the custom `deletedAt` field name throughout their
codebase.

Addresses: https://github.com/sequelize/sequelize/issues/7408

* Update `isSoftDeleted` to check for dates in the future

If the soft-delete property (default is `deleted_at`) is set, but it's
set to a date in the future, then `isSoftDeleted` should return `false`.

* Update `isSoftDeleted` to return `true` if `deletedAt` is now

* Rename variable `isNotSet` to `isSet` and inverse logic

This helps with readability and understanding.
1 parent f7a6d3d4
# Future
- [ADDED] Add `isSoftDeleted` helper method to model instance [#7408](https://github.com/sequelize/sequelize/issues/7408)
- [FIXED] Map isolation level strings to tedious isolation level [MSSQL] [#7296](https://github.com/sequelize/sequelize/issues/7296)
- [FEATURE] `addConstraint`, `removeConstraint`, `showConstraint` [#7108](https://github.com/sequelize/sequelize/pull/7108)
- [ADDED] `addConstraint`, `removeConstraint`, `showConstraint` [#7108](https://github.com/sequelize/sequelize/pull/7108)
- [FIXED] `changeColumn` generates incorrect query with ENUM type [#7455](https://github.com/sequelize/sequelize/pull/7455)
- [ADDED] `options.alter` to sequelize.sync() to alter existing tables.[#537](https://github.com/sequelize/sequelize/issues/537)
- [ADDED] Ability to run transactions on a read-replica by marking transactions as read only [#7323](https://github.com/sequelize/sequelize/issues/7323)
......
......@@ -3747,6 +3747,38 @@ class Model {
}
/**
* Helper method to determine if a instance is "soft deleted". This is
* particularly useful if the implementer renamed the `deletedAt` attribute
* to something different. This method requires `paranoid` to be enabled.
*
* @returns {Boolean}
*/
isSoftDeleted() {
if (!this.constructor._timestampAttributes.deletedAt) {
throw new Error('Model is not paranoid');
}
const deletedAtAttribute = this.constructor.rawAttributes[this.constructor._timestampAttributes.deletedAt];
const defaultValue = deletedAtAttribute.hasOwnProperty('defaultValue') ? deletedAtAttribute.defaultValue : null;
const deletedAt = this.get(this.constructor._timestampAttributes.deletedAt);
const isSet = deletedAt !== defaultValue;
// No need to check the value of deletedAt if it's equal to the default
// value. If so return the inverse of `isNotSet` since we are asking if
// the model *is* soft-deleted.
if (!isSet) {
return isSet;
}
const now = moment();
const deletedAtIsInTheFuture = moment(deletedAt).isAfter(now);
// If deletedAt is a datetime in the future then the model is *not* soft-deleted.
// Therefore, return the inverse of `deletedAtIsInTheFuture`.
return !deletedAtIsInTheFuture;
}
/**
* Restore the row corresponding to this instance. Only available for paranoid models.
*
* @param {Object} [options={}]
......
......@@ -2076,6 +2076,66 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
});
});
describe('isSoftDeleted', () => {
beforeEach(function() {
this.ParanoidUser = this.sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, { paranoid: true });
return this.ParanoidUser.sync({ force: true });
});
it('returns false if user is not soft deleted', function() {
return this.ParanoidUser.create({ username: 'fnord' }).then(() => {
return this.ParanoidUser.findAll().then((users) => {
expect(users[0].isSoftDeleted()).to.be.false;
});
});
});
it('returns true if user is soft deleted', function() {
return this.ParanoidUser.create({ username: 'fnord' }).then(() => {
return this.ParanoidUser.findAll().then((users) => {
return users[0].destroy().then(() => {
expect(users[0].isSoftDeleted()).to.be.true;
return users[0].reload({ paranoid: false }).then((user) => {
expect(user.isSoftDeleted()).to.be.true;
});
});
});
});
});
it('works with custom `deletedAt` field name', function() {
const self = this;
this.ParanoidUserWithCustomDeletedAt = this.sequelize.define('ParanoidUserWithCustomDeletedAt', {
username: { type: DataTypes.STRING }
}, {
deletedAt: 'deletedAtThisTime',
paranoid: true
});
this.ParanoidUserWithCustomDeletedAt.hasOne(this.ParanoidUser);
return this.ParanoidUserWithCustomDeletedAt.sync({ force: true }).then(() => {
return this.ParanoidUserWithCustomDeletedAt.create({ username: 'fnord' }).then(() => {
return self.ParanoidUserWithCustomDeletedAt.findAll().then((users) => {
expect(users[0].isSoftDeleted()).to.be.false;
return users[0].destroy().then(() => {
expect(users[0].isSoftDeleted()).to.be.true;
return users[0].reload({ paranoid: false }).then((user) => {
expect(user.isSoftDeleted()).to.be.true;
});
});
});
});
});
});
});
describe('restore', () => {
it('returns an error if the model is not paranoid', function() {
return this.User.create({username: 'Peter', secretValue: '42'}).then(user => {
......
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require(__dirname + '/../support'),
current = Support.sequelize,
DataTypes = require(__dirname + '/../../../lib/data-types'),
Sequelize = Support.Sequelize,
moment = require('moment');
describe(Support.getTestDialectTeaser('Instance'), () => {
describe('isSoftDeleted', () => {
beforeEach(function() {
const User = current.define('User', {
name: DataTypes.STRING,
birthdate: DataTypes.DATE,
meta: DataTypes.JSON,
deletedAt: {
type: Sequelize.DATE
}
});
const ParanoidUser = current.define('User', {
name: DataTypes.STRING,
birthdate: DataTypes.DATE,
meta: DataTypes.JSON,
deletedAt: {
type: Sequelize.DATE
}
}, {
paranoid: true
});
this.paranoidUser = ParanoidUser.build({
name: 'a'
}, {
isNewRecord: false,
raw: true
});
this.user = User.build({
name: 'a'
}, {
isNewRecord: false,
raw: true
});
});
it('should not throw if paranoid is set to true', function() {
expect(() => {
this.paranoidUser.isSoftDeleted();
}).to.not.throw();
});
it('should throw if paranoid is set to false', function() {
expect(() => {
this.user.isSoftDeleted();
}).to.throw('Model is not paranoid');
});
it('should return false if the soft-delete property is the same as ' +
'the default value', function() {
this.paranoidUser.setDataValue('deletedAt', null);
expect(this.paranoidUser.isSoftDeleted()).to.be.false;
});
it('should return false if the soft-delete property is set to a date in ' +
'the future', function() {
this.paranoidUser.setDataValue('deletedAt', moment().add(5, 'days').format());
expect(this.paranoidUser.isSoftDeleted()).to.be.false;
});
it('should return true if the soft-delete property is set to a date ' +
'before now', function() {
this.paranoidUser.setDataValue('deletedAt', moment().subtract(5, 'days').format());
expect(this.paranoidUser.isSoftDeleted()).to.be.true;
});
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!