49-Nodejs Course 2023: Database Models: Dirty Columns
Hasan Zohdy
Posted on November 7, 2022
As we are now done with casting, there is a catch here, we don't need to make any cast to any column, unless the column's value has changed, so we need to know which columns have changed, and this is what we call dirty columns.
Dirty Columns
Dirty columns are the columns that have changed, so we need to know which columns have changed, and this is what we call dirty columns.
Data are changed through multiple methods, set
, replace
and merge
methods.
We also have the originalData
property that holds the original data, so we can compare the original data with the current data to know which columns have changed.
// src/core/database/model/model.ts
import {
areEqual,
except,
get,
merge,
only,
set,
} from "@mongez/reinforcements";
/**
* Check if the given column is a dirty column
*/
public isDirty(column: string) {
return areEqual(get(this.originalData, column), get(this.data, column));
}
Here we checked for the column in both originalData
and data
and compared them, if they are equal, then the column is not dirty, otherwise, it is dirty.
We used areEqual
function from @mongez/reinforcements
package, it is a deep equal function, so it will compare the values deeply either objects, arrays, strings and so on.
But what about the new models, the original data are the same as the current data, so we need to check also if the model is relatively new one or not, if so then all columns are dirty.
// src/core/database/model/model.ts
/**
* Check if the given column is a dirty column
*/
public isDirty(column: string) {
if (this.isNewModel()) return true;
return areEqual(get(this.originalData, column), get(this.data, column));
}
We used isNewModel
method to check if the model is new or not, if so then all columns are dirty.
Now let's implement the 'isNewModel' method.
// src/core/database/model/model.ts
/**
* Check if the model is new or not
*/
public isNewModel() {
return !this.data._id || (this.data._id && this.isRestored);
}
Actually, we can also use it in the save method as well.
/**
* 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.isNewModel()) {
// 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();
this.castData();
await queryBuilder.update(
this.getCollectionName(),
{
_id: this.data._id,
},
this.data,
);
} else {
// creating a new document in the database
const generateNextId =
this.getStaticProperty("generateNextId").bind(Model);
// check for default values and merge it with the data
this.checkDefaultValues();
// 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.castData();
this.data = await queryBuilder.create(
this.getCollectionName(),
this.data,
);
}
}
Now we'll go back to the cast method and check if the column is a dirty column, if so then we'll cast it.
// src/core/database/model/model.ts
/**
* Cast data before saving
*/
protected castData() {
for (const column in this.casts) {
if (!this.isDirty(column)) continue;
let value = this.get(column);
if (value === undefined) continue;
const castType = this.casts[column];
if (typeof castType === "function") {
value = castType(column, value, this);
} else {
value = this.castValue(value, castType);
}
this.set(column, value);
}
}
And That's it.
Update the original data on save
The original data is being captured from the constructor, but we need to replace it again once the user perform a save so we can make it the latest stable data for the current model.
// 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.isNewModel()) {
// 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();
this.castData();
await queryBuilder.update(
this.getCollectionName(),
{
_id: this.data._id,
},
this.data,
);
} else {
// creating a new document in the database
const generateNextId =
this.getStaticProperty("generateNextId").bind(Model);
// check for default values and merge it with the data
this.checkDefaultValues();
// 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.castData();
this.data = await queryBuilder.create(
this.getCollectionName(),
this.data,
);
}
// update the original data to equal current data
this.originalData = this.data;
}
Keeping Initial Data
What if we want to keep the very first data that were injected in the model? well, in that case we can create a new property called initialData
and assign it the value of the data that were injected in the constructor.
// src/core/database/model/model.ts
export default abstract class Model extends CrudModel {
/**
* Model Document Initial data
*/
public initialData: Partial<ModelDocument> = {};
/**
* Constructor
*/
public constructor(public originalData: Partial<ModelDocument> = {}) {
//
super();
this.data = { ...this.originalData };
this.initialData = { ...this.originalData };
}
In that sense, now the initial value is kept in our model untouched.
🎨 Conclusion
In this lesson, we learned how to implement dirty columns, and how to check if a column is dirty or not.
We also made an update to the save method to update the original data once the user perform a save so if we want to change the data again after the save method, we can now tell that this column is dirty or not after the last save.
🚀 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)
Posted on November 7, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.