Relaties¶
Fluent's model API helpt u bij het maken en onderhouden van verwijzingen tussen uw modellen door middel van relaties. Drie typen relaties worden ondersteund:
Parent¶
De @Parent
relatie slaat een verwijzing op naar de @ID
eigenschap van een ander model.
final class Planet: Model {
// Voorbeeld van een parent relatie.
@Parent(key: "star_id")
var star: Star
}
@Parent
bevat een @Field
genaamd id
welke wordt gebruikt voor het instellen en bijwerken van de relatie.
// Stel parent relatie id in
earth.$star.id = sun.id
Bijvoorbeeld, de Planet
initializer zou er zo uitzien:
init(name: String, starID: Star.IDValue) {
self.name = name
// ...
self.$star.id = starID
}
De key
parameter definieert de veldsleutel om te gebruiken voor het opslaan van de identifier van de parent. Ervan uitgaande dat Star
een UUID
identifier heeft, is deze @Parent
relatie compatibel met de volgende veld definitie.
.field("star_id", .uuid, .required, .references("star", "id"))
Merk op dat de .references
constraint optioneel is. Zie schema voor meer informatie.
Optionele Parent¶
De @OptionalParent
relatie slaat een optionele verwijzing op naar de @ID
eigenschap van een ander model. Het werkt hetzelfde als @Parent
maar staat toe dat de relatie nil
is.
final class Planet: Model {
// Voorbeeld van een optionele parent relatie.
@OptionalParent(key: "star_id")
var star: Star?
}
De velddefinitie is gelijk aan die van @Parent
, behalve dat de .required
restrictie moet worden weggelaten.
.field("star_id", .uuid, .references("star", "id"))
Optionele Child¶
De @OptionalChild
eigenschap creëert een één-op-één relatie tussen de twee modellen. Het slaat geen waarden op in het root model.
final class Planet: Model {
// Voorbeeld van een optionele child relatie.
@OptionalChild(for: \.$planet)
var governor: Governor?
}
De for
parameter accepteert een sleutelpad naar een @Parent
of @OptionalParent
relatie die verwijst naar het root model.
Een nieuw model kan aan deze relatie worden toegevoegd met de create
methode.
// Voorbeeld van het toevoegen van een nieuw model aan een relatie.
let jane = Governor(name: "Jane Doe")
try await mars.$governor.create(jane, on: database)
Dit zal de parent id op het child model automatisch instellen.
Aangezien deze relatie geen waarden opslaat, is er geen databaseschema-item vereist voor het basismodel.
Het één-op-één-karakter van de relatie moet in het schema van het child-model worden afgedwongen met een .unique
restrictie op de kolom die naar het parent-model verwijst.
try await database.schema(Governor.schema)
.id()
.field("name", .string, .required)
.field("planet_id", .uuid, .required, .references("planets", "id"))
// Voorbeeld van unieke beperking
.unique(on: "planet_id")
.create()
Waarschuwing
Het weglaten van de unieke beperking op het parent ID veld uit het schema van de klant kan leiden tot onvoorspelbare resultaten.
Als er geen uniciteits beperking is, kan de child tabel meer dan een child rij bevatten voor een gegeven parent; in dit geval zal een @OptionalChild
eigenschap nog steeds slechts toegang hebben tot één child per keer, met geen enkele manier om te controleren welk child geladen wordt. Als je meerdere child rijen voor een bepaalde parent moet opslaan, gebruik dan @Children
.
Children¶
De @Children
eigenschap creëert een één-op-veel relatie tussen twee modellen. Het slaat geen waarden op in het root model.
final class Star: Model {
// Voorbeeld van een children relatie.
@Children(for: \.$star)
var planets: [Planet]
}
De for
parameter accepteert een sleutelpad naar een @Parent
of @OptionalParent
relatie die verwijst naar het root model. In dit geval verwijzen we naar de @Parent
relatie uit het vorige voorbeeld.
Nieuwe modellen kunnen aan deze relatie worden toegevoegd met de create
methode.
// Voorbeeld van het toevoegen van een nieuw model aan een relatie.
let earth = Planet(name: "Earth")
try await sun.$planets.create(earth, on: database)
Dit zal de parent id op het child model automatisch instellen.
Aangezien deze relatie geen waarden opslaat, is er geen invoer in het databaseschema vereist.
Siblings¶
De @Siblings
eigenschap creëert een veel-op-veel relatie tussen twee modellen. Het doet dit door middel van een tertiair model, een pivot genaamd.
Laten we eens kijken naar een voorbeeld van een veel-op-veel relatie tussen een Planet
en een Tag
.
enum PlanetTagStatus: String, Codable { case accepted, pending }
// Voorbeeld van een pivot model.
final class PlanetTag: Model {
static let schema = "planet+tag"
@ID(key: .id)
var id: UUID?
@Parent(key: "planet_id")
var planet: Planet
@Parent(key: "tag_id")
var tag: Tag
@OptionalField(key: "comments")
var comments: String?
@OptionalEnum(key: "status")
var status: PlanetTagStatus?
init() { }
init(id: UUID? = nil, planet: Planet, tag: Tag, comments: String?, status: PlanetTagStatus?) throws {
self.id = id
self.$planet.id = try planet.requireID()
self.$tag.id = try tag.requireID()
self.comments = comments
self.status = status
}
}
Elk model dat tenminste twee @Parent
relaties bevat, één voor elk model dat gerelateerd moet worden, kan gebruikt worden als pivot. Het model kan aanvullende eigenschappen bevatten, zoals zijn ID, en kan zelfs andere @Parent
relaties bevatten.
Het toevoegen van een unieke constraint aan het pivot model kan helpen om overbodige entries te voorkomen. Zie schema voor meer informatie.
// Staat duplicaat relaties niet toe.
.unique(on: "planet_id", "tag_id")
Zodra de pivot is gemaakt, gebruik de @Siblings
eigenschap om de relatie te maken.
final class Planet: Model {
// Voorbeeld van een siblings relatie.
@Siblings(through: PlanetTag.self, from: \.$planet, to: \.$tag)
public var tags: [Tag]
}
De @Siblings
eigenschap vereist drie parameters:
through
: Het type van het pivot model.from
: Sleutelpad van de pivot naar de parent-relatie die verwijst naar het basismodel.to
: Sleutelpad van de pivot naar de parent relation die naar het gerelateerde model verwijst.
De inverse @Siblings
eigenschap op het verwante model maakt de relatie compleet.
final class Tag: Model {
// Voorbeeld van een siblings relatie.
@Siblings(through: PlanetTag.self, from: \.$tag, to: \.$planet)
public var planets: [Planet]
}
Siblings Attach¶
De @Siblings
eigenschap heeft methoden voor het toevoegen en verwijderen van modellen uit de relatie.
Gebruik de attach()
methode om een enkel model of een array van modellen toe te voegen aan de relatie. Pivot modellen worden indien nodig automatisch aangemaakt en opgeslagen. Er kan een callback closure worden gespecificeerd om aanvullende eigenschappen van elke gecreëerde pivot in te vullen:
let earth: Planet = ...
let inhabited: Tag = ...
// Voegt het model toe aan de relatie.
try await earth.$tags.attach(inhabited, on: database)
// Vul de pivot attributen in bij het maken van de relatie.
try await earth.$tags.attach(inhabited, on: database) { pivot in
pivot.comments = "This is a life-bearing planet."
pivot.status = .accepted
}
// Voeg meerdere modellen met attributen toe aan de relatie.
let volcanic: Tag = ..., oceanic: Tag = ...
try await earth.$tags.attach([volcanic, oceanic], on: database) { pivot in
pivot.comments = "This planet has a tag named \(pivot.$tag.name)."
pivot.status = .pending
}
Bij het koppelen van een enkel model, kunt u de method
parameter gebruiken om te kiezen of de relatie wel of niet gecontroleerd moet worden voor het opslaan.
// Koppelt alleen als de relatie nog niet bestaat.
try await earth.$tags.attach(inhabited, method: .ifNotExists, on: database)
Gebruik de detach
methode om een model uit de relatie te verwijderen. Dit verwijdert het corresponderende pivot model.
// Verwijdert het model uit de relatie.
try await earth.$tags.detach(inhabited, on: database)
Je kunt controleren of een model gerelateerd is of niet met de isAttached
methode.
// Controleert of de modellen verwant zijn.
earth.$tags.isAttached(to: inhabited)
Get¶
Gebruik de get(on:)
methode om de waarde van een relatie op te halen.
// Haalt alle planeten van de zon op.
sun.$planets.get(on: database).map { planets in
print(planets)
}
// Of
let planets = try await sun.$planets.get(on: database)
print(planets)
Gebruik de reload
parameter om te kiezen of de relatie wel of niet opnieuw moet worden opgehaald uit de database als deze al eerder is geladen.
try await sun.$planets.get(reload: true, on: database)
Query¶
Gebruik de query(on:)
methode op een relatie om een query builder te maken voor de gerelateerde modellen.
// Haal alle planeten van de zon op die een naam hebben die begint met M.
try await sun.$planets.query(on: database).filter(\.$name =~ "M").all()
Zie query voor meer informatie.
Eager Loading¶
Fluent's query bouwer maakt het mogelijk om de relaties van een model vooraf te laden wanneer het wordt opgehaald uit de database. Dit wordt eager loading genoemd en stelt u in staat om synchroon relaties te benaderen zonder dat u eerst load
of get
hoeft aan te roepen.
Om een relatie eager te laden, geef je een sleutelpad naar de relatie door aan de with
methode op query builder.
// Voorbeeld van eager loading.
Planet.query(on: database).with(\.$star).all().map { planets in
for planet in planets {
// `star` is hier synchroon toegankelijk
// omdat het eager geladen is.
print(planet.star.name)
}
}
// Of
let planets = try await Planet.query(on: database).with(\.$star).all()
for planet in planets {
// `star` is hier synchroon toegankelijk
// omdat het eager geladen is.
print(planet.star.name)
}
In het bovenstaande voorbeeld wordt een sleutelpad naar de @Parent
relatie genaamd star
doorgegeven aan with
. Dit zorgt ervoor dat de query bouwer een extra query uitvoert nadat alle planeten zijn geladen om al hun gerelateerde sterren op te halen. De sterren zijn dan synchroon toegankelijk via de @Parent
eigenschap.
Elke relatie die eager wordt geladen vereist slechts één extra query, ongeacht hoeveel modellen er worden geretourneerd. Eager loading is alleen mogelijk met de all
en first
methodes van query builder.
Nested Eager Load¶
Met de with
methode van de query bouwer kunnen relaties eager geladen worden op het model waarop de query betrekking heeft. U kunt echter ook relaties op gerelateerde modellen eager laden.
let planets = try await Planet.query(on: database).with(\.$star) { star in
star.with(\.$galaxy)
}.all()
for planet in planets {
// `star` is hier synchroon toegankelijk
// omdat het eager geladen is.
print(planet.star.galaxy.name)
}
De with
methode accepteert een optionele closure als tweede parameter. Deze closure accepteert een eager load builder voor de gekozen relatie. Er is geen limiet aan hoe diep eager loading genest kan worden.
Lazy Eager Loading¶
In het geval dat u het bovenliggende model al heeft opgehaald en u wilt een van zijn relaties laden, dan kunt u de load(on:)
methode voor dat doel gebruiken. Hiermee wordt het gerelateerde model opgehaald uit de database en kan het worden benaderd als een lokale eigenschap.
planet.$star.load(on: database).map {
print(planet.star.name)
}
// Of
try await planet.$star.load(on: database)
print(planet.star.name)
Om te controleren of een relatie al dan niet geladen is, gebruik je de value
eigenschap.
if planet.$star.value != nil {
// Relatie is geladen.
print(planet.star.name)
} else {
// De relatie is niet geladen.
// Pogingen om toegang te krijgen tot planet.star zullen mislukken.
}
Als u het gerelateerde model al in een variabele heeft, kunt u de relatie handmatig instellen met behulp van de value
eigenschap die hierboven is genoemd.
planet.$star.value = star
Dit zal het gerelateerde model aan de ouder koppelen alsof het eager loaded of lazy loaded was zonder een extra database query.