vapor / fluent

Vapor ORM (queries, models, and relations) for NoSQL and SQL databases
https://docs.vapor.codes/4.0/fluent/overview/
MIT License
1.3k stars 171 forks source link

Only expose some field with content #723

Closed ugocottin closed 3 years ago

ugocottin commented 3 years ago

With: Vapor 4.41.4, Fluent 1.11.0

I wonder how to only expose certains field to the user after a db request, without creating a DTO struct

I have a Employee class:

final class Employee: Model, Content {

    typealias IDValue = Int

    static let schema = "employee"

    @ID(custom: "emp_id")
    var id: Int?

    @Field(key: "emp_filenbr")
    var file: Int

    @Field(key: "emp_firstname")
    var firstName: String

    /// Employee lastname
    @Field(key: "emp_lastname")
    var lastName: String

    @Parent(key: "wgr_id")
    var workGroup: WorkGroup

    init() { }
}

and the WorkGroup class:

final class WorkGroup: Model, Content {

    typealias IDValue = Int

    static let schema = "workgrp"

    @ID(custom: "wgr_id")
    var id: Int?

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

    init() { }
}

Currently, I fetch the DB with

func index(req: Request) throws -> EventLoopFuture<[Employee]> {
    return Employee
        .query(on: req.db(.fbsql))
        .all()
        .map { $0.filter { $0.file > 0 }}
}

A request will give the output:

[
    {
        "status": 0,
        "workGroup": {
            "id": 1
        },
        "firstName": "Foo",
        "id": 999,
        "file": 123456,
        "lastName": "Bar"
    }, …
]

But I want, for example, the following output:

[
    {
        "workGroup": {
            "id": 1,
            "title": "Foobar"
        },
        "firstName": "Foo",
        "id": 999
    }, …
]

Is that possible with the .field(_: ) API ? I tried with:

func index(req: Request) throws -> EventLoopFuture<[Employee]> {
    return Employee
        .query(on: req.db(.fbsql))
        .join(parent: \.$workGroup)
        .field(\.$firstName)
        .field(\.$id)
        .field(WorkGroup.self, \WorkGroup.$id)
        .field(WorkGroup.self, \WorkGroup.$title)
        .all()
}

As a result, I get a fatal error Cannot access field before it is initialized or fetched: (a field of the Employee class).

I am using the .field(_: ) right? It that possible?

siemensikkema commented 3 years ago

@Jawtoch The field API can only be used to control what goes into the model, not how it is encoded. It can be used to optimize your database queries by not loading unnecessary data. What you're observing is that the fields that are not given any value are being accessed during encoding which causes the fatal error.

You'll have to use a separate Encodable value to determine what gets output.