How to Create Parent-Child Relations in Vapor 3

637 Views

This tutorial will cover how to create parent-child relations in Vapor 3.

Parent-Child relations are useful for when one of your model "owns" multiple objects in another model. The classic example for this is a forum website: A User object has many posts, but each post belongs to only 1 user.

Models

This tutorial will have 2 models: a Post class and a User class. Each post will belong to a user, and each user will have many posts. Our Post and User class look like this:

extension User: Content, Migration { }
    extension Post: Content, Migration { }

    final class User: SQLiteModel {
        var id: Int?
        var username: String
    
        init(username: String) {
            self.username = username
        }
    }

    final class Post: SQLiteModel {
        var id: Int?
        var title: String
        var userID: User.ID
    
        init(title: String, userID: User.ID) {
            self.title = title
            self.userID = userID
        }
    }

This is a very is a very simple setup: the User class has a username property, and the Post class has a title and userID property. This userID property will be needed to attach a user to each post that is created in the database.

Fluent offers several convenience methods for both the parent and the child that can help us in retrieving them from the database, so that we don't have to manually query. In our case, they would be written:

// 1
    extension User {
        var posts: Children  {
            return children(\.userID)
        }
    }

    // 2
    extension Post {
        var owner: Parent {
            return parent(\.userID)
        }
    }

At 1, the posts variable represents the children, and will list all objects in the Post class that have been created by a given User.

At 2, the owner variable represents the parent: it will give the parent User object for any given Post object

Accessing these properties in your controller

Below are several example routes that show us how we can access these values in our controller:

// This method lists all posts for any defined userId
     func listPostsForGivenUser(_ request: Request) throws -> Future<[Post]> {
        let userId:Int = 1
        return try User.find(userId, on: request).flatMap(to: [Post].self)  { user in // 1
            guard let unwrappedUser = user else { throw Abort.init(HTTPStatus.notFound) } // 2
            return try unwrappedUser.posts.query(on: request).all() // 3
        }
    }
    
    // This method returns the User who created any valid Post
    func listUserForGivenPost(_ request: Request) throws -> Future {
        let postId: Int = 2
        return try Post.find(postId, on: request).flatMap(to: User.self, { post -> Future in // 4
            guard let unwrappedPost = post else { throw Abort.init(HTTPStatus.notFound) } 
            return try unwrappedPost.owner.get(on: request) // 5
        })
    }

At 1, we take the userId given to us, and find the corresponding user in the database. We then call .flatMap, and we flatMap to [Post].self, because that is what we declared in the route signature.

At 2, we unwrap the optional User object

For 3, we use the variable we defined previously, `posts`, and query the database for those posts., and then return them

For 4 and 5, the formula is the same, except at 5, we use the owner helper we created above to retrieve the owner of the post, and then return that

Thanks for reading! Happy coding in Vapor 3!



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