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

Vapor 2 — One To Many & Many to Many Relations

Benjamin Baumann
The Swift Web Developer

--

The following tutorial will cover the modeling and managing of One-To-Many and Many-To-Many relations in Vapor 2.

For this tutorial we will use the project we created in my first tutorial about Vapor 2 Relations and develop it a little further.

Theory

The concept for Many-To-Something relations is a little different to the concept of One-To-One relations. When we use One-To-One relations we simply store the database id of the corresponding object in another database column in the root object.

Example One to One Relation
The Example displays how the country Germany has a one-to-one relation to the city Berlin and vice versa
Country Table
+-----+---------+------------+
| id | name | capital_id |
+-----+---------+------------+
| 1 | Germany | 1 |
+-----+---------+------------+
Capital Table
+-----+--------+------------+
| id | name | country_id |
+-----+--------+------------+
| 1 | Berlin | 1 |
+-----+--------+------------+

One-to-Many relations however need another table to resemble the connections between the objects. As an example we will take an author-book relationship. One author can have multiple books but (for simplicity) one book can only have one author.

Author Table
+-----+-----------+
| id | name |
+-----+-----------+
| 1 | Hemingway |
+-----+-----------+
Books Table
+-----+------------------------+
| id | name |
+-----+------------------------+
| 1 | In Our Time |
| 2 | The Torrents of Spring |
| 3 | The Sun Also Rises |
+-----+------------------------+
Pivot Table Author_Books
+-----+-----------+---------+
| id | author_id | book_id |
+-----+-----------+---------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
+-----+-----------+---------+

A Many-to-Many relation just uses another pivot table. Let’s imagine one book can have multiple authors (like in real life) we would have just another Pivot table for Book_Author which stores the corresponding ids. For the coding example we will only use One-To-Many relationships for simplicity.

Code Example

To stay in the example of the last project with Country and Capitals we will create another object named States. A state can have one country but a country can have multiple states. E.g. USA: Ohio, Colorado, Flordia, etc…

Let’s create new Model called State in the App>Models folder and add all the Model related methods.

In Config+Setup.swift we have to modify the setupPreparations() function:

/// 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)
preparations.append(State.self)
preparations.append(Pivot<Country, State>.self)
}

To prepare the database we add preparations for State as usual. The magic happens here in the las line where we add a Pivot table to our database preparations with the corresponding objects. That’s it. Our database is prepared.

Let’s now create a route to create actual states. We will modify our “createCountry” route in 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
//creating the states Bavaria, Thuringia and Saxony
let bavaria = State(name: "Bavaria")
try bavaria.save()
let pivotBavaria = try Pivot<Country, State>(germany, bavaria)
try pivotBavaria.save()
let thuringia = State(name: "Thuringia")
try thuringia.save()
let pivotThuringia = try Pivot<Country, State>(germany, thuringia)
try pivotThuringia.save()
let saxony = State(name: "Saxony")
try saxony.save()
let pivotSaxony = try Pivot<Country, State>(germany, saxony)
try pivotSaxony.save()
try germany.save()
try berlin.save()
return "success"}

In the above code example we create the entries in the pivot tables and let Fluent take responsibility to store it correctly in our database. Easy.

Now as last step we want to access the relations and get all the objects out of the database. For that we have to modify our Country model and add the following lines:

//the method to retrieve all states assigned to the country from the database
func states() throws -> [State] {
let states: Siblings<Country, State, Pivot<Country, State>> = siblings()
return try states.all()
}

This method performs a database query handled by Vapor which returns the siblings of the country. With the states.all() method call we get an array out of the siblings.

As a finishing touch we modify our “getCountry” route to expose the relations via a GET request.

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)!
let states = try country.states().makeJSON()
return "\(country.name) has the capital \(capital.name) with states: \(states)"
}

If you run now http://0.0.0.0:8080/createCountry and then http://0.0.0.0:8080/getCountry you should see an output like this:

germany has the capital Berlin with states: JSON(wrapped: [[id: 1, name: Bavaria], [id: 2, name: Thuringia], [id: 3, name: Saxony]], context: JSON.JSONContext)

Voilá we have a working One-To-Many relationship.

Recap

In this tutorial we learned how to model One-To-Many and therefore Many-To-Many relationships in Vapor 2. Also we learned how to create and access the pivot tables which are needed to model the relationships. In the finishing touch we added the access of those relations and the retrieval of the objects from the database.

If you are interested you can check out the finished project here:

--

--