蒸气3:将Future对象的数组转换为Future其他对象的数组

时间:2019-03-21 14:07:22

标签: vapor vapor-fluent

我试图提出最基本的例子,以解决我的问题。我有一个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!
    }
  }
}

现在,我几乎在这里了,但我无法将studentsQueryEventLoopFuture<[CourseUser]>转换为EventLoopFuture<[Student]>。我尝试了mapflatMap的多种组合,但是我不知道如何将一系列期货转换成不同期货的阵列。

1 个答案:

答案 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应用来了解问题所在。