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

I am trying to write some REST API to fetch the data using Scala Slick 3.2.3. Is there a way to calculate a derived column and include it in the returned output?
My model:
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
}
I want to add a column in the response json called timeline with values "overdue", "today", "tomorrow", "upcoming", etc. calculated based on the dueOn value.
I tried searching but could not find any help. Any help with an example or any pointers would be highly appreciated. Thanks!

First I'd start from defining enum model for timeline:
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")
}
Then I'd modify dueOne column type from plain String to LocalDate - this will be easier to do on DAO level, so Slick will handle parsing errors for us.
So, to need to define custom type for LocalDate (see for more details: 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)
}
Then define API level model TaskResponse with new additional timeline field:
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
}
}
}
Then you can create TasksService responsible for business logic of converting:
class TasksService(dao: TasksDao)(implicit ec: ExecutionContext) {
def findAll: Future[Seq[TaskResponse]] = {
dao.findAll.map(_.map(TaskResponse.fromTask))
}
}
Hope this helps!

Related

playframework scala slick insert query without autoincrement

Good Morning,
I've got the following code snippet:
def createResponsibleProcessTemplates(processTemplateId: Int, username: String): Future[Int] = db.run {
println("Create Responsible TemplateId: " + processTemplateId + " UserName: " + username)
(responsibleProcessTemplates
returning responsibleProcessTemplates.map(_.processTemplateId)
) += ResponsibleProcessTemplateModel(processTemplateId, username)
}
My Model is:
package models
import play.api.libs.json.Json
case class ResponsibleProcessTemplateModel(
processTemplateId: Int,
username: String)
object ResponsibleProcessTemplateModel {
implicit val responsibleProcessTemplateFormat = Json.format[ResponsibleProcessTemplateModel]
}
And the trait looks like this:
trait ResponsibleProcessTemplateComponent {
self: HasDatabaseConfigProvider[JdbcProfile] =>
import profile.api._
class ResponsibleProcessTemplate(tag: Tag) extends Table[ResponsibleProcessTemplateModel](tag, "Responsible_ProcessTemplates") {
def processTemplateId: Rep[Int] = column[Int]("processTemplateId")
def username: Rep[String] = column[String]("username")
def * : ProvenShape[ResponsibleProcessTemplateModel] = (processTemplateId, username) <> ((ResponsibleProcessTemplateModel.apply _).tupled, ResponsibleProcessTemplateModel.unapply)
}
val responsibleProcessTemplates: TableQuery[ResponsibleProcessTemplate] = TableQuery[ResponsibleProcessTemplate]
}
It should insert some data into a database table without generating an autoincerement id.
I don't get errors, but data is not stored in database.
Thanks for your help.
I found the solution!
def createResponsibleProcessTemplates(processTemplateId: Int, username: String): Future[Int] = {
db.run(responsibleProcessTemplates.map(x => (x.processTemplateId, x.username)) += (processTemplateId, username))
}

Slick select all rows for table with no filtering

How can I get a collection of JurisdictionRow objects? I need a SELECT * FROM jurisdiction
object JurisdictionRepo extends {
val profile = slick.driver.MySQLDriver
} with JurisdictionRepo
trait JurisdictionRepo {
private val dbConfig: DatabaseConfig[MySQLDriver] = DatabaseConfig.forConfig("pnga-master-data")
private val db = dbConfig.db
val profile: slick.driver.JdbcProfile
val tableName = "jurisdiction"
def add(jurisdictionRow: JurisdictionRow): Future[Unit] = db.run(query += jurisdictionRow).map { _ => () }
def delete(id: String): Future[Int] = db.run(query.filter(_.id === id).delete)
def get(id: String): Future[Option[JurisdictionRow]] = db.run(query.filter(_.id === id).result.headOption)
def all() = ???
import profile.api._
lazy val schema: profile.SchemaDescription = query.schema
case class JurisdictionRow(id: String,
parentId: String,
name: String,
code: String)
class Jurisdiction(_tableTag: Tag) extends Table[JurisdictionRow](_tableTag, tableName) {
val id: Rep[String] = column[String](s"${tableName}_id", O.PrimaryKey, O.Length(36, varying=true))
val parentId: Rep[String] = column[String]("parent_id", O.Length(36, varying=true))
val name: Rep[String] = column[String]("name", O.Length(255, varying=true))
val code: Rep[String] = column[String]("code", O.Length(255, varying=true))
def * = (id, parentId, name, code) <> (JurisdictionRow.tupled, JurisdictionRow.unapply _)
}
lazy val query = new TableQuery(tag => new Jurisdiction(tag))
}
I would like to implement the all method to return all possible JurisdictionRow objects in the table. This seems like a common case, but the Slick documentation has not been helpful. I just need a plain old result set, no fancy filtering, etc.
Just replicate what you already have in the other queries but without the filter part.
def all = db.run(query.result)
Have look at the first example:
http://slick.lightbend.com/doc/3.2.0/gettingstarted.html#querying

How do I filter on date in Play-SLICK which is saved as Instant?

I am using scala, play, sclick; and postgres. I have made a table named order & it contains a field named created in timestamp format with zone. Now I would like to search on order based on created with parameters year & month as follows:-
SELECT * FROM "order"
WHERE created::text LIKE '2016-07%';
The above query works fine in postgres.
The scala code I have written for Order is:-
case class Order(id: Option[Int],
customerId: Int,
amount: Double,
created: Option[Instant],
updated: Option[Instant]) extends GenericEntity {
def this(customerId: Int,
amount: Double,) = this(None, customerId, amount, None, None)
}
class OrderTable(tag: Tag) extends GenericTable[Order](tag, "order") {
override def id = column[Option[Int]]("id", O.PrimaryKey, O.AutoInc)
def customerId = column[Int]("customer_id")
def amount = column[Dount]("amount")
def customer = foreignKey("fk_order_customer", customerId, Customers.table)(_.id.getOrElse(1), onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
def * = (id, customerId, amount, created, updated) <> ((Order.apply _).tupled, Order.unapply)
}
object Orders extends GenericService[Order, OrderTable] {
override val table = TableQuery[OrderTable]
override def copyEntityFields(entity: Order, id: Option[Int], created: Option[Instant], updated: Option[Instant]): Order = {
entity.copy(id = id, created = created, updated = updated)
}
def getMonthlyOrder(year:Int, month: Int) = {
// execute LIKE query for the following query
// SELECT * FROM "order"
// WHERE created::text LIKE '2016-07%';
}
}
Where GenericEntity defines id, created, updated.
So what code should I write for getMonthlyOrder function with year and month?.
You need two things:
1) A column type to let Slick know how to persist Instant to database. In this case you want to map Instant to java.sql.Timestamp, which Slick can use natively.
implicit val instantColumnType: BaseColumnType[Instant] =
MappedColumnType.base[Instant, Timestamp](
instant => Timestamp.from(instant),
ts => ts.toInstant
)
2) Functions to extract year and month from your timestamp. Here I used another approach than your LIKE query. You could also define a mapping from Instant to String and use something like startsWith.
val yearFn = SimpleFunction.unary[Instant, Int]("year")
val monthFn = SimpleFunction.unary[Instant, Int]("month")
Then you would use them in getMonthlyOrder like this
def getMonthlyOrder(year: Int, month: Int): Future[Seq[Order]] = {
val query = table.filter(_.created.map((created) => yearFn(created) === year && monthFn(created) === month))
db.run(query.result)
}
The complete code valid for Slick 3.1:
import java.sql.Timestamp
import java.time.Instant
import javax.inject.{Inject, Singleton}
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.driver.JdbcProfile
import slick.lifted.TableQuery
import scala.concurrent.Future
case class Order(id: Int,
customerId: Int,
amount: Double,
created: Option[Instant],
updated: Option[Instant])
#Singleton
class Orders #Inject()(val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
val table = TableQuery[OrderTable]
import driver.api._
implicit val instantColumnType: BaseColumnType[Instant] =
MappedColumnType.base[Instant, Timestamp](
instant => Timestamp.from(instant),
ts => ts.toInstant
)
val yearFn = SimpleFunction.unary[Instant, Int]("year")
val monthFn = SimpleFunction.unary[Instant, Int]("month")
def getMonthlyOrder(year: Int, month: Int): Future[Seq[Order]] = {
val query = table.filter(_.created.map((created) => yearFn(created) === year && monthFn(created) === month))
db.run(query.result)
}
class OrderTable(tag: Tag) extends Table[Order](tag, "ORDERS") {
def * = (id, customerId, amount, created, updated) <> ((Order.apply _).tupled, Order.unapply)
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def customerId = column[Int]("CUSTOMER_ID")
def amount = column[Double]("AMOUNT")
def created = column[Option[Instant]]("CREATED")
def updated = column[Option[Instant]]("UPDATED")
}
}

Slick: how to get an object attribute in a result set?

Given the following Scala class enhanced with Slick:
class Users(tag: Tag) extends Table[(Int, String, String)](tag, "users") {
def id: Rep[Int] = column[Int]("sk", O.PrimaryKey)
def firstName: Rep[String] = column[String]("first_name")
def lastName: Rep[String] = column[String]("last_name")
def * : ProvenShape[(Int, String, String)] = (id, firstName, lastName)
}
I need to print the last names in a query loop:
val db = Database.forConfig("dbconfig")
try {
val users: TableQuery[Users] = TableQuery[Users]
val action = users.result
val future = db.run(action)
future onComplete {
case Success(u) => u.foreach { user => println("last name : " + **user.lastName**) }
case Failure(t) => println("An error has occured: " + t.getMessage)
}
} finally db.close
But Scala doesn't recognize user.lastName (I get an error saying that "Scala doesn't recognize the symbol"). How to print the last names ?
The problem is you're using Table[(Int, String, String)]. user in your case is therefore an instance of type (Int, String, String), so it doesn't have a lastName. Use user._3 to get at the tuple's third element (the last name). Even better might be to use a case class instead of a tuple:
case class DBUser(id: Int, firstName: String, lastName: String)
class Users(tag: Tag) extends Table[DBUser](tag, "users") {
def id: Rep[Int] = column[Int]("sk", O.PrimaryKey)
def firstName: Rep[String] = column[String]("first_name")
def lastName: Rep[String] = column[String]("last_name")
def * = (id, firstName, lastName) <> (DBUser.tupled, DBUser.unapply)
}

Using multiple projection in slick for the same table

I have a user table for which I will like to have multiple projections. For example, Can I have something like
class Users(tag: Tag) extends Table [User] (tag, "user") {
def * = (id.?, emailId, firstName, lastName, gender) <> (User.tupled, User.unapply)
def allDetails = (id.?, emailId, firstName, lastName, gender, createdTime, modifiedTime)
...
}
I searched on Google but could not find anything. Can somebody tell me how can I use allDetails?
I will like to do
object Users {
val users = TableQuery[Users]
def getAllDetails = ??? // How can I use allDetails here
}
Had the same need recently, and started to use something like that:
abstract class AnyUserTable[T](tag: Tag) extends Table[T](tag, "user") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def emailId = column[String]("email_id")
def firstName = column[String]("firstname")
def lastName = column[String]("lastname")
def gender = column[String]("gender")
}
class Users(tag: Tag) extends AnyUserTable[User](tag) {
def * = (emailId, firstName, lastName, gender, id.?) <> (User.tupled, User.unapply)
}
class UserDetails(tag: Tag) extends AnyUserTable[UserDetail](tag) {
def createdTime = column[Option[Timestamp]]("created_time")
def modifiedTime = column[Option[Timestamp]]("modified_time")
def * = (emailId, firstName, lastName, gender, createdTime, modifiedTime, id.?) <> (UserDetail.tupled, UserDetail.unapply)
}
object Users {
val users = TableQuery[Users]
val getAllDetails = TableQuery[UserDetails] // That is how I propose to get all the details
}
borrowing the case classes from Sky's answer.
case class User(
emailId: String, firstName: String, lastName: String,
gender: String, id: Option[Int] = None)
case class UserDetail(
emailId: String,
firstName: String, lastName: String, gender: String,
createdTime: Option[Timestamp],
modifiedTime: Option[Timestamp], id: Option[Int] = None)
I guess that is pretty close from what I would be tempted to do using updatable views in straight sql.
Do like this:
class UserTable(tag: Tag) extends Table[UserInfo](tag, "user") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def emailId = column[String]("email_id", O.NotNull)
def firstName = column[String]("firstname")
def lastName = column[String]("lastname")
def gender = column[String]("gender")
def createdTime = column[Option[Timestamp]]("created_time")
def modifiedTime = column[Option[Timestamp]]("modified_time")
def userInfo = (emailId, firstName, lastName, gender, createdTime, modifiedTime, id.?) <> (User.tupled, User.unapply)
def * = (emailId, firstName, lastName, gender, id.?) <> (UserInfo.tupled, UserInfo.unapply)
}
case class User(emailId: String,
firstName: String, lastName: String, gender: String,
createdTime: Option[Timestamp],
modifiedTime: Option[Timestamp], id: Option[Int] = None)
case class UserInfo(emailId: String, firstName: String, lastName: String,
gender: String, id: Option[Int] = None)
for extra projection:
val userTable = TableQuery[UserTable]
def insert(userInfo: UserInfo) (implicit session: Session): Int = {
//return auto incremeted id
userTable returning (userTable.map(_.id)) += userInfo
}
def update(userInfo: UserInfo)(implicit session: Session) = {
userTable.filter(_.id === userInfo.id).update(userInfo)
}
def getUserInfo()(implicit session: Session): List[UserInfo] = {
userTable.list
}