Логика, которую вы ищете, будет выглядеть так
extension Course {
func convertToPublicCourseData(req: Request) throws -> Future<PublicCourseData> {
return teacher.get(on: req).flatMap { teacher in
return try CourseUser.query(on: req)
.filter(\.courseID == self.requireID())
.all().flatMap { courseUsers in
// here we should query a user for each courseUser
// and only then convert all of them into PublicCourseData
// but it will execute a lot of queries and it's not a good idea
}
}
}
}
Я предлагаю вам использовать SwifQL lib вместо того, чтобы создавать пользовательский запрос для получения необходимых данных в одном запросе ?
Вы можете смешивать запросы Fluent с SwifQL на тот случай, если вы хотите получить только один курс, то есть 2 запроса:
struct Student: Content {
let name: String
let progress: Int
}
extension Course {
func convertToPublicCourseData(req: Request) throws -> Future<PublicCourseData> {
return teacher.get(on: req).flatMap { teacher in
// we could use SwifQL here to query students in one request
return SwifQL.select(\CourseUser.progress, \User.name)
.from(CourseUser.table)
.join(.inner, User.table, on: \CourseUser.userID == \User.id)
.execute(on: req, as: .psql)
.all(decoding: Student.self).map { students in
return try PublicCourseData(id: self.requireID(),
name: self.name,
teacher: teacher,
students: students)
}
}
}
}
Если вы хотите получить список курсов в одном запросе, вы можете использовать чистый SwifQL
запрос.
Я немного упростил желаемый JSON
{
"id": 1,
"name": "Course 1",
"teacher": {"name": "Mr. Teacher"},
"students": [
{"name": "Student 1", progress: 10},
{"name": "Student 2", progress: 60},
]
}
Прежде всего давайте создадим модель, которая сможет декодировать в нее результат запроса
struct CoursePublic: Content {
let id: Int
let name: String
struct Teacher:: Codable {
let name: String
}
let teacher: Teacher
struct Student:: Codable {
let name: String
let progress: Int
}
let students: [Student]
}
Хорошо, теперь мы готовы создать собственный запрос. Давайте построим это в некоторой функции обработчика запросов
func getCourses(_ req: Request) throws -> Future<[CoursePublic]> {
/// create an alias for student
let s = User.as("student")
/// build a PostgreSQL's json object for student
let studentObject = PgJsonObject()
.field(key: "name", value: s~\.name)
.field(key: "progress", value: \CourseUser.progress)
/// Build students subquery
let studentsSubQuery = SwifQL
.select(Fn.coalesce(Fn.jsonb_agg(studentObject),
PgArray(emptyMode: .dollar) => .jsonb))
.from(s.table)
.where(s~\.id == \CourseUser.userID)
/// Finally build the whole query
let query = SwifQLSelectBuilder()
.select(\Course.id, \Course.name)
.select(Fn.to_jsonb(User.table) => "teacher")
.select(|studentsSubQuery| => "students")
.from(User.table)
.join(.inner, User.table, on: \Course.teacherId == \User.id)
.join(.leftOuter, CourseUser.table, on: \CourseUser.teacherId == \User.id)
.build()
/// this way you could print raw query
/// to execute it in postgres manually
/// for debugging purposes (e.g. in Postico app)
print("raw query: " + query.prepare(.psql).plain)
/// executes query with postgres dialect
return query.execute(on: req, as: .psql)
/// requests an array of results (or use .first if you need only one first row)
/// You also could decode query results into the custom struct
.all(decoding: CoursePublic.self)
}
Надеюсь, это поможет вам. В запросе могут быть некоторые ошибки, потому что я написал его без проверки. Вы можете попытаться распечатать необработанный запрос, чтобы скопировать его и выполнить, например, в. Приложение Postico в postgres напрямую, чтобы понять, что не так.