The addRelation Function
In addition to the usual CRUD functions, every model exposes two relation-management functions: addRelation and removeRelation. Use them to change the relations of an existing document. This tutorial covers addRelation โ both the many-to-many and the one-to-many cases.
Update a many-to-many relationโ
The most common case: add one or more cities to the livedCities of an existing user.
const addUserLivedCityValidator = () => {
return object({
set: object({
_id: objectIdValidation,
livedCities: array(objectIdValidation),
}),
get: coreApp.schemas.selectStruct("user", 1),
});
};
const addUserLivedCity: ActFn = async (body) => {
const { livedCities, _id } = body.details.set;
const obIdLivedCities = livedCities.map((lc: string) => new ObjectId(lc));
return await users.addRelation({
filters: { _id: new ObjectId(_id) },
projection: body.details.get,
relations: {
livedCities: {
_ids: obIdLivedCities,
relatedRelations: {
users: true,
},
},
},
});
};
coreApp.acts.setAct({
schema: "user",
actName: "addUserLivedCities",
validator: addUserLivedCityValidator(),
fn: addUserLivedCity,
});
addRelation takes an object with these keys:
filtersโ a MongoDBfindOnefilter that selects the single document whose relations we change.relationsโ the relation input, exactly like the one we used ininsertOne(see Add a new act with a relation). Keys are this model's relation names; each value has_idsand an optionalrelatedRelationsmap.projectionโ shapes the written data, same as everywhere else.replace(optional) โ a boolean. Defaults tofalse; we use it below for single relations.
In the act above, the user id and an array of city ids come in through validation. We convert the string ids to ObjectId, then hand everything to addRelation. On the user side, the cities are added to livedCities; on the city side, the user is added to each city's users list. Both sides, one call.
Update a one-to-many (single) relationโ
What if the relation field is an object instead of an array โ a single relation like the country on a user? Look at this:
const addUserCountryValidator = () => {
return object({
set: object({
_id: objectIdValidation,
country: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("user", 1),
});
};
const addUserCountry: ActFn = async (body) => {
const { country, _id } = body.details.set;
return await users.addRelation({
filters: { _id: new ObjectId(_id) },
projection: body.details.get,
relations: {
country: {
_ids: new ObjectId(country),
relatedRelations: {
users: true,
},
},
},
replace: true,
});
};
coreApp.acts.setAct({
schema: "user",
actName: "addUserCountry",
validator: addUserCountryValidator(),
fn: addUserCountry,
});
Because country is not optional and is defined with type: "single", changing a user's country means removing the user from the old country's users list and adding them to the new one. That's why we pass replace: true. If replace is false (or omitted) while a value already exists, Lesan refuses the operation rather than silently leaving both sides out of sync.
What happens under the hoodโ
The bottom line is a bit involved, but it's all automatic:
- Find the user.
- Find the user's old country.
- Find the user's new country.
- Check whether the user appears in any list of the old country (a country can keep several lists โ e.g.
cities,citiesByPopulation,users). - Build a command to remove the user from every list they were found in.
- If a list has a
limitand we've reached it, find the next documents that qualify and build the commands to refill, unify, sort, and trim the list. - Build a command to add the user to all relevant lists of the new country (again honoring any
limit). - Run all the accumulated commands.
- Run the command that stores the new country in the user.
The next three lessons (findOneAndUpdate, deleteOne) show the same engine at work in other directions.
Run the codeโ
The complete runnable example is examples/document/05-1-add-relation-fn-1.ts (many-to-many) and examples/document/05-1-add-relation-fn-2.ts (one-to-many) in the Lesan repository.
Run the code and open the playground โ you'll see addUserLivedCities and addUserCountry under main โ user, and you can test them against the data created in the previous tutorials.
Before running addUserLivedCities:
While running:
After running โ the city is now in livedCities:
For addUserCountry (the single relation with replace: true), the same before/executing/after flow applies:
Next stepsโ
- The
removeRelationfunction โ undoing links (and removing optional single relations)