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 Comment
entity does not conform to PivotProtocol
, the
add
, remove
, and isAttached
methods will not be available.