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();
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 };
}
}
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 theCrudModel
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;
}
}
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' } }
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;
}
}
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); // {}
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;
}
}
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' }
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;
}
}
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' } }
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);
}
}
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'
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;
}
}
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
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);
}
}
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' } }
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);
}
}
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' }
🎨 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
- 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)