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!
I would like to add a case class SupplierID to my code. And I would like to simplify the * method by auto-Mapping SupplierID and Int Types.
case class SupplierID(id: Int)
case class Supplier(id: SupplierID, name: String, street: String, city: String, state: String, zip: String)
// Definition of the SUPPLIERS table using case class
class Suppliers(tag: Tag) extends Table[Supplier](tag, "SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey) // This is the primary key column
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def city = column[String]("CITY")
def state = column[String]("STATE")
def zip = column[String]("ZIP")
def * ={
(id, name, street, city, state, zip).shaped.<>({ tuple => Supplier.apply(SupplierID(tuple._1), tuple._2, tuple._3, tuple._4, tuple._5, tuple._6 )}, {
(s : Supplier) => Some{(s.id.id, s.name, s.street, s.city, s.state, s.zip)}
})
}
}
I'm trying to implement a column Type like this
// And a ColumnType that maps it to Int
implicit val SupplierIDColumnType = MappedColumnType.base[SupplierID, Int](
s => s.id, // map Bool to Int
i => SupplierID(i) // map Int to Bool
)
How to use such mappingtype ?
The solution:
case class SupplierID(id: Int)
// And a ColumnType that maps it to Int
implicit val SupplierIDColumnType = MappedColumnType.base[SupplierID, Int](
s => s.id, // map SupplierID to Int
i => SupplierID(i) // map Int to SupplierID
)
case class Supplier(id: SupplierID, name: String, street: String, city: String, state: String, zip: String)
// Definition of the SUPPLIERS table using case class
class Suppliers(tag: Tag) extends Table[Supplier](tag, "SUPPLIERS") {
def id = column[SupplierID]("SUP_ID", O.PrimaryKey) // This is the primary key column
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def city = column[String]("CITY")
def state = column[String]("STATE")
def zip = column[String]("ZIP")
def * = (id, name, street, city, state, zip) <>(Supplier.tupled , Supplier.unapply _)
}
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)
}
I'm trying to map a DB row with more than 22 columns to a case class tree.
I'd rather not using HList as I don't want to work with that API, and also for some exponential compilation time feedbacks that I've read somewhere...
I have read this thread answered by Stefan Zeiger: How can I handle a > 22 column table with Slick using nested tuples or HLists?
I've seen this test which shows how to define a custom mapping function and I'd like to do that:
https://github.com/slick/slick/blob/2.1/slick-testkit/src/main/scala/com/typesafe/slick/testkit/tests/JdbcMapperTest.scala#L129-141
def * = (
id,
(p1i1, p1i2, p1i3, p1i4, p1i5, p1i6),
(p2i1, p2i2, p2i3, p2i4, p2i5, p2i6),
(p3i1, p3i2, p3i3, p3i4, p3i5, p3i6),
(p4i1, p4i2, p4i3, p4i4, p4i5, p4i6)
).shaped <> ({ case (id, p1, p2, p3, p4) =>
// We could do this without .shaped but then we'd have to write a type annotation for the parameters
Whole(id, Part.tupled.apply(p1), Part.tupled.apply(p2), Part.tupled.apply(p3), Part.tupled.apply(p4))
}, { w: Whole =>
def f(p: Part) = Part.unapply(p).get
Some((w.id, f(w.p1), f(w.p2), f(w.p3), f(w.p4)))
})
The problem is that I can't make it!
I've tried by smaller steps.
class UserTable(tag: Tag) extends TableWithId[User](tag,"USER") {
override def id = column[String]("id", O.PrimaryKey)
def role = column[UserRole.Value]("role", O.NotNull)
def login = column[String]("login", O.NotNull)
def password = column[String]("password", O.NotNull)
def firstName = column[String]("first_name", O.NotNull)
def lastName = column[String]("last_name", O.NotNull)
//
def * = (id, role, login, password, firstName, lastName) <> (User.tupled,User.unapply)
//
def login_index = index("idx_user_login", login, unique = true)
}
It seems that when I call
(id, (firstName, lastName)).shaped
The type is ShapedValue[(Column[String], (Column[String], Column[String])), Nothing]
While this one seems to work fine
(id, firstName, lastName).shaped
The U type parameter is not Nothing but as expected (String, String, String)
I don't really understand how all the Slick internals are working. Can someone explain me why I can't make my code work? Is there a missing import or something?
I guess I need to get a value of type
ShapedValue[(Column[String], (Column[String], Column[String])), (String, (String, String))]
but I don't know why it returns me Nothing and don't really understand where these implicit Shape parameters come from...
What I want is just to be able to easily split my column into 2 case classes
Thanks
Also, have the same problem with the 22 columns limitation, the test case helps very much. Not sure why the example code not working for you, the following code works fine for me,
case class UserRole(var role: String, var extra: String)
case class UserInfo(var login: String, var password: String, var firstName: String, var lastName: String)
case class User(id: Option[String], var info: UserInfo, var role: UserRole)
class UserTable(tag: Tag) extends Table[User](tag, "USER") {
def id = column[String]("id", O.PrimaryKey)
def role = column[String]("role", O.NotNull)
def extra = column[String]("extra", O.NotNull)
def login = column[String]("login", O.NotNull)
def password = column[String]("password", O.NotNull)
def firstName = column[String]("first_name", O.NotNull)
def lastName = column[String]("last_name", O.NotNull)
/** Projection */
def * = (
id,
(login, password, firstName, lastName),
(role, extra)
).shaped <> (
{ case (id, userInfo, userRole) =>
User(Option[id], UserInfo.tupled.apply(userInfo), UserRole.tupled.apply(userRole))
},
{ u: User =>
def f1(p: UserInfo) = UserInfo.unapply(p).get
def f2(p: UserRole) = UserRole.unapply(p).get
Some((u.id.get, f1(u.info), f2(u.role)))
})
def login_index = index("id_user_login", login, unique = true)
}
As Izongren answered it works fine, but it can be hard to write such code as it's annoying to work with very long tuples... I decided to do it "step by step" and always providing types explicitly to avoid type inference problemes and now it works fine.
case class Patient(
id: String = java.util.UUID.randomUUID().toString,
companyScopeId: String,
assignedToUserId: Option[String] = None,
info: PatientInfo
) extends ModelWithId
case class PatientInfo(
firstName: Option[String] = None,
lastName: Option[String] = None,
gender: Option[Gender.Value] = None,
alias: Option[String] = None,
street: Option[String] = None,
city: Option[String] = None,
postalCode: Option[String] = None,
phone: Option[String] = None,
mobilePhone: Option[String] = None,
email: Option[String] = None,
age: Option[AgeRange.Value] = None,
companySeniority: Option[CompanySeniorityRange.Value] = None,
employmentContract: Option[EmploymentContract.Value] = None,
socialStatus: Option[SocialStatus.Value] = None,
jobTitle: Option[String] = None
)
class PatientTable(tag: Tag) extends TableWithId[Patient](tag,"PATIENT") {
override def id = column[String]("id", O.PrimaryKey)
def companyScopeId = column[String]("company_scope_id", O.NotNull)
def assignedToUserId = column[Option[String]]("assigned_to_user_id", O.Nullable)
def firstName = column[Option[String]]("first_name", O.Nullable)
def lastName = column[Option[String]]("last_name", O.Nullable)
def gender = column[Option[Gender.Value]]("gender", O.Nullable)
def alias = column[Option[String]]("alias", O.Nullable)
def street = column[Option[String]]("street", O.Nullable)
def city = column[Option[String]]("city", O.Nullable)
def postalCode = column[Option[String]]("postal_code", O.Nullable)
def phone = column[Option[String]]("phone", O.Nullable)
def mobilePhone = column[Option[String]]("mobile_phone", O.Nullable)
def email = column[Option[String]]("email", O.Nullable)
def age = column[Option[AgeRange.Value]]("age", O.Nullable)
def companySeniority = column[Option[CompanySeniorityRange.Value]]("company_seniority", O.Nullable)
def employmentContract = column[Option[EmploymentContract.Value]]("employment_contract", O.Nullable)
def socialStatus = column[Option[SocialStatus.Value]]("social_status", O.Nullable)
def jobTitle = column[Option[String]]("job_title", O.Nullable)
def role = column[Option[String]]("role", O.Nullable)
private type PatientInfoTupleType = (Option[String], Option[String], Option[Gender.Value], Option[String], Option[String], Option[String], Option[String], Option[String], Option[String], Option[String], Option[AgeRange.Value], Option[CompanySeniorityRange.Value], Option[EmploymentContract.Value], Option[SocialStatus.Value], Option[String])
private type PatientTupleType = (String, String, Option[String], PatientInfoTupleType)
//
private val patientShapedValue = (id, companyScopeId, assignedToUserId,
(
firstName, lastName, gender, alias, street, city, postalCode,
phone, mobilePhone,email, age, companySeniority, employmentContract, socialStatus, jobTitle
)
).shaped[PatientTupleType]
//
private val toModel: PatientTupleType => Patient = { patientTuple =>
Patient(
id = patientTuple._1,
companyScopeId = patientTuple._2,
assignedToUserId = patientTuple._3,
info = PatientInfo.tupled.apply(patientTuple._4)
)
}
private val toTuple: Patient => Option[PatientTupleType] = { patient =>
Some {
(
patient.id,
patient.companyScopeId,
patient.assignedToUserId,
(PatientInfo.unapply(patient.info).get)
)
}
}
def * = patientShapedValue <> (toModel,toTuple)
}
Also, you can use this way
class UserTable(tag: Tag) extends TableWithId[User](tag,"USER") {
def id = column[String]("id", O.PrimaryKey)
def role = column[String]("role", O.NotNull)
def extra = column[String]("extra", O.NotNull)
def login = column[String]("login", O.NotNull)
def password = column[String]("password", O.NotNull)
def firstName = column[String]("first_name", O.NotNull)
def lastName = column[String]("last_name", O.NotNull)
def loginMap = (login, password, firstName, lastName) <> (UserInfo.tupled, UserInfo.unapply)
def roleMap = (role, extra) <> (Role.tupled, Role.unapply)
override def * = (id, roleMap, loginMap) <> (User.tupled,User.unapply)
def login_index = index("idx_user_login", login, unique = true)
}
I Understand that nested classes work fine with Slick and we have that working in our product. We are facing an issue when the nested class is optional. For instance, look at the following code
class EmpTable(tag: Tag) extends Table[Emp](tag, "emp") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def aId = column[Option[Int]]("a_id")
def location = column[Option[String]]("address")
def address = (aId, location) <> (Address.tupled, Address.unapply)
def * = (id, name, address.?) <> (Emp.tupled, Emp.unapply)
}
case class Emp(id: Int, name: String, address: Option[Address])
case class Address(aId: Option[Int], location: Option[String])
This code does not compile because address does not have method ? .
Is there an easy way to get this working?
I got a solution for this:
class EmpTable(tag: Tag) extends Table[Emp](tag, "emp") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def aId = column[Option[Int]]("a_id")
def location = column[Option[String]]("address")
def * = (id, name, address, auth) <> (Emp.tupled, Emp.unapply)
def address = (aId, location).<>[Option[Address], (Option[Int], Option[String])]({ ad: (Option[Int], Option[String]) =>
ad match {
case (Some(id), Some(loc)) => Some(Address(id, loc))
case _ => None
}
}, {
adObj: Option[Address] =>
adObj match {
case add: Option[Address] => Some((add.map(_.aId), add.map(_.location)))
}
})