Search code examples
swiftvaporserver-side-swiftvapor-fluent

Manually modifying model property values in vapor 4 response


I have a vapor 4 application. I do a query from database for getting some items and I want to perform some manual calculation based on the returned values before finishing the request. here a sample code of what I am trying to achieve.

final class Todo: Model, Content {

    static var schema: String = "todos"

    @ID(custom: .id)
    var id: Int?

    @Field(key: "title")
    var title: String

    var someValue: Int?

}

/// Allows `Todo` to be used as a dynamic migration.
struct CreateTodo: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        database.schema(Todo.schema)
            .field("id", .int, .identifier(auto: true))
            .field("title", .string, .required)
            .create()
    }

    func revert(on database: Database) -> EventLoopFuture<Void> {
        database.schema(Todo.schema).delete()
    }
}
final class TodoController:RouteCollection{

    func boot(routes: RoutesBuilder) throws {

        routes.get("tmp", use: temp)
    }

    func temp(_ req:Request) throws -> EventLoopFuture<[Todo]> {

        Todo.query(on: req.db).all().map { todos in

            todos.map {
                $0.someValue = (0...10).randomElement()!
                return $0
            }
        }
    }

}

The problem is that those manual changes, aren't available in response. In this case someValue property.

Thanks.

[
    {
        "title": "item 1",
        "id": 1
    },
    {
        "title": "item 2",
        "id": 2
    }
]

Solution

  • The problem you're hitting is that Models override the Codable implementations. This allows you to do things like passing around parents and not adding children etc.

    However, this breaks your case. What you should do, is create a new type if you want to return a Todo with another field that isn't stored in the database, something like:

    struct TodoResponse: Content {
      let id: Int
      let title: String
      let someValue: Int
    }
    

    And then convert from your database type to your response type in your route handler (this is a pretty common pattern and the recommended way to do it in Vapor)