Search code examples
swiftvapor

How to save a model with a child and get the saved model with child back as a response in Vapor 3


I'm trying to write a controller function to insert a model with a child model at the same time and get the same JSON response in return, but filled with the data added from the database (i.e. the automatically generated id).

For the bellow simplified example the JSON request body would look like this:

{
    "field1": "blabla",
    "subContent": {
        "field2": "lalala"
    }
}

I'm expecting the following response:

{
    "id": 5,
    "field1": "blabla",
    "subContent": {
        "id": 44,
        "field2": "lalala"
    }
}

The below code unfortunately doesn't compile, the error message says "Cannot convert expression of type 'EventLoopFuture' to return type 'Response'. This occurs on line

return subModel.create(on: req).flatMap { savedSubModel ->

Here's the code:

struct MainContent: Content {
    var id: UUID?
    var field1: String
    var subContent: SubContent
}

struct SubContent: Content {
    var id: UUID?
    var field2: String
}

final class Controller {
    func create(_ req: Request) throws -> EventLoopFuture<Response> {
        return try req.content.decode(MainContent.self).map { request in
            let subModel = SubModel(
                field2: request.subContent.field2
            )
            return subModel.create(on: req).flatMap { savedSubModel -> EventLoopFuture<Response> in
                let mainModel = mainModel(
                    subModelId: savedSubModel.id!,
                    field1: request.field1
                )
                return mainModel.create(on: req).flatMap { savedMainModel -> EventLoopFuture<Response> in
                    let content = MainContent(
                        id: savedMainModel.id,
                        field1: savedMainModel.field1,
                        subContent: SubContent(
                            id: savedSubContent.id,
                            field2: savedSubContent.field2
                        )
                    )
                    return content.encode(status: .created, for: req)
                }
            }
        }
    }
}

Solution

  • struct MainContent: Content {
        var id: UUID?
        var field1: String
        var subContent: SubContent
    }
    
    struct SubContent: Content {
        var id: UUID?
        var field2: String
    }
    
    final class Controller {
        func create(_ req: Request) throws -> EventLoopFuture<Response> {
            return try req.content.decode(MainContent.self).flatMap { request in
                let subModel = SubModel(
                    field2: payload.subContent.field2
                )
                return subModel.create(on: req)
            }.flatMap { savedSubModel in
                let mainModel = mainModel(
                    subModelId: try savedSubModel.requireID(),
                    field1: request.field1
                )
                return mainModel.create(on: req).flatMap { savedMainModel in
                    let content = MainContent(
                        id: try savedMainModel.requireID(),
                        field1: savedMainModel.field1,
                        subContent: SubContent(
                            id: try savedSubModel.requireID(),
                            field2: savedSubModel.field2
                        )
                    )
                    return content.encode(status: .created, for: req)
                }
            }
        }
    }