ergol 0.0.1

an async ORM for Rust
Documentation

CI

This crate provides the #[ergol] macro. It allows to persist the data in a database. For example, you just have to write

use ergol::prelude::*;

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

and the #[ergol] macro will generate most of the code you will need. You'll then be able to run code like the following:

# use ergol::prelude::*;
# #[ergol]
# pub struct User {
#     #[id] pub id: i32,
#     #[unique] pub username: String,
#     pub password: String,
#     pub age: Option<i32>,
# }
# use ergol::tokio;
# #[tokio::main]
# async fn main() -> Result<(), ergol::tokio_postgres::Error> {
#     let (client, connection) = ergol::tokio_postgres::connect(
#         "host=localhost user=orm password=orm dbname=orm",
#         ergol::tokio_postgres::NoTls,
#     )
#     .await?;
#     tokio::spawn(async move {
#         if let Err(e) = connection.await {
#             eprintln!("connection error: {}", e);
#         }
#     });
// Drop the user table if it exists
User::drop_table().execute(&client).await.ok();

// Create the user table
User::create_table().execute(&client).await?;

// Create a user and save it into the database
let mut user: User = User::create("thomas", "pa$$w0rd", Some(28)).save(&client).await?;

// Change some of its fields
*user.age.as_mut().unwrap() += 1;

// Update the user in the database
user.save(&client).await?;

// Fetch a user by its username thanks to the unique attribute
let user: Option<User> = User::get_by_username("thomas", &client).await?;

// Select all users
let users: Vec<User> = User::select().execute(&client).await?;
# Ok(())
# }

Many-to-one and one-to-one relationships

Let's say you want a user to be able to have projects. You can use the #[many_to_one] attribute in order to do so. Just add:

# use ergol::prelude::*;
# #[ergol]
# pub struct User {
#     #[id] pub id: i32,
#     #[unique] pub username: String,
#     pub password: String,
#     pub age: Option<i32>,
# }
#[ergol]
pub struct Project {
#[id] pub id: i32,
pub name: String,
#[many_to_one(projects)] pub owner: User,
}

Once you have defined this struct, many more functions become available:

# use ergol::prelude::*;
# #[ergol]
# pub struct User {
#     #[id] pub id: i32,
#     #[unique] pub username: String,
#     pub password: String,
#     pub age: Option<i32>,
# }
# #[ergol]
# pub struct Project {
#     #[id] pub id: i32,
#     pub name: String,
#     #[many_to_one(projects)] pub owner: User,
# }
# use ergol::tokio;
# #[tokio::main]
# async fn main() -> Result<(), ergol::tokio_postgres::Error> {
#     let (client, connection) = ergol::tokio_postgres::connect(
#         "host=localhost user=orm password=orm dbname=orm",
#         ergol::tokio_postgres::NoTls,
#     )
#     .await?;
#     tokio::spawn(async move {
#         if let Err(e) = connection.await {
#             eprintln!("connection error: {}", e);
#         }
#     });
// Drop the user table if it exists
Project::drop_table().execute(&client).await.ok();
User::drop_table().execute(&client).await.ok();

// Create the user table
User::create_table().execute(&client).await?;
Project::create_table().execute(&client).await?;

// Create two users and save them into the database
let thomas: User = User::create("thomas", "pa$$w0rd", 28).save(&client).await?;
User::create("nicolas", "pa$$w0rd", 28).save(&client).await?;

// Create some projects for the user
let project: Project = Project::create("My first project", &thomas).save(&client).await?;
Project::create("My second project", &thomas).save(&client).await?;

// You can easily find all projects from the user
let projects: Vec<Project> = thomas.projects(&client).await?;

// You can also find the owner of a project
let owner: User = projects[0].owner(&client).await?;
# Ok(())
# }

You can similarly have one-to-one relationship between a user and a project by using the #[one_to_one] attribute:

# use ergol::prelude::*;
# #[ergol]
# pub struct User {
#     #[id] pub id: i32,
#     #[unique] pub username: String,
#     pub password: String,
# }
#[ergol]
pub struct Project {
#[id] pub id: i32,
pub name: String,
#[one_to_one(project)] pub owner: User,
}

This will add the UNIQUE attribute in the database and make the project method only return an option:

# use ergol::prelude::*;
# #[ergol]
# pub struct User {
#     #[id] pub id: i32,
#     #[unique] pub username: String,
#     pub password: String,
#     pub age: Option<i32>,
# }
# #[ergol]
# pub struct Project {
#     #[id] pub id: i32,
#     pub name: String,
#     #[one_to_one(project)] pub owner: User,
# }
# use ergol::tokio;
# #[tokio::main]
# async fn main() -> Result<(), ergol::tokio_postgres::Error> {
#     let (client, connection) = ergol::tokio_postgres::connect(
#         "host=localhost user=orm password=orm dbname=orm",
#         ergol::tokio_postgres::NoTls,
#     )
#     .await?;
#     tokio::spawn(async move {
#         if let Err(e) = connection.await {
#             eprintln!("connection error: {}", e);
#         }
#     });
# Project::drop_table().execute(&client).await.ok();
# User::drop_table().execute(&client).await.ok();
# User::create_table().execute(&client).await?;
# Project::create_table().execute(&client).await?;
# let thomas: User = User::create("thomas", "pa$$w0rd", 28).save(&client).await?;
// You can easily find a user's project
let project: Option<Project> = thomas.project(&client).await?;
# Ok(())
# }

Note that that way, a project has exactly one owner, but a user can have no project.

Many-to-many relationships

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

# use ergol::prelude::*;
# #[ergol]
# pub struct User {
#     #[id] pub id: i32,
#     #[unique] pub username: String,
#     pub password: String,
# }
#[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:

# 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 (client, connection) = ergol::tokio_postgres::connect(
#         "host=localhost user=orm password=orm dbname=orm",
#         ergol::tokio_postgres::NoTls,
#     )
#     .await?;
#     tokio::spawn(async move {
#         if let Err(e) = connection.await {
#             eprintln!("connection error: {}", e);
#         }
#     });
# Project::drop_table().execute(&client).await.ok();
# User::drop_table().execute(&client).await.ok();
# User::create_table().execute(&client).await?;
# Project::create_table().execute(&client).await?;
# User::create("thomas", "pa$$w0rd", 28).save(&client).await?;
# User::create("nicolas", "pa$$w0rd", 28).save(&client).await?;
// Find some users in the database
let thomas = User::get_by_username("thomas", &client).await?.unwrap();
let nicolas = User::get_by_username("nicolas", &client).await?.unwrap();

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

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

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

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

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

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

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

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

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

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

Limitations

For the moment, we still have plenty of limitations:

  • this crate only works with tokio-postgres
  • there is no support for migrations
  • the names of the structs you use in #[ergol] must be used previously, e.g.
mod user {
use ergol::prelude::*;

#[ergol]
pub struct User {
#[id] pub id: i32,
}
}

use ergol::prelude::*;
#[ergol]
pub struct Project {
#[id] pub id: i32,
#[many_to_one(projects)] pub owner: user::User, // this will not work
}
mod user {
use ergol::prelude::*;
#[ergol]
pub struct User {
#[id] pub id: i32,
}
}
use user::User;

use ergol::prelude::*;
#[ergol]
pub struct Project {
#[id] pub id: i32,
#[many_to_one(projects)] pub owner: User, // this will work
}