我试图提出最基本的例子,以解决我的问题。我有一个Course
模型和一个User
的多对多表,该表还存储了一些额外的属性(在下面的示例中为progress
)。
import FluentPostgreSQL
import Vapor
final class Course: Codable, PostgreSQLModel {
var id: Int?
var name: String
var teacherId: User.ID
var teacher: Parent<Course, User> {
return parent(\.teacherId)
}
init(name: String, teacherId: User.ID) {
self.name = name
self.teacherId = teacherId
}
}
struct CourseUser: Pivot, PostgreSQLModel {
typealias Left = Course
typealias Right = User
static var leftIDKey: LeftIDKey = \.courseID
static var rightIDKey: RightIDKey = \.userID
var id: Int?
var courseID: Int
var userID: UUID
var progress: Int
var user: Parent<CourseUser, User> {
return parent(\.userID)
}
}
现在,当我返回一个Course
对象时,我希望JSON输出是这样的:
{
"id": 1,
"name": "Course 1",
"teacher": {"name": "Mr. Teacher"},
"students": [
{"user": {"name": "Student 1"}, progress: 10},
{"user": {"name": "Student 2"}, progress: 60},
]
}
这不是我通常会得到的,而是:
{
"id": 1,
"name": "Course 1",
"teacherID": 1,
}
所以我创建了一些额外的模型和一个在它们之间转换的函数:
struct PublicCourseData: Content {
var id: Int?
let name: String
let teacher: User
let students: [Student]?
}
struct Student: Content {
let user: User
let progress: Int
}
extension Course {
func convertToPublicCourseData(req: Request) throws -> Future<PublicCourseData> {
let teacherQuery = self.teacher.get(on: req)
let studentsQuery = try CourseUser.query(on: req).filter(\.courseID == self.requireID()).all()
return map(to: PublicCourseData.self, teacherQuery, studentsQuery) { (teacher, students) in
return try PublicCourseData(id: self.requireID(),
name: self.name,
teacher: teacher,
students: nil) // <- students is the wrong type!
}
}
}
现在,我几乎在这里了,但我无法将studentsQuery
从EventLoopFuture<[CourseUser]>
转换为EventLoopFuture<[Student]>
。我尝试了map
和flatMap
的多种组合,但是我不知道如何将一系列期货转换成不同期货的阵列。
答案 0 :(得分:2)
您要寻找的逻辑将如下所示
> foo <- data.frame(cat1 = c("a", "b", "c"),
cat2 = c("two", "two", "one"),
cat3 = c("alpha", "beta", "beta"))
> foo2 <- expand.grid(c("a", "b", "c"),
c("one", "two"),
c("alpha", "beta"))
我建议您改用SwifQL lib来构建自定义查询以在一个请求中获取所需数据
如果您只想获得一门课程,可以将Fluent的查询与SwifQL的查询混合使用,因此您可以在2个请求中得到它:
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
}
}
}
}
如果要在一个请求中获得课程列表,则可以使用纯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)
}
}
}
}
查询。
我简化了所需的JSON
SwifQL
首先让我们创建一个模型,以便将查询结果解码到其中
{
"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]
}
希望它会对您有所帮助。查询中可能存在一些错误,因为我没有检查就写了它。您可以尝试打印原始查询来复制并执行,例如直接在postgres中使用Postico应用来了解问题所在。