How to Make Parent-Child Relations in Vapor 2--Full Tutorial from the Beginning

288 Views

This tutorial will cover how to make parent-child relations in Vapor 2.0.

Environment
  • Xcode 8.3.2
  • Swift 3.1

In the terminal, run vapor new ParentChildTutorial --template=api.

After the project has created, cd into the project with cd ParentChildTutorial.

Next, run vapor Xcode in the terminal to generate an Xcode project. This may take some time. When the project has been created, you will see this in the terminal:

    $ Select the `Run` scheme to run.
    $ Open Xcode project?
    $ y/n>

Type y to open the project.

The project comes with a Post model and controller. You can disregard/ignore these, we will not be using them at all.

First, we will create our models. To create the User model, under the Models folder, click File -> New -> File and name it User. Next, copy and paste the code from this gist: gist for the User model into your User.swift file.

Next, we will create a Pet model. It will be very similar, with a name attribute as well, but just named Pet instead. Create a new file, call it Pet and copy and paste the following code from this gist for the Pet Model

Again, this is the most basic implementation of a model class. You may notice it is nearly identical to the User class. Next, in Config+Setup.swift, add these 2 models to your preparations,

   private func setupPreparations() throws {
        preparations.append(Post.self) // the default class vapor comes with
        preparations.append(Pet.self) // these two, pet and user, are the ones you're adding
        preparations.append(User.self)
    }

Ok, Time to create the relation!

Let's start in the Pet model. Add a new property of userId to your model, like so:

    // in Pet.swift
     var userId: Identifier

And under static let nameKey... add the following:

    // still in Pet.swift
     static let userIdKey = "user_id"

Now that we have this property, we must add it to all our init methods. Change the init(name: String) method to the following:

    init(name: String, userId: Identifier) {
        self.name = name
        self.userId = userId
    }

Next, change the init(row: Row) method to the following:

    init(row: Row) throws {
        name = try row.get(Pet.nameKey)
        userId = try row.get(Pet.userIdKey)
    }

Then change func makeRow... to:

    func makeRow() throws -> Row {
        var row = Row()
        try row.set(Pet.nameKey, name)
        try row.set(Pet.userIdKey, userId)
        return row
    }

Change the methods under the JSONConvertible extension to be the following:

   convenience init(json: JSON) throws {
     try self.init(
          name: json.get(Pet.nameKey),
          userId: json.get(Pet.userIdKey)
      )
    }
    func makeJSON() throws -> JSON {
        var json = JSON()
        try json.set(Pet.idKey, id)
        try json.set(Pet.nameKey, name)
        try json.set(Pet.userIdKey, userId)
        return json
    }

And lastly, and most importantly, change the prepare function to read:

    static func prepare(_ database: Database) throws {
        try database.create(self) { builder in
            builder.id()
            builder.string(Pet.nameKey)
            builder.foreignId(for: User.self) // this is the most important line! 
        }
    }

As noted, the builder.foreignId(for: User.self) is the line that creates the column in the database, user_id. Without this line, the relation will not work.

Now let's head to User.swift. In the User.swift file, but outside the main class definition, create the following extension:

    extension User {
        var pets: Children<User, Pet> {
            return children()
        }
     }

This computed property is how a User can access all its pets. Now, if you want to obtain a user's pets, you could do let pets = try user.pets.all() and that would yield an array of Pet objects which belong to the user.

But, if we have a pet, and we want to obtain who the owner is, what do we do? In Pet.swift, outside the class definition, add the following extension and computed property:

    extension Pet {
      var owner: Parent<Pet, User> {
          return parent(id: userId)
        }
     }

With this, now if we have a pet, and want to get the name of the owner, we could do something like:

   let owner = try pet.owner.get()
    let ownerName = owner?.name

And that's it! You can view the final code for the tutorial on GitHub here. Questions/comments below are welcome.



Enjoy this article? Consider supporting VaporForums by following us on Twitter! Get the latest Vapor articles, tutorials, and news.