DEV Community

Cover image for 41-Nodejs Course 2023: Working With Models data
Hasan Zohdy
Hasan Zohdy

Posted on

41-Nodejs Course 2023: Working With Models data

So we made such an amazing progress with the query builder and our crud model, but we still so far didn't work with the Model's concept itself, which is working the a single collection document.

Reminder about Models

Models are the way we can work with a single document in a collection, and we can use them to modify the document's data by updating, unset-ing, replacing or even destroying the document, what differs from the CrudModel operations is that Crud operations works from data coming from outside, unlike the model which will perform over its internal document.

Let's see an example to illustrate it

// src/app/users/routes.ts
import User from './models/user';

// in the set time out method

const user = new User();

// now we' just created an empty model, with no data at all 

const userWithData = new User({
  name: 'hasan',
  age: 23,
  email: 'hassanzohdy@gmail.com'
});

// now we have a model with data, but it's not saved yet

// let's save it

await userWithData.save();

// we can then change the data and re-save it

userWithData.set('name', 'Ali');

await userWithData.save();
Enter fullscreen mode Exit fullscreen mode

Now you can see what i meant by working with models internally, we can create a model with data, and then save it, and then change the data and save it again, and so on.

Model Data And Original Data

So we are receiving the document's data in the constructor, which holds the current data of the model either by passing it from our create method or by creating a new model instance directly, this where we'll call it the originalData of the model, and we'll use it to compare the current data with the original data to know if we need to update the document or not.

Model Class

Now let's use our Model class to manage the data, if you remember we set the constructor in the BaseModel let's move it to our model class.

// src/core/database/model/model.ts
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

export default abstract class Model extends CrudModel {
  /**
   * Current data
   */
  public data: Partial<ModelDocument> = {};

  /**
   * Constructor
   */
  public constructor(public originalData: Partial<ModelDocument> = {}) {
    super();
    this.data = { ...this.originalData };
  }
}
Enter fullscreen mode Exit fullscreen mode

What we did here we received the model data and stored it in originalData property, and also we assigned it to the data property, so we can use it to modify the data, and then we can compare it with the originalData to know if we need to update the document or not.

calling super in the constructor is important, because we are extending the CrudModel class, and we need to call the constructor of the parent class, that's how inheritance works in Javascript.

Model Methods

Now, let's think what kind of methods we can use to update the model's data.

  • set: to set a value to a key.
  • unset: to remove a key or more from the data.
  • replaceWith: to replace the entire data with the given ones.
  • merge: to merge the given data with the current data.
  • get: to get a value from the data, or return default value if key not found.
  • has: to check if a key exists in the data.
  • except: to get all data except the given keys.
  • only: to get only the given keys from the data.
  • save: to save the data to the database.

Now let's start implementing them.

Set

The set method receives two parameters, the key and the value, and it will set the value to the key in the data.

The key can be written in dot.notation syntax as well.

// src/core/database/model/model.ts
import { set } from "@mongez/reinforcements";
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

export default abstract class Model extends CrudModel {
  /**
   * Current data
   */
  public data: Partial<ModelDocument> = {};

  /**
   * Constructor
   */
  public constructor(public originalData: Partial<ModelDocument> = {}) {
    super();
    this.data = { ...this.originalData };
  }

  /**
   * Set the value of the given key to the data list
   */
  public set(key: string, value?: any) {
    set(this.data, key, value);

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

We added a method called set it receives two parameters, the key and the value, and it will set the value to the key in the data.

We used set utility to set the key deeply in the given object.

Let's see an example of usage:

import User from './models/user';

const user = new User();

user.set('name', 'hasan').set('address.city', 'Cairo');

console.log(user.data); // { name: 'hasan', address: { city: 'Cairo' } }
Enter fullscreen mode Exit fullscreen mode

Unset

The unset method receives one or more keys, and it will remove them from the data.

// src/core/database/model/model.ts
import { except, set } from "@mongez/reinforcements";
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

export default abstract class Model extends CrudModel {
  /**
   * Current data
   */
  public data: Partial<ModelDocument> = {};

  /**
   * Constructor
   */
  public constructor(public originalData: Partial<ModelDocument> = {}) {
    super();
    this.data = { ...this.originalData };
  }

  /**
   * Set the value of the given key to the data list
   */
  public set(key: string, value?: any) {
    set(this.data, key, value);

    return this;
  }

  /**
   * Unset the given keys from the data
   */
  public unset(...keys: string[]) {
    this.data = except(this.data, keys);

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

We added a method called unset it receives one or more keys, and it will remove them from the data.

We used except utility to remove the given keys from the object.

Let's see an example of usage:

import User from './models/user';

const user = new User();

user.set('name', 'hasan').set('address.city', 'Cairo');

console.log(user.data); // { name: 'hasan', address: { city: 'Cairo' } }

user.unset('name', 'address.city');

console.log(user.data); // {}
Enter fullscreen mode Exit fullscreen mode

Replace

The replaceWith method receives the new data, and it will replace the current data with the new one.

// src/core/database/model/model.ts
import { except, set } from "@mongez/reinforcements";
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

export default abstract class Model extends CrudModel {
  // ..

  /**
   * Replace the data with the given data entirely
   */
  public replaceWith(data: Partial<ModelDocument>) {
    const newData = { ...data };

    // if the given data has no id but current model has one, then inject it in the data
    if (!newData.id && this.data.id) {
      newData.id = this.data.id;
    }

    // if the given data has no _id but current model has one, then inject it in the data
    if (!newData._id && this.data._id) {
      newData._id = this.data._id;
    }

    this.data = newData;

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

We added a method called replace it receives the new data, and it will replace the current data with the new one.

We used the spread operator to clone the given data, and then we checked if the given data has no id but the current model has one, then we injected it in the data, and the same for _id.

Let's see an example of usage:

import User from './models/user';

const user = new User();

user.set('name', 'hasan').set('address.city', 'Cairo');

console.log(user.data); // { name: 'hasan', address: { city: 'Cairo' } }

user.replace({ name: 'Hassan' });

console.log(user.data); // { name: 'Hassan' }
Enter fullscreen mode Exit fullscreen mode

Merge

The merge method receives the new data, and it will merge the current data with the new one.

// src/core/database/model/model.ts
import { except, merge, set } from "@mongez/reinforcements";
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

export default abstract class Model extends CrudModel {
  // ..
  /**
   * Merge the data with the given object
   */
  public merge(data: Document) {
    this.data = merge(this.data, data);

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

We added a method called merge it receives the new data, and it will merge the current data with the new one.

We used merge utility to merge the given data with the current data, it will have a one additional feature than using the spread operator, it will merge the objects deeply.

Let's see an example of usage:

import User from './models/user';

const user = new User();

user.set('name', 'hasan').set('address.city', 'Cairo');

console.log(user.data); // { name: 'hasan', address: { city: 'Cairo' } }

user.merge({ name: 'Hassan', address: { country: 'Egypt' } });

console.log(user.data); // { name: 'Hassan', address: { city: 'Cairo', country: 'Egypt' } }
Enter fullscreen mode Exit fullscreen mode

Get

The get method receives one parameter, the key, and it will return the value of the given key.

// src/core/database/model/model.ts
import { except, get, merge, set } from "@mongez/reinforcements";
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

export default abstract class Model extends CrudModel {
  // ..
  /**
   * Get the value of the given key or return default value if key does not exist
   */
  public get(key: string, defaultValue: any = null) {
    return get(this.data, key, defaultValue);
  }
}
Enter fullscreen mode Exit fullscreen mode

We added a method called get it receives one parameter, the key, and it will return the value of the given key.

We used get utility to get the value of the given key.

Let's see an example of usage:

import User from './models/user';

const user = new User();

user.set('name', 'hasan').set('address.city', 'Cairo');

console.log(user.get('name')); // 'hasan'
console.log(user.get('address.city')); // 'Cairo'
console.log(user.get('address.country', 'Egypt')); // 'Egypt'
Enter fullscreen mode Exit fullscreen mode

Has

The has method receives one parameter, the key, and it will return true if the given key exists in the data, otherwise it will return false.

// src/core/database/model/model.ts
import { except, get, merge, set } from "@mongez/reinforcements";
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

const MISSING_KEY = Symbol("MISSING_KEY");

export default abstract class Model extends CrudModel {
  // ..

  /**
   * Check if the given key exists in the data
   */
  public has(key: string) {
    return this.get(key, MISSING_KEY) === MISSING_KEY;
  }
}
Enter fullscreen mode Exit fullscreen mode

We used here something kind of new in our code base, we used a symbol to represent a missing key, and we used it to check if the given key exists in the data or not.

Let's see an example of usage:

import User from './models/user';

const user = new User();

user.set('name', 'hasan').set('address.city', 'Cairo');

console.log(user.has('name')); // true
console.log(user.has('address.country')); // false
Enter fullscreen mode Exit fullscreen mode

Except

The except method receives one parameter, the keys, and it will return a new object without the given keys.

// src/core/database/model/model.ts
import { except, get, merge, set } from "@mongez/reinforcements";
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

const MISSING_KEY = Symbol("MISSING_KEY");

export default abstract class Model extends CrudModel {
  // ..
  /**
   * Get a new object without the given keys
   */
  public except(keys: string[]) {
    return except(this.data, keys);
  }
}
Enter fullscreen mode Exit fullscreen mode

We added a method called except it receives one parameter, the keys, and it will return a new object without the given keys.

We used except utility to get a new object without the given keys.

Let's see an example of usage:

import User from './models/user';

const user = new User();

user.set('name', 'hasan').set('address.city', 'Cairo');

console.log(user.except(['name'])); // { address: { city: 'Cairo' } }
Enter fullscreen mode Exit fullscreen mode

Only

The only method receives one parameter, the keys, and it will return a new object with only the given keys.

// src/core/database/model/model.ts
import { except, get, merge, only, set } from "@mongez/reinforcements";
import CrudModel from "./curd-model";
import { Document, ModelDocument } from "./types";

const MISSING_KEY = Symbol("MISSING_KEY");

export default abstract class Model extends CrudModel {
  // ..
  /**
   * Get a new object with only the given keys
   */
  public only(keys: string[]) {
    return only(this.data, keys);
  }
}
Enter fullscreen mode Exit fullscreen mode

We added a method called only it receives one parameter, the keys, and it will return a new object with only the given keys.

We used only utility to get a new object with only the given keys.

Let's see an example of usage:

import User from './models/user';

const user = new User();

user.set('name', 'hasan').set('address.city', 'Cairo');

console.log(user.only(['name'])); // { name: 'hasan' }
Enter fullscreen mode Exit fullscreen mode

🎨 Conclusion

In this article, we made a quick recap about the concept of Models as a model it works with only one single document, we also defined a new property called originalData which will hold the original data of the document, and we added some methods to the model to make it easier to work with the data.

In our next article, we'll see the save method and how it will work with the database.

🚀 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

Packages & Libraries

React Js Packages

Courses (Articles)

Top comments (0)