Relations
Relations are typed on the model. ForeignKey<T> and OneToOne<T> store
the primary key, optionally cache the loaded object, and serialize as the
key. Validation and JSON Schema follow T::Pk.
Foreign keys and one-to-one
Section titled “Foreign keys and one-to-one”#[derive(Model, Serialize, Deserialize, Schema, Debug, Clone)]#[model(table = "todos")]pub struct Todo { #[field(primary_key, auto)] pub id: i64, pub title: String, #[field(related_name = "todos")] pub owner: ForeignKey<User>,}- Column default:
{field}_id. on_deletedefault:cascade.set_nullrequiresOption<ForeignKey<..>>.- Fetch:
todo.fetch_owner(&db).await?→Arc<User>(orOption<Arc<User>>when the field is optional). - Reverse:
user.todos(&db)is aQuerySet<Todo>because ofrelated_name. The target must be in the same crate. - A
OneToOneis unique. Reverse accessor is an async method returningOption<Profile>.
Join a related field in a filter or projection:
Todo::objects(&db) .filter(Todo::owner.join(User::name).eq("ann")) .all() .await?;Todo::owner.join(User::name) is a field of Todo typed String. Further
hops compose (author.join(Author::team).join(Team::name)). Joins compile
to LEFT JOIN with a stable alias (owner, owner__team).
Many-to-many
Section titled “Many-to-many”Declared on the model, not as a struct field (there is no column and no serialized form):
#[model( table = "posts", many_to_many(tags(Tag, through_table = "post_tags", related_name = "posts")))]pub struct Post { /* .. */ }
let tags = post.tags(&db);tags.add([&tag], &db).await?;tags.remove([&tag], &db).await?;tags.set([&tag], &db).await?;tags.clear(&db).await?;let names = tags.queryset().all().await?;Defaults: through_table = {table}_{name}, source_column = {snake(Model)}_id, target_column = {snake(Target)}_id. Set both columns
explicitly for a self-referential relation. through = ThroughModel uses
an explicit through model whose table is the join table; omit
through_table then.
related_name adds a reverse queryset on the target (a correlated EXISTS
over the join table).
select_related and prefetch_related
Section titled “select_related and prefetch_related”let todos = Todo::objects(&db) .select_related(Todo::owner.relation()) .all() .await?;select_related adds a JOIN and fills the cached object on each
ForeignKey / OneToOne so later fetch_* does not hit the database.
prefetch_related issues a follow-up query and attaches related rows.
Reverse foreign-key and many-to-many prefetch_related are not modelled on
the struct (no slot to hold the children); use
ManyToManyManager::queryset() or a filtered QuerySet. Multi-hop
prefetch is also deferred — use select_related for those.
MongoDB rejects joins (Feature::Joins) with a capability error before any
I/O.
Across crates
Section titled “Across crates”Reverse accessors are inherent methods generated by the declaring model’s derive. When the target is in another crate, filter by key:
Book::objects(&db).filter(Book::author.eq(id)).all().await?;