# Relationship System Design
## Problem Statement
Current implementation requires manual trait implementation:
```rust
// Current - Manual implementation required
impl BelongsTo<User> for Post {
fn foreign_key() -> &'static str {
"user_id"
}
fn foreign_key_value(&self) -> Option<String> {
Some(self.user_id.to_string())
}
}
```
## Solution: Derive Macros for Relationships
### Target API
```rust
#[derive(ormkit::Entity)]
#[ormkit(table = "posts")]
struct Post {
#[ormkit(id)]
id: Uuid,
title: String,
#[ormkit(belongs_to = "User")]
user_id: Uuid,
}
#[derive(ormkit::Entity)]
#[ormkit(table = "users")]
struct User {
#[ormkit(id)]
id: Uuid,
name: String,
// Virtual - not a real column
#[ormkit(has_many = "Post")]
posts: Vec<Post>,
}
```
### Usage Examples
```rust
// Load relationships
let posts_with_users = Post::query()
.filter(Post::title().contains("Rust"))
.with(Post::user()) // Load User relationship
.fetch_all(&pool)
.await?;
for post in posts_with_users {
println!("{} by {}", post.title, post.user.name);
}
// Nested relationships
let posts_with_user_and_comments = Post::query()
.with(Post::user())
.with(Post::comments())
.fetch_all(&pool)
.await?;
// HasMany relationships
let users_with_posts = User::query()
.filter(User::active().eq(true))
.with(User::posts())
.fetch_all(&pool)
.await?;
for user in users_with_posts {
println!("{} has {} posts", user.name, user.posts.len());
}
```
## Implementation Plan
### 1. BelongsTo Attribute Processing
Parse `#[ormkit(belongs_to = "EntityType")]` attribute:
- Extract target entity type
- Identify the foreign key field (the field with the attribute)
- Generate `BelongsTo<EntityType>` trait implementation
- Generate accessor method: `pub fn user(&self) -> RelationLoader<User>`
### 2. HasMany Attribute Processing
Parse `#[ormkit(has_many = "ChildType")]` attribute:
- Extract child entity type
- Generate `HasMany<ChildType>` trait implementation
- Infer foreign key column: `{entity_snake}_id`
- Generate accessor method: `pub fn posts(&self) -> RelationLoader<Vec<Post>>`
### 3. RelationLoader Type
A lightweight loader that fetches the relationship on demand:
```rust
pub struct RelationLoader<R> {
entity_id: String,
loaded: RefCell<Option<R>>,
_phantom: PhantomData<R>,
}
impl<R> RelationLoader<R>
where
R: Entity,
{
/// Get the loaded relation, loading it if necessary
pub async fn get(&self, pool: &PgPool) -> Option<&R> {
// Lazy-load from database
}
/// Eagerly load the relationship
pub async fn load(&self, pool: &PgPool) -> Result<(), LoadError> {
// Load from database
}
}
```
### 4. Query Builder Integration
Add `.with()` method to TypedQueryBuilder:
```rust
impl<E> TypedQueryBuilder<E>
where
E: Entity,
{
/// Eager load a relationship
pub fn with<R>(self, relation: fn(&E) -> RelationLoader<R>) -> WithBuilder<E, R> {
// ...
}
}
pub struct WithBuilder<E, R> {
query: TypedQueryBuilder<E>,
_phantom: PhantomData<R>,
}
impl<E, R> WithBuilder<E, R>
where
E: Entity + LoadRelation<R>,
R: Entity,
{
pub async fn fetch_all(self, pool: &PgPool) -> Result<Vec<Loaded<E, R>>, sqlx::Error> {
// Execute query and load relationships in single batch
}
}
```
## Generated Code Example
### Input:
```rust
#[derive(ormkit::Entity)]
#[ormkit(table = "posts")]
struct Post {
#[ormkit(id)]
id: Uuid,
#[ormkit(belongs_to = "User")]
user_id: Uuid,
}
```
### Generated:
```rust
// Implement BelongsTo<User>
impl ormkit::relations::BelongsTo<User> for Post {
fn foreign_key() -> &'static str {
"user_id"
}
fn foreign_key_value(&self) -> Option<String> {
Some(self.user_id.to_string())
}
}
// Add accessor method
impl Post {
pub fn user(&self) -> ormkit::relations::RelationLoader<User> {
ormkit::relations::RelationLoader::new(self.user_id.to_string())
}
}
```
## Benefits
1. **No manual trait implementation** - Fully automated
2. **Type-safe** - Compile-time checking of relationships
3. **IDE autocomplete** - Accessor methods show up in IDE
4. **Flexible** - Supports both eager and lazy loading
5. **N+1 prevention** - Batch loading built-in
## Implementation Order
1. ✅ Parse `belongs_to` attribute in derive macro
2. ✅ Generate BelongsTo trait implementation
3. ✅ Generate accessor methods
4. ⏳ Parse `has_many` attribute
5. ⏳ Generate HasMany trait implementation
6. ⏳ Add RelationLoader type
7. ⏳ Integrate with TypedQueryBuilder
8. ⏳ Add comprehensive tests
## Example Usage After Implementation
```rust
// Simple BelongsTo
let posts = Post::query()
.with(Post::user)
.fetch_all(&pool)
.await?;
for post in posts {
println!("{}", post.user.name); // Automatically loaded!
}
// HasMany
let users = User::query()
.with(User::posts)
.fetch_all(&pool)
.await?;
for user in users {
println!("{} has {} posts", user.name, user.posts.len());
}
// Nested
let posts = Post::query()
.with(Post::user)
.with_nested(|user| user.posts())
.fetch_all(&pool)
.await?;
// Conditions on relationships
let posts = Post::query()
.filter(Post::user().name().eq("John"))
.fetch_all(&pool)
.await?;
```
This will make ormkit competitive with Django ORM, Eloquent, and ActiveRecord.