taken from: https://github.com/vapor/fluent

Vapor 2 — One To One Relations

Benjamin Baumann
The Swift Web Developer

--

When I created the first prototype in Vapor one of the first things that were not clear to me were how to model database relations. Maybe because I have not worked too much with databases in the past or maybe because you have to get used to the concept, or both.

This tutorial is Part 1 of my tutorials on Vapor 2 relations. You can find Part 2 here:

In the following tutorial I will cover how to model one-to-one relations Vapor2.

Project Setup

At first we create a new project with the following command:

vapor new relations — template=api
cd relations
vapor xcode
y

That’s it now we have a fresh project to start working.

One-ToOne Relation

The First and simplest relation we will look at is the one-to-one relation. It means one element has a reference to only one other element. Let’s take the example of a County which has only one capital

Country <-> Capital

We create a class called “Country” and a class called “Capital” in the folder App -> Models. We will only use one field called “name” for simplicity. Make sure they are part of the “App” target. The classes should look like this:

To model our relation we added the following field to the Country class:

/// one to one relation to capital
var capital_id : Identifier?

and to the Capital class:

/// one to one relation to country
var country_id : Identifier?

We also had to add some lines to the Model related methods:

/// Initializes the Country from the
/// database row
init(row: Row) throws {
name = try row.get(“name”)
capital_id = try row.get(“capital_id”)
}
// Serializes the Country to the database
func makeRow() throws -> Row {
var row = Row()
try row.set(“name”, name)
try row.set(“capital_id”, capital_id)
return row
}

The magic happens in the database preparation. We want to have an optional field which can be null for our foreign key to the Country table. You can achieve that like so:

/// Prepares a table/collection in the database
/// for storing Countries
static func prepare(_ database: Database) throws {
try database.create(self) { builder in
builder.id()
builder.string("name")
builder.foreignId(for: Capital.self, optional: true, unique: false, foreignIdKey: "capital_id", foreignKeyName: "capital_id")
}

Vice Versa we have to adjust also our Capital class. Please look into the example project (linked below) to see how.

We have to add our classes to the database preparations in Config+Setup.swift like so:

/// Add all models that should have their
/// schemas prepared before the app boots
private func setupPreparations() throws {
preparations.append(Post.self)
preparations.append(Country.self)
preparations.append(Capital.self)
}

Now we are prepared to store and Countries and Capitals with relationships to each other. We will demonstrate this by adding the following route to Routes.swift:

get("createCountry") { req in
let germany = Country(name: "germany")
let berlin = Capital(name: "Berlin")
try germany.save()
try berlin.save()
germany.capital_id = berlin.id
berlin.country_id = germany.id
try germany.save()
try berlin.save()
return "success"
}

That route create the country germany and the capital Berlin and it creates the relationship by adding the identifier to the foreign key. The bad thing is that you have to save twice because after the first save the database id is generated and the second save is storing the changes to the ids.

To get the objects from the database we will create also a route:

get("getCountry") {req in
//gets the first country in the database
let country = try Country.all().first!
//gets the capital of that country
let capital = try Capital.find(country.capital_id)!
return "\(country.name) has the capital \(capital.name)"
}

If you build and run and then open http://0.0.0.0:8080/createCountry and http://0.0.0.0:8080/getCountry you should get the following result:

germany has the capital Berlin

Recap

We created a simple api to create a static set of country and capital in a in memory SQLite database. The country and the capital are related with a one-to-one relationship. We also learned how to access this relationship from one of the objects and fetch the other object from the database.

If you want the project you can get if from here:

--

--