I need to use Slick 3.1.1 for a Postgres based project, but I have a hard time to write clean code for the following super simple usage:
Assume I have a Task model:
case class Task(id: Option[UUID], foo: Int, bar: String)
The id: UUID is the primary key, so I should NOT provide it (id = None) when doing database INSERT. However, I do need it when doing GET which maps a database row to a Task object.
Therefore, the Slick Table class becomes very ugly:
class Tasks(tag: Tag) extends Table[Task](tag, "tasks") {
def id = column[UUID]("id", O.SqlType("UUID"), O.PrimaryKey)
def foo = column[Int]("foo")
def bar = column[String]("bar")
def insert: MappedProjection[Task, (Int, String)] =
(foo, bar).shaped.<>(
{ tuple =>
Task.tupled(None, tuple._1, tuple._2)
}, { (task: Task) =>
Task.unapply(task).map { tuple =>
(tuple._2, tuple._3)
}
}
)
override def * : ProvenShape[Task] =
(id.?,
foo,
bar).shaped.<>(Task.tupled, Task.unapply)
}
If case class Task has 10 elements, I then have to write (tuple._1, tuple._2, tuple._3, ......) My co-workers will slap my face if I submit a PR like above. Please suggest!
If you'll let the database to autoincrement your IDs, that Table definition could be shortened significantly:
import slick.driver.PostgresDriver.api._
import java.util.UUID
case class Task(id: Option[UUID], foo: Int, bar: String)
class Tasks(tag: Tag) extends Table[Task](tag, "tasks") {
def id = column[Option[UUID]]("id", O.SqlType("UUID"), O.PrimaryKey, O.AutoInc)
def foo = column[Int]("foo")
def bar = column[String]("bar")
def * = (id, foo, bar) <> (Task.tupled, Task.unapply)
}
This could be improved further by moving the id field to the end of the case class and giving it the default None value. This way you won't have to provide it every time you want to instantiate the Task:
case class Task(foo: Int, bar: String, id: Option[UUID] = None)
val firstTask = Task(123, "John")
val secondTask = Task(456, "Paul")
Related
In a Play application, I have a few tables and classes that share the same structure. I want to keep them different to maintain application semantics (yay static typing!). However, I'd like to avoid duplicating boilerplate code.
For example, here's class Region.
case class Region(id: Int, name:String)
and here is its Slick's table query class:
class RegionsTable(tag:Tag) extends Table[Region](tag, "regions") {
def id = column[Int]("id", O.AutoInc, O.PrimaryKey)
def name = column[String]("name", O.Unique)
def * = (id, name) <> (Region.tupled, Region.unapply)
}
How can I avoid duplicating the table query class for each of the other classes that share Region's structure?
Perhaps not very Slick native solution, but refactoring might look like:
import slick.jdbc.H2Profile.api._
import scala.reflect.ClassTag
case class Region(id: Int, name: String)
case class Country(id: Int, name: String)
class IdNameTable[T](tag: Tag, tableName: String, apply: (Int, String) => T, unapply: T => Option[(Int, String)])
(implicit classTag: ClassTag[T]) extends Table[T](tag, tableName) {
def id = column[Int]("id", O.AutoInc, O.PrimaryKey)
def name = column[String]("name", O.Unique)
def * = (id, name) <> (apply.tupled, unapply)
}
class RegionsTable(tag: Tag) extends IdNameTable[Region](tag, "regions", Region, Region.unapply)
class CountryTable(tag: Tag) extends IdNameTable[Country](tag, "country", Country, Country.unapply)
Working Scatie example: https://scastie.scala-lang.org/fR7BzS1jSKSXptE9CTxXyA
trying work with Sangria and Slick. New to both of them.
I have bunch of tables which share a list of common fields. Slick's representation of this is below:
case class CommonFields(created_by: Int = 0, is_deleted: Boolean = false)
trait CommonModel {
def commonFields: CommonFields
def created_by = commonFields.created_by
def is_deleted = commonFields.is_deleted
}
case class User(id: Int,
name: String,
commonFields: CommonFields = CommonFields()) extends CommonModel
Slick tables:
abstract class CommonTable [Model <: CommonModel] (tag: Tag, tableName: String) extends Table[Model](tag, tableName) {
def created_by = column[Int]("created_by")
def is_deleted = column[Boolean]("is_deleted")
}
case class CommonColumns(created_by: Rep[Int], is_deleted: Rep[Boolean])
implicit object CommonShape extends CaseClassShape(
CommonColumns.tupled, CommonFields.tupled
)
class UsersTable(tag: Tag) extends CommonTable[User](tag, "USERS") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def * = (id,
name,
CommonColumns(created_by, is_deleted)) <> (User.tupled, User.unapply)
}
val Users = TableQuery[UsersTable]
The problem is with Graphql:
lazy val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User]()
When I try to create UserType using derivedObjectType Macro, it complains that
Can't find suitable GraphQL output type for .CommonFields. If you have defined it already, please consider making it implicit and ensure that it's available in the scope.
[error] lazy val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User](
How do I tell Sangria/Graphql how to handle this nested list of fields (from CommonFields) ?
Please help.
You are deriving the Type for User but user also has CommonFields which you have not derived. So it's not able to find Type information for CommonFields. Derive both and make the derivation implicit for CommonFields.
implicit val CommonFieldsType: ObjectType[Unit, CommonFields] = deriveObjectType[Unit, CommonFields]
implicit val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User]
I have some columns all my tables share, so I'd like to provide the default columns for all the tables. Following is what I have tried so far. I am using Slick 3.0.
// created_at and updated_at are the columns every table share
abstract class BaseTable[T](tag: Tag, tableName: String)
extends Table[T](tag, tableName) {
def currentWhenInserting = new Timestamp((new Date).getTime)
def createdAt = column[Timestamp]("created_at", O.Default(currentWhenInserting))
def updatedAt = column[Timestamp]("updated_at", O.Default(currentWhenInserting))
}
And the simple way to implement this seems like the following.
case class Student(
name: String, age: Int,
created_at: Timestamp, updated_at: Timestamp
)
class Students(tag: Tag) extends BaseTable[Student](tag, "students"){
def name = column[String]("name")
def age = column[Int]("age")
override def * : ProvenShape[Student] =
(name, age, createdAt, updatedAt).shaped <>
(Student.tupled, Student.unapply _)
}
But it's not desirable.
First, force every table row case class to incoporate created_at and updated_at. If I have more fields that would be totally unacceptable from the API design angle.
Second, write the two createdAt, updatedAt in (name, age, createdAt, updatedAt) explicitly. This is not what I expect about Default row.
My ideal way of solving this would be like the following:
case class Student(name: String, age: Int)
class Students(tag: Tag) extends BaseTable[Student](tag, "students"){
def name = column[String]("name")
def age = column[Int]("age")
override def * : ProvenShape[Student] =
(name, age).shaped <>
(Student.tupled, Student.unapply _)
}
That is, write some method in BaseTable or Define BaseCaseClass to avoid explicitly writing the extra two fields in table definition like Students and row case class definition Student.
However, after a painful struggle, still can get it done.
Any help would be greatly appreciated.
I'm using the following pattern:
case class Common(arg0: String, arg1: String)
trait Commons { this: Table[_] =>
def arg0 = column[String]("ARG0", O.Length(123))
def arg1 = column[String]("ARG1", O.Length(123))
def common = (arg0, arg1).<> [Meta, (String, String)](
r => {
val (arg0, arg1) = r
Meta(arg0, arg1)
},
o => Some(o.arg0, o.arg1)
)
}
case class MyRecord(a: String, b: String, common: Common)
class MyRecords(tag: Tag) extends Table[MyRecord](tag, "MY_RECORDS") with Commons {
def a = column[String]("A", O.PrimaryKey, O.Length(123))
def b = column[String]("B", O.Length(123))
def * = (a, b, common) <> (MyRecord.tupled, MyRecord.unapply)
}
It's not perfect but it helps avoid duplication without it being to difficult to understand.
I use scala 2.11 and slick 2.1.0 and have a compiled code:
trait TSegmentClient { this: Profile =>
import profile.simple._
class SegmentClients(tag: Tag) extends Table[(Int, Long)](tag, "seg") {
def segmentId = column[Int]("segment_id")
def clientId = column[Long]("client_id")
def * = (segmentId, clientId)
}
}
segmentClients.insert(clientBehaviors.map(c => (1, c.clientId)))
it works.
But i need a case class like this:
case class SegmentClient(segmentId: Int, clientId: Long)
trait TSegmentClient { this: Profile =>
import profile.simple._
class SegmentClients(tag: Tag) extends Table[SegmentClient](tag, "seg") {
def segmentId = column[Int]("segment_id")
def clientId = column[Long]("client_id")
def * = (segmentId, clientId) <> (SegmentClient.tupled, SegmentClient.unapply)
}
}
segmentClients.insert(clientBehaviors.map(c => (1, c.clientId)))
But it doesn't compile.
(value: models.coper.datamining.SegmentClient)(implicit session:
scala.slick.jdbc.JdbcBackend#SessionDef)Int cannot be applied to
(scala.slick.lifted.Query[(scala.slick.lifted.Column[Int],
scala.slick.lifted.Column[Long]),(Int, Long),Seq])
segmentClients.insert(clientBehaviors.map(c => (segmentId, c.clientId)))
What is wrong with my code?
You can do this using another projection to a tuple that is not mapped to a case class.
case class SegmentClient(segmentId: Int, clientId: Long)
trait TSegmentClient { this: Profile =>
import profile.simple._
class SegmentClients(tag: Tag) extends Table[SegmentClient](tag, "seg") {
def segmentId = column[Int]("segment_id")
def clientId = column[Long]("client_id")
def tuple = (segmentId, clientId)
def * = tuple <> (SegmentClient.tupled, SegmentClient.unapply)
}
}
segmentClients.map(_.tuple).insert(clientBehaviors.map(c => (1, c.clientId)))
The insert method on segmentClients in your second example expects a SegmentClient instance since SegmentClients is a mapped table. That's what the compiler error message is basically saying. I don't know whether there's a more idiomatic approach, since I don't know Slick too well, but as a workaround you could also use:
val behaviours = clientBehaviours.list.map(c => SegmentClient(1, c.clientId))
segmentClients.insertAll(behaviours)
How can I return a mapped object using Slick? Using the following code my query returns List[(Int, String)] and not a List[Task] like I want it to. Is this not possible using Slick or am I thinking about Slick the wrong way is it not an ORM? I'm trying to return a query and use it in a view template using the Play2 framework. I'd like to end up accessing the objects like task.id task.label etc... Thanks.
import play.api.Play.current
import play.api.db._
import scala.slick.driver.H2Driver.simple._
case class Task(id: Int, label: String)
object Task extends Table[(Int, String)]("TASKS") {
lazy val database = Database.forDataSource(DB.getDataSource())
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def label = column[String]("LABEL")
def * = id ~ label
def all() : List[Task] = database.withSession { implicit db: Session =>
Query(Task).list
}
}
The issue is with how you defined your table. Try changing your table definition to:
case class Task(id: Int, label: String)
object Task extends Table[Task]("TASKS") {
lazy val database = Database.forDataSource(DB.getDataSource())
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def label = column[String]("LABEL")
def * = id ~ label <> (Task.apply _, Task.unapply _)
def all() : List[Task] = database.withSession { implicit db: Session =>
Query(Task).list
}
}
The difference is that the type param I am passing to the Table is Task instead of (Int, String). This should fix your issue.