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)