Add more relation

So far we have created two models and one relationship. Let us create another relationship for these two models.
Just add this object to relatedRelations of city:

citiesByPopulation: {
  type: "multiple" as RelationDataType,
  limit: 50,
  sort: {
    field: "population",
    order: "desc" as RelationSortOrderType,
  },
}

With this relationship, we plan to store the 50 most populated cities of each country in an embedded form.
Now the cityRelations object should look like this:

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,
        },
      },
      citiesByPopulation: {
        type: "multiple" as RelationDataType,
        limit: 50,
        sort: {
          field: "population",
          order: "desc" as RelationSortOrderType,
        },
      },
    },
  },
};

And we also need to change the function we wrote to add cities:

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,
          citiesByPopulation: true,
        },
      },
    },
  });
};

We just add this line of code:

  citiesByPopulation: true,

Let us add other relationships before testing this code.

Add arbitrary relation

Let's choose a city as the capital for the countries. For this purpose we must have a single relatedRelation for each country selectively. So we add this code:

capital: {
  type: "single" as RelationDataType,
},

And we change the function and validation we wrote to add the city as follows:

const addCityValidator = () => {
  return object({
    set: object({
      ...countryCityPure,
      country: objectIdValidation,
      isCapital: boolean(),
    }),
    get: coreApp.schemas.selectStruct("city", 1),
  });
};

const addCity: ActFn = async (body) => {
  const { country, name, population, abb, isCapital } = body.details.set;

  return await cities.insertOne({
    doc: { name, population, abb },
    projection: body.details.get,
    relations: {
      country: {
        _ids: new ObjectId(country),
        relatedRelations: {
          cities: true,
          citiesByPopulation: true,
          capital: isCapital,
        },
      },
    },
  });
};

We just add isCapital: boolean(), to addCityValidator and add capital: isCapital to the insertOne functions.

Run the code

All the code we have written so far is as follows (You can also see and download this code from here):

import {
  ActFn,
  boolean,
  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,
        },
      },
      citiesByPopulation: {
        type: "multiple" as RelationDataType,
        limit: 50,
        sort: {
          field: "population",
          order: "desc" as RelationSortOrderType,
        },
      },
      capital: {
        type: "single" as RelationDataType,
      },
    },
  },
};

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,
      isCapital: boolean(),
    }),
    get: coreApp.schemas.selectStruct("city", 1),
  });
};

const addCity: ActFn = async (body) => {
  const { country, name, population, abb, isCapital } = body.details.set;

  return await cities.insertOne({
    doc: { name, population, abb },
    projection: body.details.get,
    relations: {
      country: {
        _ids: new ObjectId(country),
        relatedRelations: {
          cities: true,
          citiesByPopulation: true,
          capital: isCapital,
        },
      },
    },
  });
};

coreApp.acts.setAct({
  schema: "city",
  actName: "addCity",
  validator: addCityValidator(),
  fn: addCity,
});

coreApp.runServer({ port: 1366, typeGeneration: true, playground: true });

If you run the code and go to the playground, you will see that a new input called isCapital has been added to add the city, capital-field

and if we put the value true in it, this city will be added to the country as the new capital.

In addition, we have a field called citiesByPopulation in the country, where the 50 most populated cities of the country are stored. population-city

Please note that you only send a request for a new city, and the new city is stored in three different fields with different conditions in the schema of the corresponding country.

Add E2E Test

In following to adding requests to the E2E test section, For adding addCity request to E2E section you should click on the E2E button, like below picture.

e2e sequence

Like before, you should change the country id of the addCity request. In the addCity sequence, you should delete the country id :

e2e sequence

And put the variable name that you set in the capture in addCountry sequence , in my example, {IranId}.

e2e sequence

Add many-to-many relationship

Let us add a new model named user for this purpose. We add the following code for the user model:

const userPure = {
  name: string(),
  age: number(),
};

const users = coreApp.odm.newModel("user", userPure, {
  livedCities: {
    optional: false,
    schemaName: "city",
    type: "multiple",
    sort: {
      field: "_id",
      order: "desc",
    },
    relatedRelations: {
      users: {
        type: "multiple",
        limit: 50,
        sort: {
          field: "_id",
          order: "desc",
        },
      },
    },
  },

  country: {
    optional: false,
    schemaName: "country",
    type: "single",
    relatedRelations: {
      users: {
        type: "multiple",
        limit: 50,
        sort: {
          field: "_id",
          order: "desc",
        },
      },
    },
  },
});

Well, in the code above, we have created a new model called user, which has two pure fields with name and age keys. In addition, it has a relationship with the country and the city. Its relationship with the country is single and there is a relatedRelation with the country with a field called users. But its relationship with the city is multiple by the livedCities key, and there is also a relatedRelation with the city with a field called users, which is also multiple. Therefore, the relationship between the city and the user is many-to-many.
The function we want to add a user is as follows:

const addUserValidator = () => {
  return object({
    set: object({
      ...userPure,
      country: objectIdValidation,
      livedCities: array(objectIdValidation),
    }),
    get: coreApp.schemas.selectStruct("user", 1),
  });
};
const addUser: ActFn = async (body) => {
  const { country, livedCities, name, age } = body.details.set;
  const obIdLivedCities = livedCities.map((lc: string) => new ObjectId(lc));

  return await users.insertOne({
    doc: { name, age },
    projection: body.details.get,
    relations: {
      country: {
        _ids: new ObjectId(country),
        relatedRelations: {
          users: true,
        },
      },
      livedCities: {
        _ids: obIdLivedCities,
        relatedRelations: {
          users: true,
        },
      },
    },
  });
};
coreApp.acts.setAct({
  schema: "user",
  actName: "addUser",
  validator: addUserValidator(),
  fn: addUser,
});

We need to import array function from lesan

The only thing worth mentioning in the code above is the livedCities input in validation, which receives an array of IDs as a string. In the Act function, we convert this input into an array of object IDs with a map. Note that the _ids key in the livedCities object receives an array of object IDs.

Well, let's see the complete code again, run the software and check the outputs.

All codes

You can see and download this code from here

import {
  ActFn,
  array,
  boolean,
  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,
        },
      },
      citiesByPopulation: {
        type: "multiple" as RelationDataType,
        limit: 50,
        sort: {
          field: "population",
          order: "desc" as RelationSortOrderType,
        },
      },
      capital: {
        type: "single" as RelationDataType,
      },
    },
  },
};

const cities = coreApp.odm.newModel("city", countryCityPure, cityRelations);

const userPure = {
  name: string(),
  age: number(),
};

const users = coreApp.odm.newModel("user", userPure, {
  livedCities: {
    optional: false,
    schemaName: "city",
    type: "multiple",
    sort: {
      field: "_id",
      order: "desc",
    },
    relatedRelations: {
      users: {
        type: "multiple",
        limit: 5,
        sort: {
          field: "_id",
          order: "desc",
        },
      },
    },
  },

  country: {
    optional: false,
    schemaName: "country",
    type: "single",
    relatedRelations: {
      users: {
        type: "multiple",
        limit: 5,
        sort: {
          field: "_id",
          order: "desc",
        },
      },
    },
  },
});

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,
      isCapital: boolean(),
    }),
    get: coreApp.schemas.selectStruct("city", 1),
  });
};

const addCity: ActFn = async (body) => {
  const { country, name, population, abb, isCapital } = body.details.set;

  return await cities.insertOne({
    doc: { name, population, abb },
    projection: body.details.get,
    relations: {
      country: {
        _ids: new ObjectId(country),
        relatedRelations: {
          cities: true,
          citiesByPopulation: true,
          capital: isCapital,
        },
      },
    },
  });
};

coreApp.acts.setAct({
  schema: "city",
  actName: "addCity",
  validator: addCityValidator(),
  fn: addCity,
});

const addUserValidator = () => {
  return object({
    set: object({
      ...userPure,
      country: objectIdValidation,
      livedCities: array(objectIdValidation),
    }),
    get: coreApp.schemas.selectStruct("user", 1),
  });
};
const addUser: ActFn = async (body) => {
  const { country, livedCities, name, age } = body.details.set;
  const obIdLivedCities = livedCities.map((lc: string) => new ObjectId(lc));

  return await users.insertOne({
    doc: { name, age },
    projection: body.details.get,
    relations: {
      country: {
        _ids: new ObjectId(country),
        relatedRelations: {
          users: true,
        },
      },
      livedCities: {
        _ids: obIdLivedCities,
        relatedRelations: {
          users: true,
        },
      },
    },
  });
};
coreApp.acts.setAct({
  schema: "user",
  actName: "addUser",
  validator: addUserValidator(),
  fn: addUser,
});

coreApp.runServer({ port: 1366, typeGeneration: true, playground: true });

Open Playground in the browser and go to addUser function.
add-user

Note that the livedCities field receives an array of IDs, you just need to enter an input like ["65466c407123faa9c1f3c180", "65466c2c7123faa9c1f3c17e"]. playground parses it and converts it into an array suitable for sending.

Add E2E Test

Probably you know what to do! For adding addUser request to E2E section you should click on the E2E button, like below picture.

e2e sequence

And like before, you should change the country id and this time, change the city id. Of course that first you should add capture to addCity sequences.

In my example, i add the Hamedan city and Tehran city to my user. so , in the addCity sequence of Hamedan, i click on the Add Capture button and fill the inputs like below picture.

e2e sequence

And in the addCity sequence of Tehran, i click on the Add Capture button and fill the inputs like below picture.

e2e sequence

Then, in the addUser sequence, you can see the curent country id and also lived cities id like below picture.

e2e sequence

Only thing you do is just to replace the country id and lived cities id with country id and cities id that you set in the capture sequences. in my example, country id is {IranId} and also my first city id is {HamedanId} and second one is {TehranId}. like below picture.

e2e sequence

And last thing you should done is that add capture to addUser sequence for using in feature. i click on the Add Captue button and fill the inputs like below picture.

e2e sequence