How do I update the children in a one-to-many relationship in Vapor 4/FluentKit?

Viewed 308

I've combed through the docs here and I can't quite figure out how to update the relationship and the model (e.g., there are changes to the relation and a change to a property). It appears that the "childrenArray" in ModelA is empty after decoding, however, I can't be sure because I get this error when attempting to print the value:

Fatal error: Children relation not eager loaded, use $ prefix to access

My parent model:

final class ModelA: Model, Content {
    static let schema = "model_a"
    
    // MARK: - PROPERTIES
    
    @ID(key: .id)
    var id: UUID?

    @Field(key: "name")
    var name: String
    
    // MARK: - RELATIONS
    
    @Children(for: \.$modelA)
    var childrenArray: [ModelB]

    init() { }

    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}

The model for the children:

final class ModelB: Model, Content {
    static let schema = "model_b"

    // MARK: - Properties
    
    @ID(key: .id)
    var id: UUID?

    @Field(key: "command")
    var command: String
    
    // MARK: - Relations
    
    @Parent(key: "model_a_id")
    var modelA: ModelA
}

My update function:

func update(req: Request) throws -> EventLoopFuture<ModelA> {
        guard let id = req.parameters.get("id", as: UUID.self) else {
            throw Abort(.internalServerError)
        }
        
        let updatedModelA = try req.content.decode(ModelA.self)
        return ModelA.find(id, on: req.db)
            .unwrap(or: Abort(.notFound))
            .flatMap { modelA in
                modelA.$childrenArray.create(updatedModelA.childrenArray, on: req.db)
                return modelA.update(on: req.db).map { modelA }
            }
    }

For reference, the Postman POST request looks like this:

{
    "id": "<ModelA's UUID>",
    "name": "ModelA Name",
    "childrenArray": [{
        "id": "{{$randomUUID}}" ,
        "command": ""
    }]
}
1 Answers

To complete the 'eager load', you need to include a with on the parent query. You can't do this with a find so you will need to change your query to:

ModelA.query(on: req.db)
    .filter(\.$id == id)
    .with(\.$childrenArray)
    .first()
    .unwrap(or: Abort(.notFound))
    .flatMap { modelA in
        // can access children now
        return modelA.update(on: req.db).map { modelA }
    }

I assume you want this line to create children in ModelB:

modelA.$childrenArray.create(updatedModelA.childrenArray, on: req.db)

It won't. You'll need to create a new instance of ModelB, initialise the parent property and then call create.

Related