Skip to content

Relations

Fluent relations allow you to relate your models in three different ways:

Type Relations
One to One Parent / Child
One to Many Parent / Children
Many to Many Siblings

One to Many

We'll start with one-to-many since it's the easiest type of relation to understand.

Take the following database schema:

users

id name
<id type> string

pets

id name user_id
<id type> string <id type>

Seealso

Visit the database preparations guide for more information on how to create schema.

Here each pet has exactly one owner (a user) and each owner can have multiple pets. This is a one-to-many relationship. One owner has many pets.

Tip

Use the builder.foreignId() to create foreign ids like user_id. This will automatically create foreign key constraints and follow pre-set key naming conventions.

Children

To access the user's pets, we will use the Children relation.

extension User {
    var pets: Children<User, Pet> {
        return children()
    }
}

Imagine the children relation as Children<Parent, Child> or Children<From, To>. Here we are relating from the user type to the pet type.

We can now use this relation to get all of the user's pets.

let pets = try user.pets.all() // [Pet]

This will create SQL similar to:

SELECT * FROM `pets` WHERE `user_id` = '...';

Relations work similarly to queries.

let pet = try user.pets.filter("name", "Spud").first()

Parent

To access a pet's owner from the pet, we will use the Parent relation.

extension Pet {
    let userId: Identifier

    ...

    var owner: Parent<Pet, User> {
        return parent(id: userId)
    }
}

Imagine the parent relation as Parent<Child, Parent> or Parent<From, To>. Here we are relating from the pet type to the parent type.

Note

Notice the Parent relation requires an identifier to be passed in. Make sure to load this identifier in your model's init(row:) method.

We can now use this relation to get the pet's owner.

let owner = try pet.owner.get() // User?

Migration

Adding a parent identifier to the child table can be done using the .parent() method on the schema builder.

try database.create(Pet.self) { builder in
    ...
    builder.parent(User.self)
}

One to One

One-to-one relations work exactly the same as one-to-many relations. You can use the code from the previous example and simply call .first() and all calls from the parent type.

However, you can add a convenience for doing this. Let's assume we wanted to change the previous example from one-to-many to one-to-one.

extension User {
   func pet() throws -> Pet? {
        return try children().first()
   } 
}

Many to Many

Many to many relations require a table in between to store which model is related to which. This table is called a pivot table.

You can use any entity you want as a pivot, but Fluent provides a default one called Pivot.

Take the following schema.

pets

id name
<id type> string

pet_toy

id pet_id toy_id
<id type> <id type> <id type>

toys

id name
<id type> string

Here each pet can own many toys and each toy can belong to many pets. This is a many-to-many relationship.

Siblings

To represent this many-to-many relationship, we will use the Siblings relation.

extension Pet {
    var toys: Siblings<Pet, Toy, Pivot<Pet, Toy>> {
        return siblings()
    }
}

Imagine the siblings relations as Siblings<From, To, Through>. Here we are relating from the pet type to the toy type through the pet/toy pivot.

Note

The generic syntax might look a little intimidating at first, but it allows for a very powerful API.

With this relation added on pets, we can fetch a pet's toys.

let toys = pet.toys.all() // [Toy]

The siblings relation works similarly to queries and parent/children relations.

Migration

If you are using a Pivot type, you can simply add it to your Droplet's preparation array.

drop.preparations.append(Pivot<Pet, Toy>.self)

If you are using a Pivot for your "through" model, it will also have methods for adding and removing models from the relation.

Add

To add a new model to the relation, use the .add() method.

try pet.toys.add(toy)

Note

The newly created pivot will be returned.

Remove

To remove a model from being related, use the .remove() method.

try pet.toys.remove(toy)

Is Attached

To check if a model is related, use the .isAttached() method.

if try pet.toys.isAttached(to: toy) {
    // it is attached
}

Custom Through

You can use any entity type as the "through" entity in your siblings relation.

extension User {
    var posts: Siblings<User, Post, Comment> {
        return siblings()
    }
}

In the above example we are pivoting on the comments entity to retreive all posts the user has commented on.

As long as the "through" entity has a user_id and post_id, the siblings relation will work.

Note

If the Commententity does not conform to PivotProtocol, the add, remove, and isAttached methods will not be available.