Relational Select

In the previous section, we explored how to perform select on a single entity.

However, relational databases are known for connecting entities with relations, such that we can perform queries across different entities.

For example, given a bakery, we can find all the chefs working there.

Suppose the following code were run before, inserting the bakery and the chefs it employed into the database.

#![allow(unused)]
fn main() {
let la_boulangerie = bakery::ActiveModel {
    name: ActiveValue::Set("La Boulangerie".to_owned()),
    profit_margin: ActiveValue::Set(0.0),
    ..Default::default()
};
let bakery_res = Bakery::insert(la_boulangerie).exec(db).await?;

for chef_name in ["Jolie", "Charles", "Madeleine", "Frederic"] {
    let chef = chef::ActiveModel {
        name: ActiveValue::Set(chef_name.to_owned()),
        bakery_id: ActiveValue::Set(bakery_res.last_insert_id),
        ..Default::default()
    };
    Chef::insert(chef).exec(db).await?;
}
}

There are 4 chefs working at the bakery La Boulangerie, and we can find them later on as follows:

#![allow(unused)]
fn main() {
// First find *La Boulangerie* as a Model
let la_boulangerie: bakery::Model = Bakery::find_by_id(bakery_res.last_insert_id)
    .one(db)
    .await?
    .unwrap();

let chefs: Vec<chef::Model> = la_boulangerie.find_related(Chef).all(db).await?;
let mut chef_names: Vec<String> = chefs.into_iter().map(|b| b.name).collect();
chef_names.sort_unstable();

assert_eq!(chef_names, ["Charles", "Frederic", "Jolie", "Madeleine"]);
}

As new bakeries open in the town, it would be inefficient to do find_related on each of the bakeries.

#![allow(unused)]
fn main() {
    // Inserting two bakeries and their chefs
    let la_boulangerie = bakery::ActiveModel {
        name: ActiveValue::Set("La Boulangerie".to_owned()),
        profit_margin: ActiveValue::Set(0.0),
        ..Default::default()
    };
    let bakery_res = Bakery::insert(la_boulangerie).exec(db).await?;
    for chef_name in ["Jolie", "Charles", "Madeleine", "Frederic"] {
        let chef = chef::ActiveModel {
            name: ActiveValue::Set(chef_name.to_owned()),
            bakery_id: ActiveValue::Set(bakery_res.last_insert_id),
            ..Default::default()
        };
        Chef::insert(chef).exec(db).await?;
    }
    let la_id = bakery_res.last_insert_id;

    let arte_by_padaria = bakery::ActiveModel {
        name: ActiveValue::Set("Arte by Padaria".to_owned()),
        profit_margin: ActiveValue::Set(0.2),
        ..Default::default()
    };
    let bakery_res = Bakery::insert(arte_by_padaria).exec(db).await?;
    for chef_name in ["Brian", "Charles", "Kate", "Samantha"] {
        let chef = chef::ActiveModel {
            name: ActiveValue::Set(chef_name.to_owned()),
            bakery_id: ActiveValue::Set(bakery_res.last_insert_id),
            ..Default::default()
        };
        Chef::insert(chef).exec(db).await?;
    }
    let arte_id = bakery_res.last_insert_id;

    // would then need two sets of find_related to find 
}

We can utilize a loader to do the heavy lifting for us.

#![allow(unused)]
fn main() {
    // First find bakeries as Models
    let bakeries: Vec<bakery::Model> = Bakery::find()
        .filter(
            Condition::any()
                .add(bakery::Column::Id.eq(la_id))
                .add(bakery::Column::Id.eq(arte_id))
        )
        .all(db)
        .await?;

    // Then use loader to load the chefs in one query.
    let chefs: Vec<Vec<chef::Model>> = bakeries.load_many(Chef, db).await?;
    let mut la_chef_names: Vec<String> = chefs[0].to_owned().into_iter().map(|b| b.name).collect();
    la_chef_names.sort_unstable();
    let mut arte_chef_names: Vec<String> = chefs[1].to_owned().into_iter().map(|b| b.name).collect();
    arte_chef_names.sort_unstable();

    assert_eq!(la_chef_names, ["Charles", "Frederic", "Jolie", "Madeleine"]);
    assert_eq!(arte_chef_names, ["Brian", "Charles", "Kate", "Samantha"]);
}

Using a loader can greatly reduce the traffic of the database.

For more advanced usage, visit the documentation.