We've proceeded with a nice progress in the saving process, but we still have a problem, what if we want to delete a document from the database but we actually don't want to delete it?
Well, we can use the recycle bin
concept, we can just move the document to the recycle bin and then we can restore it.
Recycle Bin
Here is how it works, when we call the delete
method, we'll actually not deleting it, but we'll move it to another collection which will be called collectionNameTrash
where collectionName
is the name of the collection we want to delete the document from.
For example if we want to delete a document from the users
collection, we'll move it to the usersTrash
collection.
Now let's try it in the destroy
method first.
// src/core/database/model/model.ts
/**
* Delete the document form database
*/
public async destroy() {
if (!this.data._id) return;
// Trash The data
this.data.deletedAt = new Date();
// create a copy of the current data in the collectionTrash
// create a copy of the current data in the collectionTrash
await queryBuilder.create(this.getCollectionName() + "Trash", {
document: this.data,
id: this.data.id,
_id: this.data._id,
});
// perform the actual delete
await queryBuilder.deleteOne(this.getCollectionName(), {
_id: this.data._id,
});
}
We performed here before the actual delete from the database a new query to create a copy of the document in the trash collection which will be added in the Model's collection name with the Trash
suffix.
The content that we added in the collection, contains the entire document data, the id and the _id
of the document.
But why would we add the id and the _id
of the document?
Well, we'll need them later to restore the document.
Restoring The Document
Now let's go to the restoring process, we'll create a new method called restore
which will be a static method that can be called directly from the model class itself.
// src/core/database/model/crud-model.ts
// ...
/**
* Update model by the given id
*/
public static async restore<T>(
this: ChildModel<T>,
id: PrimaryIdType,
): Promise<T | null> {
// find the document in the trash collection
const document = await queryBuilder.first(this.collectionName + "Trash", {
[this.primaryIdColumn]: id,
});
// if no document, then it means there is no document in the trash
// table with that id
if (!document) return null;
// if we got here, create a new model and inject to it
// the document object
const model = this.self(document.document);
// add restoredAt column with current date
model.set("restoredAt", new Date());
// perform save
await model.save();
// return the model
return model;
}
The restore method receives one parameter, the id of the document that we want to restore from the trash, if the document exists in the trash collection, get it.
Now if the document exists, create a new model, inject the document
which contains the entire data of the deleted document, after that add the restoredAt
column with the current date.
Now we can perform the save, and return the model.
But there is a catch here, remember in the save
method we have a condition that checks if the document has an _id
or not, if it has an _id
then it means that the document already exists in the database, so we'll perform an update, but if it doesn't have an _id
then we'll perform an insert.
So we need to remove the _id
from the document before we perform the save, so we'll do that in the restore
method.
So we'll add another condition to check if it is being restored.
We'll created a property called isRestored
and method to mark the model as restored.
// src/core/database/model/model.ts
// ...
/**
* A flag to determine whether the model is restored or not
*/
protected isRestored = false;
/**
* Mark the model as restored
*/
public markAsRestored() {
this.isRestored = true;
return this;
}
Now let's call it in the restore
method.
// src/core/database/model/crud-model.ts
// ...
/**
* Update model by the given id
*/
public static async restore<T>(
this: ChildModel<T>,
id: PrimaryIdType,
): Promise<T | null> {
// find the document in the trash collection
const document = await queryBuilder.first(this.collectionName + "Trash", {
[this.primaryIdColumn]: id,
});
// if no document, then it means there is no document in the trash
// table with that id
if (!document) return null;
// if we got here, create a new model and inject to it
// the document object
const model = this.self(document.document);
// add restoredAt column with current date
model.set("restoredAt", new Date());
// mark the model as restored
model.markAsRestored();
// perform save
await model.save();
// return the model
return model;
}
Now let's go to the save
method and add the condition.
// src/core/database/model/model.ts
/**
* Perform saving operation either by updating or creating a new record in database
*/
public async save(mergedData: Document = {}) {
this.merge(mergedData);
// check if the data contains the primary id column
// but is not being restored
if (this.data._id && !this.isRestored) {
// perform an update operation
// check if the data has changed
// if not changed, then do not do anything
if (areEqual(this.originalData, this.data)) return;
this.data.updatedAt = new Date();
await queryBuilder.update(
this.getCollectionName(),
{
_id: this.data._id,
},
this.data,
);
// unset the isRestored flag
if (this.isRestored) {
this.isRestored = false;
}
} else {
// creating a new document in the database
const generateNextId =
this.getStaticProperty("generateNextId").bind(Model);
// if the column does not exist, then create it
if (!this.data.id) {
this.data.id = await generateNextId();
}
const now = new Date();
// if the column does not exist, then create it
if (!this.data.createdAt) {
this.data.createdAt = now;
}
// if the column does not exist, then create it
if (!this.data.updatedAt) {
this.data.updatedAt = now;
}
this.data = await queryBuilder.create(
this.getCollectionName(),
this.data,
);
}
}
And that's it, now we can restore the document.
import User from './models/users';
const user = await User.create({
name: 'John Doe',
});
await user.destroy();
const restoredUser = await User.restore(user.data.id);
console.log(restoredUser.data); // { id: 1, name: 'John Doe', createdAt: '2021-01-01', updatedAt: '2021-01-01', restoredAt: '2021-01-01', deletedAt: '2021-01-01', _id: '5ff5c1c0e4b0e8b8b8b8b8b8' }
And that's it!
🎨 Conclusion
In this article, we learned how to implement the recycle bin and move the model to it and learnt how to restore it again.
🚀 Project Repository
You can find the latest updates of this project on Github
😍 Join our community
Join our community on Discord to get help and support (Node Js 2023 Channel).
🎞️ Video Course (Arabic Voice)
If you want to learn this course in video format, you can find it on Youtube, the course is in Arabic language.
💰 Bonus Content 💰
You may have a look at these articles, it will definitely boost your knowledge and productivity.
General Topics
- Event Driven Architecture: A Practical Guide in Javascript
- Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript
- After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
Packages & Libraries
- Collections: Your ultimate Javascript Arrays Manager
- Supportive Is: an elegant utility to check types of values in JavaScript
- Localization: An agnostic i18n package to manage localization in your project
React Js Packages
Courses (Articles)
Top comments (0)