Skip to main content

Relations

Declare related tables so method names in a #[repository] trait can reference their columns. Stack one relation attribute per relation below #[repository(...)] — order matters, see Attribute ordering below. A trait may declare several relations, of the same or different kinds, as long as each targets a distinct table (a relation's table value doubles as the DSL prefix matched in method names, e.g. {table}_{col} — so two relations pointing at the same target table would collide and aren't supported).

KindParamsDefaultsJOIN emittedFK shortcut ({table}_id, no JOIN)?
belongs_to(table, fk?)fk = FK column on this (owning) tablefk = "{table}_id"JOIN "{table}" ON "{table}".id = "{this_table}".{fk}Yes — {table}_id resolves straight to the fk column on this table, no JOIN
has_one(table, fk?)fk = FK column on the target table, pointing back herefk = "{this_table}_id"JOIN "{table}" ON "{table}".{fk} = "{this_table}".idNo
has_many(table, fk?)same shape as has_onesame as has_onesame as has_oneNo
belongs_to_many(table, through, fk?, other_fk?)through (required) = pivot table name; fk = this table's FK column in the pivot; other_fk = target table's FK column in the pivotfk = "{this_table}_id", other_fk = "{table}_id"JOIN "{through}" ON "{through}".{fk} = "{this_table}".id JOIN "{table}" ON "{table}".id = "{through}".{other_fk}No

{this_table} is the table = "..." value passed to #[repository(...)] itself. There's no separate relation-name/alias parameter — the table value is simultaneously the real SQL table name and the DSL prefix matched in method names.

Fan-out: has_many/belongs_to_many columns are find_all_by_*-only​

has_many and belongs_to_many can each match multiple rows on the far side of the join — unlike belongs_to/has_one, which are always many-to-one or one-to-one and can never multiply rows. Concretely, filtering user by a has_many order column would generate:

SELECT "user".* FROM "user" JOIN "order" ON "order".user_id = "user".id WHERE "order".status = $1

If a user has 3 orders with that status, this returns the same user row 3 times — silently wrong for a first_by_*/fetch_optional/fetch_one call site (expects at most one row), and merely surprising for a Vec call site. To avoid this, referencing a has_many/belongs_to_many column from anything other than find_all (first_by_*, find_random_by_*, count_by_*, exists_by_*, delete_by_*, set_*_by_*) is a compile error, not a runtime surprise.

Attribute ordering​

#[repository(...)] must be the first/outermost attribute, with relation attributes listed after it:

#[repository(table = "order")] // must come first
#[belongs_to(table = "user")] // relation attributes come after
pub trait OrderRepository: Repository { /* ... */ }

This is because has_many/has_one/belongs_to/belongs_to_many aren't independently registered proc-macro attributes — they're inert tokens that #[repository(...)] receives as part of the trait's attribute list and strips out before re-emitting the trait. If a relation attribute is listed before #[repository(...)], rustc tries to resolve it on its own and fails with "cannot find attribute has_many in this scope".