Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Creating Records

Toasty generates a create builder for each model. Call <YourModel>::create(), set fields with chained methods, and call .exec(&mut db) to insert the record.

Creating a single record

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[unique]
    email: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = User::create()
    .name("Alice")
    .email("alice@example.com")
    .exec(&mut db)
    .await?;

println!("Created user with id: {}", user.id);
Ok(())
}
}

User::create() returns a builder with a setter method for each non-auto field. Chain the setters to provide values, then call .exec(&mut db) to insert the row. The returned User instance has all fields set, including auto-generated ones like id.

The generated SQL looks like:

INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');

Required vs optional fields

Required fields (String, u64, etc.) must be set before calling .exec(). Optional fields (Option<T>) default to NULL if not set.

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,

    name: String,
    bio: Option<String>,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {

// bio defaults to None (NULL in the database)
let user = User::create()
    .name("Alice")
    .exec(&mut db)
    .await?;

assert!(user.bio.is_none());

// Or set it explicitly
let user = User::create()
    .name("Bob")
    .bio("Likes Rust")
    .exec(&mut db)
    .await?;

assert_eq!(user.bio.as_deref(), Some("Likes Rust"));
Ok(())
}
}

Creating many records

Use toasty::batch() to insert multiple records at once. Pass an array of create builders for the same model, or a tuple for mixed models. Call .exec() to insert them all.

Array syntax (same model)

When creating multiple records of the same model, pass an array. The return type is Vec<User>:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[unique]
    email: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let users = toasty::batch([
    User::create().name("Alice").email("alice@example.com"),
    User::create().name("Bob").email("bob@example.com"),
    User::create().name("Carol").email("carol@example.com"),
])
.exec(&mut db)
.await?;

assert_eq!(users.len(), 3);
Ok(())
}
}

This also works with a Vec of builders, which is useful when the number of records is determined at runtime:

let names = ["Alice", "Bob", "Carol"];
let builders: Vec<_> = names
    .iter()
    .enumerate()
    .map(|(i, n)| User::create().name(*n).email(format!("user{i}@example.com")))
    .collect();

let users = toasty::batch(builders).exec(&mut db).await?;

Tuple syntax (mixed models)

When creating records of different models, use a tuple. The return type matches the tuple structure:

let (user, post): (User, Post) = toasty::batch((
    User::create().name("Alice"),
    Post::create().title("Hello World"),
))
.exec(&mut db)
.await?;

Nested creation

When models have relationships, you can create a parent and its children in a single call. This is covered in more detail in the relationship chapters (BelongsTo, HasMany, HasOne), but here is a preview.

Given a User that has many Todo items:

let user = User::create()
    .name("Alice")
    .todo(Todo::create().title("Buy groceries"))
    .todo(Todo::create().title("Write docs"))
    .exec(&mut db)
    .await?;

Toasty creates the user first, then creates each todo with the user’s ID automatically set as the foreign key. Toasty makes a best effort to execute nested creation atomically — either all records are inserted or none are. Whether full atomicity is guaranteed depends on your database’s capabilities, so check your database’s transaction and consistency documentation.

What gets generated

For a model like User, #[derive(Model)] generates:

  • User::create() — returns a builder with a setter for each non-auto field

The create builder’s setter methods accept flexible input types through the IntoExpr trait. For a String field, you can pass &str, String, or &String. For numeric fields, you can pass the value directly or by reference. See Defining Models — What types can you pass to setters? for details.