I am struggling a bit with how to return a model that contains a parent relationship, while mapping that eagerly loaded model into a different form.
Let's consider the following 2 models: Course and User.
final class Course: Model, Content {
static let schema = "courses"
@ID(key: .id)
var id: UUID?
@Field(key: "name")
var name: String
@Parent(key: "teacher_id")
var teacher: User
init() { }
}
final class User: Model, Content {
static let schema = "users"
@ID(key: .id)
var id: UUID?
@OptionalField(key: "avatar")
var avatar: String?
@Field(key: "name")
var name: String
@Field(key: "private")
var somePrivateField: String
init() { }
}
I have a route like this, which returns an array of courses:
func list(req: Request) throws -> EventLoopFuture<[Course]> {
return Course
.query(on: req.db)
.all()
}
The resulting JSON looks something like this:
[
{
"id": 1,
"name": "Course 1",
"teacher": {
"id": 1
}
]
What I want instead is that the teacher object is returned, which is easy enough by adding .with(\.$teacher) to the query. Vapor 4 does make this very easy!
[
{
"id": 1,
"name": "Course 1",
"teacher": {
"id": 1,
"name": "User 1",
"avatar": "https://www.example.com/avatar.jpg",
"somePrivateField": "super secret internal info"
}
]
And there's my problem: the entire User object is returned, with literally all fields, even ones I don't want to make public.
What is the easiest way to transform the teacher info a different version of the User model, like PublicUser? Does that mean I have to make a DTO for the Course, map my array from [Course] to [PublicCourse], copy all properties, keep them in sync when the Course model changes, etc?
That seems like a lot of boilerplate with lots of room for mistakes in the future. Would love to hear if there are better options.