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);
# }
# });
User::drop_table().execute(&client).await.ok();
User::create_table().execute(&client).await?;
let mut user: User = User::create("thomas", "pa$$w0rd", Some(28)).save(&client).await?;
*user.age.as_mut().unwrap() += 1;
user.save(&client).await?;
let user: Option<User> = User::get_by_username("thomas", &client).await?;
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);
# }
# });
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?;
User::create("nicolas", "pa$$w0rd", 28).save(&client).await?;
let project: Project = Project::create("My first project", &thomas).save(&client).await?;
Project::create("My second project", &thomas).save(&client).await?;
let projects: Vec<Project> = thomas.projects(&client).await?;
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?;
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?;
let thomas = User::get_by_username("thomas", &client).await?.unwrap();
let nicolas = User::get_by_username("nicolas", &client).await?.unwrap();
let first_project = Project::create("My first project").save(&client).await?;
first_project.add_authorized_user(&thomas, &client).await?;
nicolas.add_visible_project(&first_project, &client).await?;
let second_project = Project::create("My second project").save(&client).await?;
thomas.add_visible_project(&second_project, &client).await?;
let third_project = Project::create("My third project").save(&client).await?;
third_project.add_authorized_user(&nicolas, &client).await?;
let projects: Vec<Project> = thomas.visible_projects(&client).await?;
let users: Vec<User> = first_project.authorized_users(&client).await?;
let _: bool = first_project.remove_authorized_user(&thomas, &client).await?;
let _: bool = nicolas.remove_visible_project(&first_project, &client).await?;
# 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, }
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, }