toasty_core/schema/app/relation/has_many.rs
1use crate::{
2 schema::app::{BelongsTo, HasKind, Model, ModelId, Name, Schema},
3 stmt,
4};
5
6/// The inverse side of a one-to-many relationship.
7///
8/// A `HasMany` field on model A means "A has many Bs". A direct `HasMany`
9/// pairs with a [`BelongsTo`] field on model B that holds the foreign key; a
10/// multi-step (`via`) `HasMany` reaches B by following a path of existing
11/// relations. Which one it is is recorded in [`kind`](HasMany::kind).
12///
13/// # Examples
14///
15/// ```ignore
16/// // Given a `User` model that has many `Post`s:
17/// let has_many: &HasMany = user_field.ty.as_has_many_unwrap();
18/// let post_model = has_many.target(&schema);
19/// let inverse = has_many.pair(&schema); // the BelongsTo on Post
20/// ```
21#[derive(Debug, Clone)]
22pub struct HasMany {
23 /// The [`ModelId`] of the associated (target) model.
24 pub target: ModelId,
25
26 /// The expression type this field evaluates to from the application's
27 /// perspective.
28 pub expr_ty: stmt::Type,
29
30 /// The singular name for one associated item (used in generated method
31 /// names).
32 pub singular: Name,
33
34 /// How this relation reaches its target — a paired `BelongsTo`
35 /// ([`HasKind::Direct`]) or a [`Via`](super::Via) path
36 /// ([`HasKind::Via`]).
37 pub kind: HasKind,
38}
39
40impl HasMany {
41 /// Resolves the target [`Model`] from the given schema.
42 pub fn target<'a>(&self, schema: &'a Schema) -> &'a Model {
43 schema.model(self.target)
44 }
45
46 /// Resolves the paired [`BelongsTo`] relation on the target model.
47 ///
48 /// # Panics
49 ///
50 /// Panics if this is a multi-step (`via`) relation — it has no pair — or
51 /// if the paired field is not a `BelongsTo` variant.
52 pub fn pair<'a>(&self, schema: &'a Schema) -> &'a BelongsTo {
53 let pair = self
54 .kind
55 .pair_id()
56 .expect("`via` relation has no paired `BelongsTo`");
57 schema.field(pair).ty.as_belongs_to_unwrap()
58 }
59}