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

Preloading Associations

Preloading (also called eager loading) loads related records alongside the main query, avoiding extra database round-trips when you access associations.

The N+1 problem

Without preloading, accessing a relation on each record in a list causes one query per record:

// 1 query: load all users
let users = User::all().exec(&mut db).await?;

for user in &users {
    // N queries: one per user to load their posts
    let posts = user.posts().exec(&mut db).await?;
    println!("{}: {} posts", user.name, posts.len());
}

If there are 100 users, this executes 101 queries. Preloading reduces this to a fixed number of queries regardless of how many records you load.

Using .include()

Add .include() to a query to preload a relation. Pass the field path from the model’s fields() accessor:

#![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("Hello"))
    .exec(&mut db)
    .await?;
let user = User::filter_by_id(user.id)
    .include(User::fields().posts())
    .get(&mut db)
    .await?;

// Access preloaded posts — no additional query
let posts: &[Post] = user.posts.get();
assert_eq!(1, posts.len());
Ok(())
}
}

The .include() call tells Toasty to load the associated posts as part of the query. After preloading, access the data through the field directly (user.posts.get()) instead of the method (user.posts().exec()).

Preloaded vs unloaded access

There are two ways to access a relation, depending on whether it was preloaded:

Access patternWhen to useQueries
user.posts().exec(&mut db)Relation was not preloadedExecutes a query
user.posts.get()Relation was preloaded with .include()No query

Calling .get() on an unloaded relation panics. Only use .get() when you know the relation was preloaded.

Preloading BelongsTo

Preload a parent record from the child side:

#![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("Hello"))
    .exec(&mut db)
    .await?;
let post_id = user.posts().exec(&mut db).await?[0].id;
let post = Post::filter_by_id(post_id)
    .include(Post::fields().user())
    .get(&mut db)
    .await?;

// Access the preloaded user
let user: &User = post.user.get();
assert_eq!("Alice", user.name);
Ok(())
}
}

Preloading HasOne

Preload a single child record from the parent side:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[has_one]
    profile: toasty::HasOne<Option<Profile>>,
}
#[derive(Debug, toasty::Model)]
struct Profile {
    #[key]
    #[auto]
    id: u64,
    bio: String,
    #[unique]
    user_id: Option<u64>,
    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<Option<User>>,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = User::create()
    .name("Alice")
    .profile(Profile::create().bio("A person"))
    .exec(&mut db)
    .await?;
let user = User::filter_by_id(user.id)
    .include(User::fields().profile())
    .get(&mut db)
    .await?;

// Access the preloaded profile
let profile = user.profile.get().as_ref().unwrap();
assert_eq!("A person", profile.bio);
Ok(())
}
}

If no related record exists, the preloaded value is None rather than causing a panic:

#![allow(unused)]
fn main() {
use toasty::Model;
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[has_one]
    profile: toasty::HasOne<Option<Profile>>,
}
#[derive(Debug, toasty::Model)]
struct Profile {
    #[key]
    #[auto]
    id: u64,
    bio: String,
    #[unique]
    user_id: Option<u64>,
    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<Option<User>>,
}
async fn __example(mut db: toasty::Db) -> toasty::Result<()> {
let user = User::create().name("No Profile").exec(&mut db).await?;

let user = User::filter_by_id(user.id)
    .include(User::fields().profile())
    .get(&mut db)
    .await?;

// Preloaded and empty — .get() returns None, not a panic
assert!(user.profile.get().is_none());
Ok(())
}
}

Multiple includes

Chain multiple .include() calls to preload several relations in one query:

let user = User::filter_by_id(user_id)
    .include(User::fields().posts())
    .include(User::fields().comments())
    .get(&mut db)
    .await?;

// Both are preloaded
let posts: &[Post] = user.posts.get();
let comments: &[Comment] = user.comments.get();

You can mix relation types — preload HasMany, HasOne, and BelongsTo relations in the same query:

let user = User::filter_by_id(user_id)
    .include(User::fields().profile())   // HasOne
    .include(User::fields().posts())     // HasMany
    .get(&mut db)
    .await?;

Preloading with collection queries

.include() works with .exec() (collection queries), not just .get() (single record). All records in the result have their relations preloaded:

let users = User::all()
    .include(User::fields().posts())
    .exec(&mut db)
    .await?;

for user in &users {
    // No additional query per user
    let posts: &[Post] = user.posts.get();
    println!("{}: {} posts", user.name, posts.len());
}

Summary

SyntaxDescription
.include(Model::fields().relation())Preload a relation in the query
model.relation.get()Access preloaded HasMany data (returns &[T])
model.relation.get()Access preloaded BelongsTo data (returns &T)
model.relation.get()Access preloaded HasOne data (returns &T or &Option<T>)
model.relation.is_unloaded()Check if a relation was preloaded