Home > Net >  Vapor, fluent save/create complex model with PostgreSQL
Vapor, fluent save/create complex model with PostgreSQL

Time:04-21

I'm trying to save model to PostgreSQL DB. The problem is, I can't properly save submodels. When I save model, it saves only ID without any relation. What am I do wrong? Request body are properly set from as POST request. I don't know how to connect model field with another model/array of models.

Create method:

func create(req: Request) async throws -> CollectionResponse {
    let todo = try req.content.decode(CollectionResponse.self)
    try await todo.save(on: req.db)
    return todo
}

Migrations

struct CreateCollection: AsyncMigration {
    func prepare(on database: Database) async throws {
        try await database.schema("collection")
            .id()
            .field("childrenArray", .custom([Object()]))
            .create()
    }

    func revert(on database: Database) async throws {
        try await database.schema("collection").delete()
    }
}

struct CreateCollectionObject: AsyncMigration {
    func prepare(on database: Database) async throws {
        try await database.schema("collectionObject")
            .id()
            .field("personalID", .string, .required)
            .field("title", .string)
            .field("thumbnail", .string)
            .field("model_a_id", .uuid, .required, .references(CollectionResponse.schema, .id))
            .create()
    }

    func revert(on database: Database) async throws {
        try await database.schema("collectionObject").delete()
    }
}

Models:

final class CollectionResponse: Model, Content, Codable{
    static var schema: String = "collection"
    
    @ID(key: .id)
    var id: UUID?
    
    @Children(for: \.$modelA)
    var childrenArray: [Object]
    
    init() { }

    init(id: UUID? = UUID(), childrenArray: [Object] = []) {
        self.id = id
        self.childrenArray = childrenArray
    }
}

final class Object: Model, Content, Codable{
    static var schema: String = "collectionObject"
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "perosnalID")
    var personalID: String?
    
    @Field(key: "title")
    var title: String?
    
    @Field(key: "thumbnail")
    var thumbnail: String?
    
    @Parent(key: "model_a_id")
    var modelA: CollectionResponse
    
    init() { }

    init(id: UUID? = UUID(), perosnalID: String?, title: String?, thumbnail: String?, modelA: UUID = UUID()) {
        self.id = id
        self.personalID = personalID
        self.title = title
        self.thumbnail = thumbnail
        self.$modelA.id = modelA
    }
}

CodePudding user response:

The save in your create route will not save any children objects in todo. This is your responsibility. I haven't changed to async methods yet, but the way to do it originally in vapor 4 is to save the parent first and then capture the id and update the children objects so that they have the correct foreign key value. Something like:

func create(req: Request) async throws -> CollectionResponse {
    let todo = try req.content.decode(CollectionResponse.self)
    return todo.save(on: req.db).flatMap { _ in
        // todo has been updated with the primary key field value
        todo.childrenArray.map{ $0.$modelA.$id = todo.$id }
        todo.childrenArray.save(on: req.db).flatMap { _ in
            return todo
        }
    }
}

Following the guidelines in https://docs.vapor.codes/4.0/fluent/model/ for naming fields and properties consistently would be a good idea. See this page for details of how the array save works on the childrenArray, it is essentially doing it as a single transaction.

  • Related