计算 select 输出中的派生列 - Scala Slick 3.2.3

Calculate a derived column in the select output - Scala Slick 3.2.3

我正在尝试编写一些 REST API 以使用 Scala Slick 3.2.3 获取数据。有没有办法计算派生列并将其包含在返回的输出中?

我的模特:

case class Task(id: Option[TaskId], title: String, dueOn: String, status: String, createdAt: String, updatedAt: String)

Table class:

class TasksTable(tag: Tag) extends Table[Task](tag, _tableName = "TASKS") {
  def id: Rep[TaskId] = column[TaskId]("ID", O.PrimaryKey, O.AutoInc)
  def title: Rep[String] = column[String]("TITLE")
  def dueOn: Rep[String] = column[String]("DUE_ON")
  def status: Rep[String] = column[String]("STATUS")
  def createdAt: Rep[String] = column[String]("CREATED_AT")
  def updatedAt: Rep[String] = column[String]("UPDATED_AT")
  def * = (id.?, title, dueOn, status, createdAt, updatedAt) <> ((Task.apply _).tupled, Task.unapply)
}

DAO:

object TasksDao extends BaseDao {
  def findAll: Future[Seq[Task]] = tasksTable.result
}

我想在响应 json 中添加一个名为 timeline 的列,其值为 "overdue"、"today"、"tomorrow"、"upcoming"等根据dueOn值计算。

我尝试搜索但找不到任何帮助。对示例或任何指针的任何帮助将不胜感激。谢谢!

首先,我将从为时间轴定义枚举模型开始:

object Timelines extends Enumeration {
  type Timeline = Value
  val Overdue: Timeline = Value("overdue")
  val Today: Timeline = Value("today")
  val Tomorrow: Timeline = Value("tomorrow")
  val Upcoming: Timeline = Value("upcoming")
}

然后我将 dueOne 列类型从普通 String 修改为 LocalDate - 这在 DAO 级别上更容易完成,因此 Slick 将为我们处理解析错误。 因此,需要为 LocalDate 定义自定义类型(有关详细信息,请参阅:http://scala-slick.org/doc/3.0.0/userdefined.html#using-custom-scalar-types-in-queries)。

// Define mapping between String and LocalDate
private val defaultDateFormat: DateTimeFormatter = DateTimeFormatter.ISO_DATE // replace it with formatter you use for a date

def stringDateColumnType(format: DateTimeFormatter): BaseColumnType[LocalDate] = {
 MappedColumnType.base[LocalDate, String](_.format(format), LocalDate.parse(_, format))
}

implicit val defaultStringDateColumnType: BaseColumnType[LocalDate] = stringDateColumnType(defaultDateFormat)

private val defaultDateFormat: DateTimeFormatter = DateTimeFormatter.ISO_DATE // replace it with formatter you use for a date

// Change `dueOn` from String to LocalDate
case class Task(id: Option[TaskId], title: String, dueOn: LocalDate, status: String, createdAt: String, updatedAt: String)

class TasksTable(tag: Tag) extends Table[Task](tag, _tableName = "TASKS") {
  def id: Rep[TaskId] = column[TaskId]("ID", O.PrimaryKey, O.AutoInc)
  def title: Rep[String] = column[String]("TITLE")
  def dueOn: Rep[LocalDate] = column[LocalDate]("DUE_ON") // Then replace column type
  def status: Rep[String] = column[String]("STATUS")
  def createdAt: Rep[String] = column[String]("CREATED_AT")
  def updatedAt: Rep[String] = column[String]("UPDATED_AT")
  def * = (id.?, title, dueOn, status, createdAt, updatedAt) <> ((Task.apply _).tupled, Task.unapply)
}

然后使用新的附加 timeline 字段定义 API 级别模型 TaskResponse

case class TaskResponse(id: Option[TaskId], title: String, dueOn: LocalDate, status: String, createdAt: String, updatedAt: String, timeline: Timeline)

  object TaskResponse {
    import Timelines._
    def fromTask(task: Task): TaskResponse = {
      val timeline = dueOnToTimeline(task.dueOn)
     TaskResponse(task.id, task.title, task.dueOn, task.status, task.createdAt, task.updatedAt, timeline)
    }

    def dueOnToTimeline(dueOn: LocalDate): Timeline = {
      val today = LocalDate.now()
      Period.between(today, dueOn).getDays match {
        case days if days < 0 => Overdue
        case 0 => Today
        case 1 => Tomorrow
        case _ => Upcoming
      }
    }
  }

然后你可以创建TasksService负责转换的业务逻辑:

  class TasksService(dao: TasksDao)(implicit ec: ExecutionContext) {
    def findAll: Future[Seq[TaskResponse]] = {
      dao.findAll.map(_.map(TaskResponse.fromTask))
    }
  }

希望对您有所帮助!