Relation
Relations allow foreign key based connections between database entities. This is common in SQL-based databases, but can also be used with NoSQL.
Fluent's relations are named as follows:
- Parent (BelongsTo)
- Children (HasMany, HasOne)
- Siblings (ManyToMany, BelongsToMany)
Parent
The parent relation should be called on an entity that has a foreign key to another entity. For example, assume the following schema:
pets
- id
- owner_id
- name
- type
owner
- id
- name
Here each pet can have one owner. To access that owner from the pet, call .parent()
.
let pet: Pet = ...
let owner = try pet.parent(pet.ownerId, Owner.self).get()
The parent method requires the foreign key for the parent as well as the type.
Convenience
To make requesting a parent easier, a method can be added to the model.
extension Pet {
func owner() throws -> Parent<Owner> {
return try parent(ownerId)
}
}
Since we are extending Pet
, we no longer need to use pet.
before the ownerId
. Furthermore, because we are providing the type information about Owner
in the return type of the method, we no longer need to pass that as an argument.
The Parent<T>
type is a queryable object, meaning you could delete()
the parent, filter()
, etc.
try pet.owner().delete()
To fetch the parent, you must call get()
.
let owner = try pet.owner().get()
Children
Children
are the opposite side of the Parent
relationship. Assuming the schema from the previous example, the pets could be retrieved from an owner like so:
let owner: Owner = ...
let pets = owner.children(Pet.self).all()
Here only the type of child is required.
Convenience
Similarly to Parent
, convenience methods can be added for children.
extension Owner {
func pets() throws -> Children<Pet> {
return try children()
}
}
Since the type information is clear from the return value, Pet.self
does not need to be passed to children()
.
The Children<T>
is also a queryable object like Parent<T>
. You can call first()
, all()
, filter()
, etc.
let coolPets = try owner.pets().filter("type", .in, ["Dog", "Ferret"]).all()
Siblings
Siblings
work differently from Children
or Parent
since they require a Pivot
.
For an example, let's say we want to allow our pets to have multiple toys. But we also want the toys to be shared by multiple pets. We need a pivot entity for this.
pets
- id
- type
- owner_id
toys
- id
- name
pets_toys
- id
- pet_id
- toy_id
Here you can see the pivot entity, pets_toys
, or Pivot<Pet, Toy>
.
Convenience
Let's add the convenience methods to Pet
.
extension Pet {
func toys() throws -> Siblings<Toy> {
return try siblings()
}
}
And the opposite for Toy
.
extension Toy {
func pets() throws -> Siblings<Pet> {
return try siblings()
}
}
Now you are free to query pets and toys similarly to children.
let pet: Pet = ...
let toys = pet.toys().all()
To create a new many-to-many relationship you can do the following.
var toy: Toy = ... // Create a new toy
try toy.save() // Save the toy to the db
var pet: Pet = ... // Create a new pet
try pet.save() // Save the pet to the db
// Link them together in the db
var pivot = Pivot<Toy, Pet>(toy, pet) // Create the relationship
try pivot.save() // Save the relationship to the db
Preparation
To prepare for a relationship with a Pivot
, simply add the pivot to the Droplet
's preparations.
let drop = Droplet()
drop.preparations += [
Toy.self,
Pet.self,
Pivot<Toy, Pet>.self
]