Skip to main content
Version: 2.2.0

Self-relations

A self-referential or self-relation occurs when a table has a foreign key that references its own primary key within the same table. This creates a relationship between different rows within the same table.

One-to-one

Imagine we have a blog and want to create links between our posts, where you can traverse forward and backward in the post history. Then we can create a self-referencing relation pointing to the next post in the chain.

class: Post
table: post
fields:
content: String
previous: Post?, relation(name=next_previous_post)
nextId: int?
next: Post?, relation(name=next_previous_post, field=nextId, onDelete=SetNull)
indexes:
next_unique_idx:
fields: nextId
unique: true

In this example, there is a named relation holding the data on both sides of the relation. The field nextId is a nullable field that stores the id of the next post. It is nullable as it would be impossible to create the first entry if we already needed to have a post created. The next post represents the object on "this" side while the previous post is the corresponding object on the "other" side. Meaning that the previous post is connected to the nextId of the post that came before it.

One-to-many

In a one-to-many self-referenced relation there is one object field connected to a list field. In this example we have modeled the relationship between a cat and her potential kittens. Each cat has at most one mother but can have n kittens, for brevity, we have only modeled the mother.

class: Cat
table: cat
fields:
name: String
mother: Cat?, relation(name=cat_kittens, optional, onDelete=SetNull)
kittens: List<Cat>?, relation(name=cat_kittens)

The field motherId: int? is injected into the dart class, the field is nullable since we marked the field mother as an optional relation. We can now find all the kittens by looking at the motherId of other cats which should match the id field of the current cat. The other cat can instead be found by looking at the motherId of the current cat and matching it against one other cat id field.

Many-to-many

Let's imagine we have a system where we have members that can block other members. We would like to be able to query who I'm blocking and who is blocking me. This can be achieved by modeling the data as a many-to-many relation ship.

Each member has a list of all other members they are blocking and another list of all members that are blocking them. But since the list side needs to point to a foreign key and cannot point to another list directly, we have to define a junction table that holds the connection between the rows.

class: Member
table: member
fields:
name: String
blocking: List<Blocking>?, relation(name=member_blocked_by_me)
blockedBy: List<Blocking>?, relation(name=member_blocking_me)
class: Blocking
table: blocking
fields:
blocked: Member?, relation(name=member_blocking_me, onDelete=Cascade)
blockedBy: Member?, relation(name=member_blocked_by_me, onDelete=Cascade)
indexes:
blocking_blocked_unique_idx:
fields: blockedId, blockedById
unique: true

The junction table has an entry for who is blocking and another for who is getting blocked. Notice that the blockedBy field in the junction table is linked to the blocking field in the member table. We have also added a combined unique constraint on both the blockedId and blockedById, this makes sure we only ever have one entry per relation, meaning I can only block one other member one time.

The cascade delete means that if a member is deleted all the blocking entries are also removed for that member.