Many to Many
A many-to-many relation is formed by three tables, where two tables are related via a junction table. As an example, a Cake
has many Filling
and Filling
are shared by many Cake
via an intermediate entity CakeFilling
.
Defining the Relation
On the Cake
entity, implement the Related<filling::Entity>
trait.
Relation
in SeaORM is an arrow: it has from
and to
. cake_filling::Relation::Cake
defines CakeFilling -> Cake
. Calling rev
reverses it into Cake -> CakeFilling
.
Chaining this with cake_filling::Relation::Filling
which defines CakeFilling -> Filling
resulting in Cake -> CakeFilling -> Filling
.
impl Related<super::filling::Entity> for Entity {
// The final relation is Cake -> CakeFilling -> Filling
fn to() -> RelationDef {
super::cake_filling::Relation::Filling.def()
}
fn via() -> Option<RelationDef> {
// The original relation is CakeFilling -> Cake,
// after `rev` it becomes Cake -> CakeFilling
Some(super::cake_filling::Relation::Cake.def().rev())
}
}
Similarly, on the Filling
entity, implement the Related<cake::Entity>
trait. First, join with intermediate table via
the inverse of cake_filling::Relation::Filling
relation, then join to
Cake
entity with cake_filling::Relation::Cake
relation.
impl Related<super::cake::Entity> for Entity {
fn to() -> RelationDef {
super::cake_filling::Relation::Cake.def()
}
fn via() -> Option<RelationDef> {
Some(super::cake_filling::Relation::Filling.def().rev())
}
}
Defining the Inverse Relation
On the CakeFilling
entity, its cake_id
attribute is referencing the primary key of Cake
entity, and its filling_id
attribute is referencing the primary key of Filling
entity.
To define the inverse relation:
- Add two new variants
Cake
andFilling
to theRelation
enum. - Define both relations with
Entity::belongs_to()
.
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Cake,
Filling,
}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Cake => Entity::belongs_to(super::cake::Entity)
.from(Column::CakeId)
.to(super::cake::Column::Id)
.into(),
Self::Filling => Entity::belongs_to(super::filling::Entity)
.from(Column::FillingId)
.to(super::filling::Column::Id)
.into(),
}
}
}
Alternatively, the definition can be shortened by the DeriveRelation
macro, where the following eliminates the need for the RelationTrait
implementation above:
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::cake::Entity",
from = "Column::CakeId",
to = "super::cake::Column::Id"
)]
Cake,
#[sea_orm(
belongs_to = "super::filling::Entity",
from = "Column::FillingId",
to = "super::filling::Column::Id"
)]
Filling,
}
Note that the implementation of Related
with via
and to
methods will not be generated if there exists multiple paths via an intermediate table.
For example, in the schema defined below, there are two paths:
- Path 1.
users <-> users_votes <-> bills
- Path 2.
users <-> users_saved_bills <-> bills
Therefore, the implementation of Related<R>
will not be generated
CREATE TABLE users
(
id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(),
email TEXT UNIQUE NOT NULL,
...
);
CREATE TABLE bills
(
id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(),
...
);
CREATE TABLE users_votes
(
user_id uuid REFERENCES users (id) ON UPDATE CASCADE ON DELETE CASCADE,
bill_id uuid REFERENCES bills (id) ON UPDATE CASCADE ON DELETE CASCADE,
vote boolean NOT NULL,
CONSTRAINT users_bills_pkey PRIMARY KEY (user_id, bill_id)
);
CREATE TABLE users_saved_bills
(
user_id uuid REFERENCES users (id) ON UPDATE CASCADE ON DELETE CASCADE,
bill_id uuid REFERENCES bills (id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT users_saved_bills_pkey PRIMARY KEY (user_id, bill_id)
);