So we saw how we can save our data in the model, but we need to add more details about the saving process like when the document has been created and when it has been updated.
So let's do it.
Create Method
In our static class CrudModel we've the create and update methods, the create method generates a new id before saving the data and returns a new instance of the model.
But what about if we reverse it? we create a new instance with the data and then we just save it!
// src/core/database/model/crud-model.ts
export default abstract class CrudModel extends BaseModel {
// ...
/**
* Create a new record in the database for the current model (child class of this one)
* and return a new instance of it with the created data and the new generated id
*/
public static async create<T>(
this: ChildModel<T>,
data: Document,
): Promise<T> {
const model = this.self(data);
await model.save();
return model;
}
}
See, much cleaner and easier, also it will prevent code duplicate as we don't have to generate the id in both methods in the create and save methods.
Update Method
Let's update the update method as well
// src/core/database/model/crud-model.ts
export default abstract class CrudModel extends BaseModel {
// ...
/**
* Update model by the given id and return it if found
*/
public static async update<T>(
this: ChildModel<T>,
id: PrimaryIdType,
data: Document,
): Promise<T | null> {
const model = (await this.find(id)) as any; // silent typescript compiler
if (!model) return null;
await model.save(data);
return model;
}
}
We first get the model by the given id.
If the model is not found, then we return null.
Otherwise, we update the model with the given data and return it.
Replace Method
Let's do the same with the replace method
/**
* Replace the entire document for the given document id with the given new data
*/
public static async replace<T>(
this: ChildModel<T>,
id: PrimaryIdType,
data: Document,
): Promise<T | null> {
const model = (await this.find(id)) as any;
if (!model) return null;
model.replaceWith(data);
await model.save();
return model;
}
We replaced the data using the replaceWith method and then we save it.
Upsert method
Now let's update the upsert method
/**
* Find and update the document for the given filter with the given data or create a new document/record
* if filter has no matching
*/
public static async upsert<T>(
this: ChildModel<T>,
filter: ModelDocument,
data: Document,
): Promise<T | null> {
let model = (await this.first(filter)) as any;
// if not exists, create it
if (!model) {
model = this.self({ ...filter, ...data });
} else {
// update it
model.replaceWith({ ...filter, ...data });
}
await model.save();
return model;
}
Here is how it works, we first get the first matched model for the given filter, if the model is not found, then we create a new one with the given filter and data.
Otherwise, we update the model with the given data.
Recapping what we've done
So basically we transformed all of the static operations to act as if we were working with the model directly, this will allow us to put all updates in one place which is the save method only.
Created At And Updated At
Now we need to add the createdAt and updatedAt fields to the model, so let's do it.
The createdAt will be added when the model is being saved as creating, and the updatedAt will be updated every time the model is updated and in the create process as well.
// 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
if (this.data._id) {
// 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;
// update the updated at column
this.data.updatedAt = new Date();
await queryBuilder.update(
this.getCollectionName(),
{
_id: this.data._id,
},
this.data,
);
} else {
const generateNextId =
this.getStaticProperty("generateNextId").bind(Model);
this.data.id = await generateNextId();
// createdAt Column
const now = new Date();
this.data.createdAt = now;
// updatedAt Column
this.data.updatedAt = now;
this.data = await queryBuilder.create(
this.getCollectionName(),
this.data,
);
}
}
So we added the createdAt and updatedAt columns in the save method, and we also updated the update method to update the updatedAt column.
🎨 Conclusion
So we've added the createdAt and updatedAt columns to the model, and we've also updated the save method to update the updatedAt column.
We also moved all insertion and updating operations to the save method only.
🚀 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)