49-Nodejs Course 2023: Database Models: Dirty Columns
Tags: node, typescript, mongodb, fastify
Published: 2022-11-07 12:38:41

---
title: 49-Nodejs Course 2023: Database Models: Dirty Columns
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kzbl4m8jcrv2bsh7vcof.png
# Use a ratio of 100:42 for best results.
---

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.

```ts
// 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.

```ts
// 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.

```ts
// 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.

```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,
      );
    }
  }
```

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.

```ts
// 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.

```ts
// 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.

```ts
// 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](https://github.com/hassanzohdy/nodejs-2023)

## 😍 Join our community

Join our community on [Discord](https://discord.gg/pb2vmdfhGf) 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](https://www.youtube.com/playlist?list=PLGO8ntvxgiZMJc7RN2lIq9WmMOlWZGzmz), 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](https://dev.to/hassanzohdy/event-driven-architecture-the-best-paradigm-that-i-love-to-work-with-in-javascript-and-node-js-1gnk)
- [Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript](https://dev.to/hassanzohdy/best-practices-for-case-styles-camel-pascal-snake-and-kebab-case-in-node-and-javascript-55oi)
- [After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
](https://dev.to/hassanzohdy/after-6-years-of-practicing-mongodb-here-are-my-thoughts-on-mongodb-vs-mysql-574b)

Packages & Libraries

- [Collections: Your ultimate Javascript Arrays Manager](https://dev.to/hassanzohdy/collections-your-ultimate-javascript-array-handler-3o15)
- [Supportive Is: an elegant utility to check types of values in JavaScript](https://dev.to/hassanzohdy/supportive-is-an-elegant-utility-to-check-types-of-values-in-javascript-1b3e)
- [Localization: An agnostic i18n package to manage localization in your project](https://dev.to/hassanzohdy/mongez-localization-the-simplest-way-to-translate-your-website-regardless-your-favorite-framework-4gi3)

 React Js Packages

- [useFetcher: easiest way to fetch data in React Js](https://dev.to/hassanzohdy/usefetcher-the-easiest-way-to-fetch-data-in-react-45o9)

Courses (Articles)

- [React Js: Let"s Create File Manager With React Js and Node Js](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)