rust-eloquent 0.2.0

An Active Record ORM for Rust, inspired by Laravel's Eloquent.
Documentation

Rust Eloquent 🌟

Crates.io Docs.rs License: MIT Databases

An Active Record ORM for Rust, inspired by Laravel's Eloquent.

Built on top of sqlx and procedural macros, rust-eloquent aims to bring the delightful and simplistic syntax of Laravel directly to the high-performance Rust ecosystem. It supports PostgreSQL, MySQL, and SQLite universally out of the box using dynamic driver loading!

πŸš€ Why Rust Eloquent?

In traditional Rust database handling, you have to write raw SQL queries, manage connection pools manually across every function, and bind variables repetitively. Rust Eloquent solves this by abstracting the heavy lifting behind a single #[derive(Eloquent)] macro.

Rust Eloquent v0.2 brings a massive array of enterprise-grade features:

  • Constrained Eager Loading for fetching deep relationships safely.
  • Global Lifecycle Observers to intercept operations before/after they happen.
  • Subqueries & Advanced Joins with multi-constraint ON clauses.
  • Artisan Migrations CLI for auto-generating, mapping, and rolling back database schemas.
  • Dynamic STDOUT Query Logging for rapid debugging.
  • Model Field Serialization & Hiding to strip out secrets.

πŸ› οΈ Installation

Add the library to your Cargo.toml:

[dependencies]
rust-eloquent = "0.2.0"
tokio = { version = "1.0", features = ["full"] }

πŸ“– Quick Start

use rust_eloquent::{Eloquent, sqlx::FromRow};

// 1. Just add the Eloquent macro to your struct!
#[derive(Debug, Clone, FromRow, rust_eloquent::Eloquent)]
#[eloquent(table = "users")] // Optional: specifies a custom table name
pub struct User {
    pub id: i32, // ID = 0 means it hasn't been saved yet
    pub name: String,
    pub email: String,
    #[eloquent(hidden)] // This field won't be exposed when calling user.to_json()
    pub password: String,
}

#[tokio::main]
async fn main() -> Result<(), rust_eloquent::sqlx::Error> {
    // 2. Initialize the global connection pool
    Eloquent::init("sqlite::memory:").await?;

    // 3. Create a new user magically
    let mut user = User {
        id: 0,
        name: "Vene Louis".to_string(),
        email: "vene@cosmos.com".to_string(),
        password: "secret".to_string(),
    };
    
    user.save().await?; // Runs INSERT and updates the ID automatically!

    // 4. Update the user
    user.name = "John Doe".to_string();
    user.save().await?; // Detects ID > 0 and runs UPDATE automatically!

    // 5. Fetch from database
    let found = User::find(1).await?;
    println!("Found: {:?}", found);

    // 6. Delete
    found.unwrap().delete().await?;

    Ok(())
}

✨ Available Query Builder Methods

The #[derive(Eloquent)] macro injects an entire Query Builder into your model, allowing you to chain methods endlessly.

πŸ” Active Record Methods

These methods are called directly on your model instance or struct:

  • Model::query() -> Starts a new Query Builder instance.
  • Model::find(id: i32) -> Find a single record by its Primary Key (returns Option).
  • Model::find_or_fail(id: i32) -> Find a single record or throw RowNotFound.
  • Model::all() -> Retrieve an array containing all records.
  • model.save() -> Automatically runs an INSERT or UPDATE depending on if the id is 0.
  • model.delete() -> Deletes the record from the database.

⛓️ Query Filters (Chainable)

You can chain these methods after calling Model::query() to filter your data. All values are automatically bound to prevent SQL Injection:

AND Filters:

  • .where_eq(column, value)
  • .where_not_eq(column, value)
  • .where_gt(column, value) / .where_lt(column, value)
  • .where_like(column, value) / .where_not_like(column, value)
  • .where_null(column) / .where_not_null(column)
  • .where_in(column, vec_of_values)
  • .where_between(column, min, max)

OR Filters:

  • .or_where(column, value)
  • .or_where_not_eq(column, value)
  • .or_where_like(column, value)
  • .or_where_in(column, vec_of_values)

πŸ”’ Selection & Aggregation

  • .select_raw("users.*, posts.title") -> Choose specific columns or aliases
  • .group_by(column) -> Add GROUP BY clause
  • .order_by(column) / .order_by_desc(column)
  • .limit(value: usize) / .offset(value: usize)

⚑ Executors (Terminal Methods)

End your Query Builder chain with one of these to execute the SQL query asynchronously:

  • .get().await? -> Returns a Vec<Model> matching your filters.
  • .first().await? -> Returns Option<Model> (automatically applies LIMIT 1).
  • .paginate(page, per_page).await? -> Returns PaginationResult<Model>.
  • .count().await? -> Returns an i64 representing the number of rows.
  • .delete_all().await? -> Deletes all rows matching your filters.

πŸš€ Advanced Subqueries & Joins

Rust Eloquent provides powerful primitives for complex SQL joins and subqueries, maintaining sqlx binding safety!

Constrained Joins

You can join tables and apply multiple exact matches inside the join clause:

let posts_with_users = Post::query()
    .join_constrained("users", |join| {
        join.on("posts.user_id", "=", "users.id")
            .on_eq("users.name", "Alice")
    })
    .where_eq("posts.status", "published")
    .get()
    .await?;

Subqueries (where_exists)

Inject nested WHERE EXISTS queries natively by passing another query builder:

let active_users = User::query()
    .where_exists(
        Post::query()
            .where_column("posts.user_id", "users.id")
            .where_eq("posts.status", "published")
    )
    .get()
    .await?;

πŸ›‘οΈ Global Lifecycle Observers

You can hook into your models’ lifecycle without cluttering your structs! Create an observer and register it globally:

pub struct UserObserverImpl;

#[rust_eloquent::async_trait]
impl UserObserver for UserObserverImpl {
    async fn saving(&self, model: &mut User) -> Result<(), rust_eloquent::sqlx::Error> {
        println!("We are about to save user: {}", model.name);
        Ok(())
    }
}

// Register your observer once globally:
User::observe(Arc::new(UserObserverImpl));

Supported Events: saving, saved, creating, created, updating, updated, deleting, deleted.


🐘 Rust Artisan CLI (Migrations & Seeding)

Ship your applications with an integrated database migration architecture running within Rust!

// In your application's CLI entry point:
rust_eloquent::schema::run_artisan(std::env::args().collect(), vec![ /* Seeders here */ ]).await;

Commands provided natively:

  • make:migration create_users_table -> Scaffolds a .rs migration file using a fluent Blueprint generator.
  • migrate -> Executes un-run migrations sequentially against the database.
  • migrate:rollback -> Undoes the previous batch of executed migrations.
  • db:seed -> Iterates through your database Seeders.

πŸ”Ž Query Debug Logging

Ever wondered what SQL queries are running under the hood? Toggle STDOUT query logging dynamically at any point!

Eloquent::enable_query_log();
// All queries, limits, offsets, and parameter bindings will print to STDOUT
Eloquent::disable_query_log();

βš™οΈ Compile-Time Magic Methods

The macro intelligently inspects your struct fields at compile time and generates exclusive methods for each field. If your struct has an email field, you automatically unlock:

  • .where_email(value)
  • .or_where_email(value)
  • .where_not_email(value)
  • .order_by_email()
  • .order_by_email_desc()

This provides an incredible developer experience identical to Laravel!