Relations¶
Ferro provides a robust system for connecting models, supporting standard relational patterns with zero-boilerplate reverse lookups and automated join table management.
One-to-Many¶
The most common relationship type. It is defined using a ForeignKey on the "child" model and a BackRelationship marker on the "parent" model.
from typing import Annotated
from ferro import Model, ForeignKey, BackRelationship
class Author(Model):
id: int
name: str
# Marker for reverse lookup; provides full Query intellisense
posts: BackRelationship["Post"] = None
class Post(Model):
id: int
title: str
# Defines the forward link and the name of the reverse field
author: Annotated[Author, ForeignKey(related_name="posts")]
Shadow Fields¶
For every ForeignKey field (e.g., author), Ferro automatically creates a "shadow" ID column in the database (e.g., author_id). You can access or filter by this field directly via post.author_id.
One-to-One¶
A strict 1:1 link is created by adding unique=True to a ForeignKey.
Behavioral Difference:
- Forward: Accessing
await profile.userreturns a singleUserobject. - Reverse: Accessing
await user.profilereturns a singleProfileobject (internally calls.first()) instead of aQueryobject.
Many-to-Many¶
Defined using the ManyToManyField. Ferro automatically manages the hidden join table required for this relationship.
from ferro import ManyToManyField
class Student(Model):
name: str
courses: Annotated[list["Course"], ManyToManyField(related_name="students")] = None
class Course(Model):
title: str
students: BackRelationship["Student"] = None
Join Table Management¶
The Rust engine automatically registers and creates a join table (e.g., student_courses) when the models are initialized. You do not need to define a "through" model manually unless you need custom fields on the link.
Relationship Mutators¶
Many-to-Many relationships provide specialized methods for managing links:
.add(*instances): Create new links in the join table..remove(*instances): Remove specific links..clear(): Remove all links for the current instance.
Lazy Loading vs. Queries¶
Ferro relations are lazy. Data is never fetched until you explicitly request it.
- Forward Relations: Accessing a
ForeignKeyreturns an awaitable descriptor. - Reverse/M2M Relations: Accessing a
BackRelationshiporManyToManyFieldreturns aQueryobject. This allows you to chain further filters before execution.