# Relationships Guide
This guide covers entity relationships in Ormkit, including BelongsTo, HasMany, and N+1 query prevention.
**Status Note:** Relationship loading is functional but the API is still being refined. BelongsTo and HasMany work well. One-to-one and many-to-many relationships are planned.
---
## Table of Contents
1. [BelongsTo (Many-to-One)](#belongsto-many-to-one)
2. [HasMany (One-to-Many)](#hasmany-one-to-many)
3. [Eager Loading](#eager-loading)
4. [N+1 Query Prevention](#n1-query-prevention)
5. [Query Count Examples](#query-count-examples)
---
## BelongsTo (Many-to-One)
A `BelongsTo` relationship represents ownership where one entity belongs to another.
### Defining BelongsTo
```rust
#[derive(ormkit::Entity)]
#[ormkit(table = "posts")]
struct Post {
#[ormkit(id)]
id: Uuid,
title: String,
content: String,
// Foreign key to users table
#[ormkit(belongs_to = "User")]
user_id: Uuid,
}
#[derive(ormkit::Entity)]
#[ormkit(table = "users")]
struct User {
#[ormkit(id)]
id: Uuid,
name: String,
email: String,
}
```
### Loading BelongsTo Relationships
```rust
use ormkit::relations::RelationBuilder;
// Load posts with their users
let posts = Post::query().fetch_all(&pool).await?;
// Load users for all posts in a single query
let posts_with_users = RelationBuilder::for_entities(posts)
.load::<User>(&pool)
.await?;
for loaded in posts_with_users {
println!("Post: {} by {}", loaded.entity.title, loaded.related.name);
}
```
### Using the .with() API
```rust
use ormkit::WithRelations;
// Load single post with user
let post = Post::query().fetch_one(&pool).await?;
let post_with_user = post.with::<User>().load(&pool).await?;
println!("Post: {} by {}", post_with_user.entity.title, post_with_user.related.unwrap().name);
// Load multiple posts with users
let posts = Post::query().fetch_all(&pool).await?;
let posts_with_users = posts.with::<User>().load(&pool).await?;
```
---
## HasMany (One-to-Many)
A `HasMany` relationship represents an entity that contains multiple related entities.
### Defining HasMany
```rust
#[derive(ormkit::Entity)]
#[ormkit(table = "users")]
struct User {
#[ormkit(id)]
id: Uuid,
name: String,
// Note: This field tracks the relationship but isn't stored
#[ormkit(has_many = "Post")]
posts: Vec<Uuid>, // Stores post IDs for reference
}
#[derive(ormkit::Entity)]
#[ormkit(table = "posts")]
struct Post {
#[ormkit(id)]
id: Uuid,
title: String,
// Foreign key back to user
user_id: Uuid,
}
```
### Loading HasMany Relationships
```rust
use ormkit::WithRelations;
// Load users with their posts
let users = User::query().fetch_all(&pool).await?;
let users_with_posts = users.with_many::<Post>().load(&pool).await?;
for (user, posts) in users_with_posts {
println!("User: {}", user.name);
for post in posts {
println!(" - {}", post.title);
}
}
```
---
## Eager Loading
Eager loading prevents N+1 queries by loading related entities in batch.
### The N+1 Problem
**Without eager loading (N+1 queries):**
```rust
// BAD: N+1 queries
let posts = Post::query().fetch_all(&pool).await?; // 1 query
for post in posts {
// N additional queries (one per post)
let user = User::query()
.filter(User::id().eq(post.user_id))
.fetch_one(&pool).await?;
println!("{} by {}", post.title, user.name);
}
```
**With eager loading (2 queries):**
```rust
// GOOD: 2 queries total
let posts = Post::query().fetch_all(&pool).await?; // 1 query
let posts_with_users = posts.with::<User>().load(&pool).await?; // 1 query
for loaded in posts_with_users {
println!("{} by {}", loaded.entity.title, loaded.related.name);
}
```
### Multiple Relationships
You can chain eager loads for multiple relationships:
```rust
let posts = Post::query().fetch_all(&pool).await?;
// Load users first
let posts_with_users = posts.with::<User>().load(&pool).await?;
// Then load comments for each post
// (This would require nested relationship support - planned feature)
```
---
## N+1 Query Prevention
Ormkit prevents N+1 queries through:
1. **Batch Loading**: Load all related entities in a single query
2. **IN Clauses**: Use `WHERE id IN (...)` instead of individual queries
3. **RelationBuilder**: Centralized loading logic
### How It Works
```rust
// Internally, this generates:
SELECT * FROM posts; // Get all posts
// Then:
SELECT * FROM users
WHERE id IN (
'uuid1', 'uuid2', 'uuid3', ... // All post.user_id values
);
```
This reduces **N queries** to **2 queries** regardless of entity count.
---
## Query Count Examples
### Example 1: Blog Posts with Authors
**Naive approach (N+1):**
```rust
// 1 query to fetch posts
// N queries to fetch authors (one per post)
// Total: 101 queries for 100 posts
```
**Eager loading:**
```rust
let posts = Post::query().fetch_all(&pool).await?; // 1 query
let posts_with_authors = posts.with::<User>().load(&pool).await?; // 1 query
// Total: 2 queries for 100 posts
```
**Improvement: 50x reduction in queries**
### Example 2: Users with Posts
**Naive approach (N+1):**
```rust
// 1 query to fetch users
// N queries to fetch posts (one per user)
// Total: 51 queries for 50 users
```
**Eager loading:**
```rust
let users = User::query().fetch_all(&pool).await?; // 1 query
let users_with_posts = users.with_many::<Post>().load(&pool).await?; // 1 query
// Total: 2 queries for 50 users
```
**Improvement: 25x reduction in queries**
---
## Planned Features
### One-to-One Relationships
```rust
// Planned syntax (not yet implemented)
#[derive(ormkit::Entity)]
struct User {
#[ormkit(id)]
id: Uuid,
#[ormkit(has_one = "Profile")]
profile: Option<Uuid>,
}
let user_with_profile = user.with_one::<Profile>().load(&pool).await?;
```
### Many-to-Many Relationships
```rust
// Planned syntax (not yet implemented)
#[ormkit(join_table = "post_tags")]
struct PostTags {
post_id: Uuid,
tag_id: Uuid,
}
#[derive(ormkit::Entity)]
struct Post {
#[ormkit(id)]
id: Uuid,
#[ormkit(many_to_many = "Tag")]
tags: Vec<Tag>,
}
let posts_with_tags = posts.with_many::<Tag>().load(&pool).await?;
```
### Nested Eager Loading
```rust
// Planned: Load posts with users and user comments
let posts_with_all = posts
.with::<User>()
.with_many::<Comment>()
.load(&pool)
.await?;
```
---
## Best Practices
1. **Always use eager loading** for relationships in loops
2. **Measure query counts** in development
3. **Use .with() API** for cleaner syntax
4. **Avoid relationship loading** for single entities when not needed
5. **Consider join queries** for complex filtering (planned feature)
---
## See Also
- [Core ORM Guide](CORE_GUIDE.md) - Basic ORM usage
- [Type-Safe Queries](CORE_GUIDE.md#type-safe-queries) - Query DSL
- [Performance Guide](PERFORMANCE_OPTIMIZATIONS.md) - Performance optimization
---
**Current Status**: BelongsTo and HasMany relationships are functional. API ergonomics are being refined. One-to-one and many-to-many relationships are planned.