Add relation
As we said before, we embed all relationships. So when you define a relation we store the pure fields of both models in each other.
So far we have defined only one model, let us add the second model.
We add the following code to the previous codes to add a new model called city
.
const cityPure = {
name: string(),
population: number(),
abb: string(),
};
const cityRelations = {
country: {
optional: false,
schemaName: "country",
type: "single" as RelationDataType,
relatedRelations: {
cities: {
type: "multiple" as RelationDataType,
limit: 50,
sort: {
field: "_id",
order: "desc" as RelationSortOrderType,
},
},
},
},
};
const cities = coreApp.odm.newModel("city", cityPure, cityRelations);
We talked about newModel
and its input here.
Now we only talk about the third input, the relation definition. which receives an object with string key which is the feild name and we store the relation data by this name inside each document, and TRelation
value (relations: Record<string, TRelation>;
) which gave us some metadata
about the relation. The TRelation
type is as follows:
export type RelationDataType = "single" | "multiple";
export type RelationSortOrderType = "asc" | "desc";
export type TRelatedRelation = {
type: RelationDataType;
limit?: null | number;
sort?: {
field: string;
order: RelationSortOrderType;
};
};
interface TRelation {
schemaName: string;
type: RelationDataType;
optional: boolean;
sort?: {
field: string;
order: RelationSortOrderType;
};
relatedRelations: {
[key: string]: TRelatedRelation;
};
}
- The
schemaName
key receives the exact name of anotherschema
to establish a relation. - The
type
key specifies whether the relationship type should besingle
ormultiple
. - The
optional
key specifies whether or not it is mandatory to enter information about this relationship when importing a new document. - The
sort
key is optional and specifies that if the relationship type ismultiple
, based on whichfield
of the relationship schema, the information should be arranged. This key receives an object with two keys:- The
field
that receives the name of one of the schema fields in the relation. - An
order
that receives the value ofasc
anddesc
and specifies whether the arrangement should be from bottom to top or vice versa.
- The
relatedRelations
key that specifies the effects of this schema on the other side of the relationship. This key receives the value of the object with the following keys:- The
type
key specifies whether the relationship type should besingle
ormultiple
. limit
which specifies the number of relations to be kept if the relationtype
ismultiple
.- The
sort
key is optional and specifies that if the relationship type ismultiple
, based on whichfield
of the relationship schema, the information should be arranged. This key receives an object with two keys:- The
field
that receives the name of one of the schema fields in the relation. - An
order
that receives the value ofasc
anddesc
and specifies whether the arrangement should be from bottom to top or vice versa.
- The
- The
Add new Act
with relation
Let us define an Act
for this new model. We add the following code for this purpose:
const addCityValidator = () => {
return object({
set: object({
...cityPure,
country: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("city", 1),
});
};
const addCity: ActFn = async (body) => {
const { country, name, population, abb } = body.details.set;
return await cities.insertOne({
doc: { name, population, abb },
projection: body.details.get,
relations: {
country: {
_ids: new ObjectId(country),
relatedRelations: {
cities: true,
},
},
},
});
};
coreApp.acts.setAct({
schema: "city",
actName: "addCity",
validator: addCityValidator(),
fn: addCity,
});
We need to import
objectIdValidation
andObjectId
fromlesan
We see Validator
and Act
and setAct
and insertOne
functions before, Here we are only talking about the relations
input in the insert function. The type of this input is as follow:
export type TInsertRelations<T extends IRelationsFileds> = {
[mainKey in keyof T]?: {
_ids: ObjectId | ObjectId[];
relatedRelations?: {
[key in keyof T[mainKey]["relatedRelations"]]: boolean;
};
};
};
This input receives an object with the key name of the relations that we have previously defined in the model. This object has the following keys:
ids
which receives either anObjectId
or an array ofObjectIds
.relatedRelations
, which receives an object with the key of the name of the related relations that we have previously defined in the model along with aboolean
value. If the value istrue
, in addition to the given relationship being saved in this new document, this created document is also saved in the related relationship. And if it isfalse
, the relationship will be saved only in this new document.
Here, by adding a city and giving the country ID associated with that city, we store both the pure fields of that country in this newly created city, and within that country in an array of objects, we also store the pure fields of this city.
All codes
Let's see all the code written here and run it. (You can also see and download this code from here)
import {
ActFn,
lesan,
MongoClient,
number,
object,
ObjectId,
objectIdValidation,
RelationDataType,
RelationSortOrderType,
string,
} from "https://deno.land/x/lesan@vx.x.x/mod.ts"; // Please replace `x.x.x` with the latest version in [releases](https://github.com/MiaadTeam/lesan/releases)
const coreApp = lesan();
const client = await new MongoClient("mongodb://127.0.0.1:27017/").connect();
const db = client.db("dbName"); // change dbName to the appropriate name for your project.
coreApp.odm.setDb(db);
const countryCityPure = {
name: string(),
population: number(),
abb: string(),
};
const countryRelations = {};
const countries = coreApp.odm.newModel(
"country",
countryCityPure,
countryRelations
);
const cityRelations = {
country: {
optional: false,
schemaName: "country",
type: "single" as RelationDataType,
relatedRelations: {
cities: {
type: "multiple" as RelationDataType,
limit: 50,
sort: {
field: "_id",
order: "desc" as RelationSortOrderType,
},
},
},
},
};
const cities = coreApp.odm.newModel("city", countryCityPure, cityRelations);
const addCountryValidator = () => {
return object({
set: object(countryCityPure),
get: coreApp.schemas.selectStruct("country", 1),
});
};
const addCountry: ActFn = async (body) => {
const { name, population, abb } = body.details.set;
return await countries.insertOne({
doc: {
name,
population,
abb,
},
projection: body.details.get,
});
};
coreApp.acts.setAct({
schema: "country",
actName: "addCountry",
validator: addCountryValidator(),
fn: addCountry,
});
const addCityValidator = () => {
return object({
set: object({
...countryCityPure,
country: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("city", 1),
});
};
const addCity: ActFn = async (body) => {
const { country, name, population, abb } = body.details.set;
return await cities.insertOne({
doc: { name, population, abb },
projection: body.details.get,
relations: {
country: {
_ids: new ObjectId(country),
relatedRelations: {
cities: true,
},
},
},
});
};
coreApp.acts.setAct({
schema: "city",
actName: "addCity",
validator: addCityValidator(),
fn: addCity,
});
coreApp.runServer({ port: 1366, typeGeneration: true, playground: true });
Now, by running this code and going to playgroun, you should see this page to add the country:
And to add a new city, you should see this page:
What exactly happened to the database? If you open MongoDB Compass, the following data should be stored for the country:
And the following data should be stored for the city:
As you can see, when you add a city, the pure values are stored as embedded on both sides of the relation. This makes receiving data much faster.
The only noteworthy point is that a limited number of cities are stored in the country. Try to save as many as you think you will need in the first paginate. To get the rest of the cities, we will also query their own schema.
Add E2E Test
Like before, you can click on the E2E button (like bottom picture) to add your request to E2E section.
Then, when you go to the E2E section, you can see 2 sequense that first one is addCountry
from getting start page and second one, is addCity
request.
In the first sequence, you can click on Add Capture button to show you two input to set variable and value:
Then, you should fill the inputs like bottom picture:
Finaly, In the second sequence, add city
, you should delete the country id :
And put the variable name that you set in the capture in the addCountry
sequence, in my example, {IranId}
.