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

HasMany

A HasMany relationship connects a parent model to multiple child records. The parent declares a HasMany<T> field, and the child stores a foreign key pointing back to the parent via BelongsTo.

Defining a HasMany relationship

Add a #[has_many] field of type HasMany<T> on the parent model. The child model must have a corresponding #[belongs_to] field.

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

    name: String,

    #[has_many]
    posts: toasty::HasMany<Post>,
}

#[derive(Debug, toasty::Model)]
struct Post {
    #[key]
    #[auto]
    id: u64,

    #[index]
    user_id: u64,

    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<User>,

    title: String,
}
}

The #[has_many] attribute does not add any columns to the parent’s table. The relationship is stored entirely in the child’s foreign key column (user_id).

Querying children

Call the relation method on a parent instance to get an accessor for its children:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[has_many]
    posts: toasty::HasMany<Post>,
}
#[derive(Debug, toasty::Model)]
struct Post {
    #[key]
    #[auto]
    id: u64,
    #[index]
    user_id: u64,
    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<User>,
    title: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = User::create().name("Alice").exec(&mut db).await?;
let posts: Vec<Post> = user.posts().exec(&mut db).await?;

for post in &posts {
    println!("{}", post.title);
}
Ok(())
}
}

The generated SQL is:

SELECT * FROM posts WHERE user_id = ?;

All queries through the relation accessor are automatically scoped to the parent. user.posts() only returns posts belonging to that user.

Creating through the relation

Create a child record through the parent’s relation accessor. Toasty automatically sets the foreign key:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[has_many]
    posts: toasty::HasMany<Post>,
}
#[derive(Debug, toasty::Model)]
struct Post {
    #[key]
    #[auto]
    id: u64,
    #[index]
    user_id: u64,
    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<User>,
    title: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = User::create().name("Alice").exec(&mut db).await?;
let post = user
    .posts()
    .create()
    .title("Hello World")
    .exec(&mut db)
    .await?;

assert_eq!(post.user_id, user.id);
Ok(())
}
}

You don’t need to set user_id — Toasty fills it in from the parent.

Nested creation

Create a parent and its children in a single call using the singular form of the relation name on the create builder:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[has_many]
    posts: toasty::HasMany<Post>,
}
#[derive(Debug, toasty::Model)]
struct Post {
    #[key]
    #[auto]
    id: u64,
    #[index]
    user_id: u64,
    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<User>,
    title: String,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = User::create()
    .name("Alice")
    .post(Post::create().title("First post"))
    .post(Post::create().title("Second post"))
    .exec(&mut db)
    .await?;

let posts = user.posts().exec(&mut db).await?;
assert_eq!(2, posts.len());
Ok(())
}
}

Toasty creates the user first, then creates each post with the user’s ID as the foreign key.

Inserting and removing children

Use .insert() and .remove() to link and unlink existing records.

Inserting

Associate an existing child record with a parent:

let post = Post::create().title("Orphan post").user_id(0).exec(&mut db).await?;

// Associate the post with a user
user.posts().insert(&mut db, &post).await?;

This updates the child’s foreign key to point to the parent. You can also insert multiple records at once:

user.posts().insert(&mut db, &[post1, post2, post3]).await?;

If the child is already associated with a different parent, .insert() moves it to the new parent.

Removing

Disassociate a child from a parent:

user.posts().remove(&mut db, &post).await?;

What happens to the child depends on whether the foreign key is required or optional:

Foreign key typeEffect of .remove()
Required (user_id: u64)Deletes the child record
Optional (user_id: Option<u64>)Sets the foreign key to NULL

When the foreign key is required, the child cannot exist without a parent, so Toasty deletes it. When the foreign key is optional, the child remains in the database with a null foreign key.

Scoped queries

The relation accessor supports scoped queries — filtering, updating, and deleting within the parent’s children.

Filtering with .query()

// Find posts with a specific condition
let drafts = user
    .posts()
    .query(Post::fields().published().eq(false))
    .exec(&mut db)
    .await?;

Looking up by ID within the scope

let post = user.posts().get_by_id(&mut db, &post_id).await?;

This only returns the post if it belongs to the user. If the post exists but belongs to a different user, this returns an error.

Updating through the scope

user.posts()
    .filter_by_id(post_id)
    .update()
    .title("New title")
    .exec(&mut db)
    .await?;

Deleting through the scope

user.posts()
    .filter_by_id(post_id)
    .delete()
    .exec(&mut db)
    .await?;

Filtering parents by children

You can filter parent records based on conditions on their children using .any() on the relation field:

// Find users who have at least one published post
let users = User::filter(
    User::fields()
        .posts()
        .any(Post::fields().published().eq(true))
)
.exec(&mut db)
.await?;

What gets generated

For a User model with #[has_many] posts: HasMany<Post>, Toasty generates:

On the parent instance:

MethodReturnsDescription
user.posts()Relation accessorAccessor scoped to this user’s posts
.exec(&mut db)Result<Vec<Post>>All posts belonging to this user
.create()Create builderCreate a post with the foreign key pre-filled
.get_by_id(&mut db, &id)Result<Post>Get a post by ID within the scope
.query(expr)Query builderFilter posts within the scope
.insert(&mut db, &post)Result<()>Associate an existing post with the user
.remove(&mut db, &post)Result<()>Disassociate a post from the user

On the create builder:

MethodDescription
.post(Post::create()...)Add one child to create alongside the parent
.posts(...)Add multiple children to create alongside the parent

On the fields accessor:

MethodDescription
User::fields().posts()Field path for preloading and filtering
User::fields().posts().any(expr)Filter parents by child conditions