Skip to content

crudify-orm automatically generates CRUD methods, DTOs, and pagination helpers for your database entities using the Entity derive macro.

Notifications You must be signed in to change notification settings

uttom-akash/crudify_orm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

crudify-orm

crudify-orm automatically generates CRUD methods, DTOs, and pagination helpers for your database entities using the Entity derive macro.

Available as a Rust crate: crudify_orm on crates.io

✨ Features

  • 🆕 Create: Automatically generate create methods.
  • 🔍 Read: Fetch records by primary key.
  • ✏️ Update: Auto-generate update methods with partial updates.
  • Delete: Delete records by primary key.
  • 📄 Page Pagination: Simple page-based pagination.
  • 🔑 Keyset Pagination: Efficient cursor-based pagination for large datasets.
  • 🔎 Filtering: Auto-generate filtering methods based on entity fields.
  • 🗂 DTO Generation: Automatically generate DTOs for all CRUD operations.

📝 Note

This crate currently only supports PostgreSQL now and has a hard dependency on sqlx:

sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio-rustls", "chrono"] }

Future versions may support other database backends.

⚠️ Important: Since SQLx connects to the database at compile time for query validation, you must provide a valid connection string in your .env file:

DATABASE_URL=postgres://user:password@localhost:5432/your_database

You will find working examples here: 👉 demo project

1️⃣ Define Your Entity

use crudify_orm::Entity;
use sqlx::FromRow;

#[derive(Debug, Entity, FromRow)]
#[entity(table_name = "partners")]
struct PartnerEntity {
    #[entity(id, keyset_pagination("id_created_at"))]
    id: i64,

    #[entity(alias = "partner_name")]
    name: String,

    partner_type: String,

    #[entity(default, keyset_pagination("id_created_at"))]
    created_at: NaiveDateTime,

    enabled: bool,
}

Explanation:

  • #[derive(Entity, FromRow)] generates all CRUD and pagination methods.

  • #[entity(table_name = "...")] specifies the database table.

  • Field attributes:
    • id marks the primary key.
    • alias maps struct field names to database column names.
    • default sets default values when creating a new record.
    • keyset_pagination specifies columns used for keyset pagination.

2️⃣ Create a Record

let partner_create_dto = PartnerEntityCreate {
    id: 1,
    name: "Test Partner".to_string(),
    partner_type: "test".to_string(),
    created_at: None,
    enabled: true,
};

let created_partner = PartnerEntity::create(partner_create_dto, &pool).await?;
println!("Created partner: {:?}", created_partner);

Explanation:

  • PartnerEntityCreate is auto-generated.
  • PartnerEntity::create inserts the record into the database and returns the inserted row.

3️⃣ Read a Record

let queried_partner = PartnerEntity::get_by_id(created_partner.id, &pool).await?;
println!("Queried partner: {:?}", queried_partner);

Explanation:

  • get_by_id fetches a record by its primary key.
  • Returns Option<PartnerEntity>.

4️⃣ Update a Record

let partner_update_dto = PartnerEntityUpdate {
    id: Some(created_partner.id),
    name: Some("Updated Partner".to_string()),
    partner_type: None,
    created_at: None,
    enabled: Some(false),
};

let updated_partner = PartnerEntity::update_by_id(created_partner.id, partner_update_dto, &pool).await?;
println!("Updated partner: {:?}", updated_partner);

Explanation:

  • PartnerEntityUpdate is auto-generated and allows partial updates.
  • Only fields set to Some(value) will be updated in the database.

5️⃣ Delete a Record

PartnerEntity::delete_by_id(updated_partner.id, &pool).await?;
println!("Partner deleted successfully");

Explanation:

  • delete_by_id deletes a record using its primary key.

6️⃣ Page Pagination

let page_results = PartnerEntity::get_paged(
    PagePagination { page: 1, page_size: 5 },
    &pool,
).await?;
println!("Page results: {:?}", page_results);

Explanation:

  • Simple pagination based on page and page_size.

  • Returns a Vec<PartnerEntity> for the requested page.


7️⃣ Keyset Pagination

let mut id_cursor: i64 = i64::MAX;
let mut created_at = Utc::now().naive_utc();
let limit: i64 = 5;

loop {
    let results = PartnerEntity::paginate_dby_id_created_at(
        PaginationCursorIdCreatedAt {
            id: id_cursor,
            created_at,
            limit,
            condition: "<".to_string(),
            order_by: "desc".to_string(),
        },
        &pool,
    ).await?;

    if results.is_empty() { break; }

    println!("Keyset results: {:?}", results);

    // Update cursor for next iteration
    let last = results.last().unwrap();
    id_cursor = last.id;
    created_at = last.created_at;
}

Explanation:

  • Keyset pagination is more efficient for large datasets.
  • Uses id and created_at as cursors to fetch the next set of rows.

8️⃣ Filter Records

let mut filter = PartnerEntityFilter::default();
filter.enabled = Some(false);
filter.enabled_condition = Some("=".to_string());
filter.id = Some(100);
filter.id_condition = Some("<=".to_string());

let filtered_results = PartnerEntity::filter(filter, &pool).await?;
println!("Filtered results: {:?}", filtered_results);

Explanation:

  • PartnerEntityFilter allows filtering using field conditions.
  • Conditions can include =, !=, <, >, <=, >=.

Full Example


use chrono::{Duration, NaiveDateTime, Utc};
use crudify_orm::Entity;
use rand::Rng;
use sqlx::{FromRow, PgPool};

#[derive(Debug,Entity, FromRow)]
#[entity(table_name = "partners")]
struct PartnerEntity {
    #[entity(id, keyset_pagination("id_created_at"))]
    id: i64,

    #[entity(alias = "partner_name")]
    name: String,

    partner_type: String,

    #[entity(default, keyset_pagination("id_created_at"))]
    created_at: NaiveDateTime,

    enabled: bool,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let database_url = "postgres://crudx:[email protected]:5432/test_pagination_sqlx";

    // Create a connection pool
    let pool = PgPool::connect(database_url).await?;

    let id: i64 = rand::thread_rng().gen_range(1..=10000);

    let updated_id: i64 = rand::thread_rng().gen_range(10000..=100000);

    /// Auto generated create struct
    let partner_create_dto = PartnerEntityCreate {
        id: id.clone(),
        name: format!("akash-test partner-{}", id),
        partner_type: "test".to_string(),
        created_at: None,
        enabled: true,
    };

    /// Auto generated create method
    let created_partner = PartnerEntity::create(partner_create_dto, &pool).await?;

    println!(
        "########### created partner: {:?} ###########",
        created_partner
    );

    /// Auto generated update struct
    let partner_update_dto = PartnerEntityUpdate {
        id: Some(updated_id.clone()),
        name: Some(format!("updated partner-{}", updated_id)),
        partner_type: None,
        created_at: None,
        enabled: Some(false),
    };

    /// Auto generated update method
    let updated_partner =
        PartnerEntity::update_by_id(created_partner.id, partner_update_dto, &pool).await?;

    println!(
        "########### updated partner: {:?} ###########",
        updated_partner
    );

    /// Auto generated get by id method
    let queried_partner = PartnerEntity::get_by_id(updated_partner.id, &pool).await?;

    println!(
        "########### queried partner: {:?} ###########",
        queried_partner
    );


    /// Auto generated pagination
    println!("########### Pagination Results: ###########");
    let now = Utc::now().naive_utc();
    let mut created_at = now - Duration::hours(12);
    let mut id: i64 = i64::MAX;
    let limit: i64 = 6;

    loop {
        let results: Vec<PartnerEntity> = PartnerEntity::paginate_dby_id_created_at(
            PaginationCursorIdCreatedAt {
                id,
                created_at,
                limit,
                condition: "<".to_string(),
                order_by: "desc".to_string(),
            },
            &pool,
        )
        .await?;

        println!("### {:?} : {:?} ####", id, results);

        match results.last() {
            Some(partner_dbo) => {
                id = partner_dbo.id;
                created_at = partner_dbo.created_at;
            }
            None => break,
        };
    }

    /// Auto generated page pagination struct and method
    println!("########### Page Pagination Results: ###########");
    let results: Vec<PartnerEntity> = PartnerEntity::get_paged(
        PagePagination {
            page: 1,
            page_size: 6,
        },
        &pool,
    )
    .await?;

    println!("### {:?} ####", results.last());

    println!("### ---------------- ####");

    println!("#### FILTER ####");

    /// Auto generated filter struct and method
    let mut filter = PartnerEntityFilter::default();
    filter.id =  Some(id);
    filter.id_condition = Some("<=".to_string());
    filter.enabled = Some(false);
    filter.enabled_condition = Some("=".to_string());

    let results = PartnerEntity::filter(filter, &pool,).await?;
    
    println!("###### {:?} #####", results);


    /// Auto generated delete by id method
    match PartnerEntity::delete_by_id(updated_partner.id, &pool).await {
        Ok(_) => println!("########### Good to go: partner deleted ###########"),
        Err(e) => println!("########### ERROR: deleting partner: {} ###########", e),
    }

    match PartnerEntity::get_by_id(updated_partner.id, &pool).await {
        Ok(Some(_)) => println!("########### ERROR: partner found ###########"),
        _ => println!("########### Good to go: partner should not be found ###########"),
    }

    Ok(())
}

About

crudify-orm automatically generates CRUD methods, DTOs, and pagination helpers for your database entities using the Entity derive macro.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages