Many-to-many relationships

Ergol also supports many-to-many relationships. In order to do so, you need to use the #[many_to_many] attribute:

#![allow(unused)]
fn main() {
extern crate ergol;
use ergol::prelude::*;

#[ergol]
pub struct User {
    #[id] pub id: i32,
    #[unique] pub username: String,
    pub password: String,
    pub age: i32,
}

#[ergol]
pub struct Project {
    #[id] pub id: i32,
    pub name: String,
    #[many_to_many(visible_projects)] pub authorized_users: User,
}
}

The same way, you will have plenty of functions that you will be able to use to manage your objects:

extern crate tokio;
extern crate ergol;
use ergol::prelude::*;
#[ergol]
pub struct User {
    #[id] pub id: i32,
    #[unique] pub username: String,
    pub password: String,
    pub age: i32,
}
#[ergol]
pub struct Project {
    #[id] pub id: i32,
    pub name: String,
    #[many_to_many(visible_projects)] pub authorized_users: User,
}
#[tokio::main]
async fn main() -> Result<(), ergol::tokio_postgres::Error> {
    let (db, connection) = ergol::connect(
        "host=localhost user=ergol password=ergol dbname=ergol",
        ergol::tokio_postgres::NoTls,
    )
    .await?;
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });
Project::drop_table().execute(&db).await.ok();
User::drop_table().execute(&db).await.ok();
User::create_table().execute(&db).await?;
Project::create_table().execute(&db).await?;
User::create("thomas", "pa$$w0rd", 28).save(&db).await?;
User::create("nicolas", "pa$$w0rd", 28).save(&db).await?;
// Find some users in the database
let thomas = User::get_by_username("thomas", &db).await?.unwrap();
let nicolas = User::get_by_username("nicolas", &db).await?.unwrap();

// Create a project
let first_project = Project::create("My first project").save(&db).await?;

// Thomas can access this project
first_project.add_authorized_user(&thomas, &db).await?;

// The other way round
nicolas.add_visible_project(&first_project, &db).await?;

// The second project can only be used by thomas
let second_project = Project::create("My second project").save(&db).await?;
thomas.add_visible_project(&second_project, &db).await?;

// The third project can only be used by nicolas.
let third_project = Project::create("My third project").save(&db).await?;
third_project.add_authorized_user(&nicolas, &db).await?;

// You can easily retrieve all projects available for a certain user
let projects: Vec<Project> = thomas.visible_projects(&db).await?;

// And you can easily retrieve all users that have access to a certain project
let users: Vec<User> = first_project.authorized_users(&db).await?;

// You can easily remove a user from a project
let _: bool = first_project.remove_authorized_user(&thomas, &db).await?;

// Or vice-versa
let _: bool = nicolas.remove_visible_project(&first_project, &db).await?;

// The remove functions return true if they successfully removed something.
Ok(())
}

Extra information in a many-to-many relationship

It is possible to insert some extra information in a many to many relationship. The following exemple gives roles for the users for projects.

#![allow(unused)]
fn main() {
extern crate ergol;
use ergol::prelude::*;

#[ergol]
pub struct User {
    #[id] pub id: i32,
    #[unique] pub username: String,
    pub password: String,
    pub age: i32,
}

#[derive(PgEnum, Debug)]
pub enum Role {
   Admin,
   Write,
   Read,
}

#[ergol]
pub struct Project {
    #[id] pub id: i32,
    pub name: String,
    #[many_to_many(projects, Role)] pub users: User,
}
}

With these structures, the signature of generated methods change to take a role as argument, and to return tuples of (User, Role) or (Project, Role).

extern crate tokio;
extern crate ergol;
use ergol::prelude::*;
#[ergol]
pub struct User {
    #[id] pub id: i32,
    #[unique] pub username: String,
    pub password: String,
    pub age: i32,
}
#[derive(PgEnum, Debug)]
pub enum Role {
   Admin,
   Write,
   Read,
}
#[ergol]
pub struct Project {
    #[id] pub id: i32,
    pub name: String,
    #[many_to_many(projects, Role)] pub users: User,
}
#[tokio::main]
async fn main() -> Result<(), ergol::tokio_postgres::Error> {
    let (db, connection) = ergol::connect(
        "host=localhost user=ergol password=ergol dbname=ergol2",
        ergol::tokio_postgres::NoTls,
    )
    .await?;
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });
Project::drop_table().execute(&db).await.ok();
User::drop_table().execute(&db).await.ok();
Role::drop_type().execute(&db).await.ok();
Role::create_type().execute(&db).await?;
User::create_table().execute(&db).await?;
Project::create_table().execute(&db).await?;
User::create("thomas", "pa$$w0rd", 28).save(&db).await?;
User::create("nicolas", "pa$$w0rd", 28).save(&db).await?;
let thomas = User::get_by_username("thomas", &db).await?.unwrap();
let nicolas = User::get_by_username("nicolas", &db).await?.unwrap();
let project = Project::create("My first project").save(&db).await?;
project.add_user(&thomas, Role::Admin, &db).await?;
nicolas.add_project(&project, Role::Read, &db).await?;

for (user, role) in project.users(&db).await? {
    println!("{} has {:?} rights on project {:?}", user.username, role, project.name);
}

let project = Project::create("My second project").save(&db).await?;
project.add_user(&thomas, Role::Admin, &db).await?;

let project = Project::create("My third project").save(&db).await?;
project.add_user(&nicolas, Role::Admin, &db).await?;
project.add_user(&thomas, Role::Read, &db).await?;

for (project, role) in thomas.projects(&db).await? {
    println!("{} has {:?} rights on project {:?}", thomas.username, role, project.name);
}
Ok(())
}