Skip to main content
Version: 1.1.x

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.

entity/cake.rs
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.

entity/filling.rs
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:

  1. Add two new variants Cake and Filling to the Relation enum.
  2. Define both relations with Entity::belongs_to().
entity/cake_filling.rs
#[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)
);