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
Related
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 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")
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 am having a looking at Slick 2.0
In this tutorial, the schema is as below:
// Definition of the SUPPLIERS table
class Suppliers(tag: Tag) extends Table[(Int, String, String, String, String, String)](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")
// Every table needs a * projection with the same type as the table's type parameter
def * = (id, name, street, city, state, zip)
}
val suppliers = TableQuery[Suppliers] //Definition TableQuery for suppliers
// Definition of the COFFEES table
class Coffees(tag: Tag) extends Table[(String, Int, Double, Int, Int)](tag, "COFFEES") {
def name = column[String]("COF_NAME", O.PrimaryKey)
def supID = column[Int]("SUP_ID")
def price = column[Double]("PRICE")
def sales = column[Int]("SALES")
def total = column[Int]("TOTAL")
def * = (name, supID, price, sales, total)
// A reified foreign key relation that can be navigated to create a join
def supplier = foreignKey("SUP_FK", supID, suppliers)(_.id) // Supplier defined above
}
val coffees = TableQuery[Coffees]
Here, the definition of the TableQuery[Suppliers] is done in the same file as the definition of Coffee so we can supply a TableQuery for the foreignKey (supplier)
Now, I would like to keep each class in a file and create the TableQuery whereever I need .
My question is:
How should I go to define the foreign key in the Coffee class and keep in a seperate file the Suppliers class?
Do I have to create those TableQuery in an Scala object and import it the Suppliers class so that I can provide a TableQuery to the foreignKey definition ?
I hope I was clear enough :/
Thank you
You need to simply reference the TableQuery to which the foreign key relates to:
// SuppliersSchema.scala
object SuppliersSchema {
class Suppliers(tag: Tag) extends Table[(Int, String, String, String, String, String)](tag, "SUPPLIERS") {
/* Omitted for brevity */
}
val suppliers = TableQuery[Suppliers] //Definition TableQuery for suppliers
}
// CoffeesSchema.scala
object CoffeesSchema {
class Coffees(tag: Tag) extends Table[(String, Int, Double, Int, Int)](tag, "COFFEES") {
/* Omitted for brevity */
def supplier = foreignKey("SUP_FK", supID, SuppliersSchema.suppliers)(_.id) // define in another file
}
val coffees = TableQuery[Coffees]
}
Another way would be to create a TableQuery reference to Suppliers inside of CoffeesSchema and use that in your foreign key definition key, anyway this approach is untested as I personally prefer the first one.
The following code shows a module (in the form of a trait) containing two Slick table definitions, with the second having a fk reference to the first. Each table object defines an inner case class called Id, which is used as its primary key. This all compiles and works just fine.
trait SlickModule {
val driver = slick.driver.BasicDriver
import driver.Table
case class A(id: TableA.Id, name: String)
case class B(id: TableB.Id, aId: TableA.Id)
import scala.slick.lifted.MappedTypeMapper
implicit val aIdType = MappedTypeMapper.base[TableA.Id, Long](_.id, new TableA.Id(_))
implicit val bIdType = MappedTypeMapper.base[TableB.Id, Long](_.id, new TableB.Id(_))
object TableA extends Table[A]("table_a") {
case class Id(id: Long)
def id = column[TableA.Id]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name", O.NotNull)
def * = id ~ name <> (A.apply _, A.unapply _)
}
object TableB extends Table[B]("table_b") {
case class Id(id: Long)
def id = column[Id]("id", O.PrimaryKey, O.AutoInc)
def aId = column[TableA.Id]("fk_a", O.NotNull)
def fkA = foreignKey("fk_a", aId, TableA)(_.id)
def * = id ~ aId <> (B.apply _, B.unapply _)
}
}
However, if I change the column definition of id in TableA from
def id = column[TableA.Id]("id", O.PrimaryKey, O.AutoInc)
to this, by removing the explicit path to Id
def id = column[Id]("id", O.PrimaryKey, O.AutoInc)
I get the following compilation error:
type mismatch;
found : SlickModule.this.TableA.type => scala.slick.lifted.Column[x$5.Id] forSome { val x$5: SlickModule.this.TableA.type }
required: SlickModule.this.TableA.type => scala.slick.lifted.Column[SlickModule.this.TableA.Id]
Error occurred in an application involving default arguments.
def fkA = foreignKey("fk_a", aId, TableA)(_.id)
^
So the type parameter of the aId column is found along a path that now includes TableA.type, whilst the parameter is just expected to be TableA.Id. Can anyone explain why this difference occurs and how I might get around it without needing the explicit reference to the TableA object? I am trying to abstract out the definition of my primary key columns into a trait, and this problem is preventing me from doing that.
I am using the Scala 2.10.2 compiler.
I am not completely sure why exactly your code gets a compilation error, but the following seems to achieve your goals:
trait TableModule {
import scala.slick.lifted.{MappedTypeMapper, BaseTypeMapper}
val driver = slick.driver.BasicDriver
case class Id(id: Long)
type Row
abstract class Table(name: String) extends driver.Table[Row](name) {
def id = column[Id]("id", O.PrimaryKey, O.AutoInc)
import driver.Implicit._
def findById(id: Id) = (for (e <- this if (e.id === id)) yield e)
}
implicit def idTypeMapper : BaseTypeMapper[Id] = MappedTypeMapper.base[Id, Long](_.id, new Id(_))
}
trait Schema {
object ModuleA extends TableModule {
case class Row(id: Id, name: String)
object table extends Table("table_a") {
def name = column[String]("name", O.NotNull)
def * = id ~ name <> (Row.apply _, Row.unapply _)
}
}
object ModuleB extends TableModule {
case class Row(id: Id, aId: ModuleA.Id)
object table extends Table("table_b") {
def name = column[String]("name", O.NotNull)
def aId = column[ ModuleA.Id]("fk_a", O.NotNull)
def fkA = foreignKey("fk_a", aId, ModuleA.table)(_.id)
def * = id ~ aId <> (Row.apply _, Row.unapply _)
}
}
}
object schema extends Schema {
def main(args: Array[String]): Unit = {
val ddl = ModuleA.table.ddl ++ ModuleB.table.ddl
println("Create:")
ddl.createStatements.foreach(println)
println("Delete:")
ddl.dropStatements.foreach(println)
}
}
In particular the Id classes associated with different tables are distinct so that
val aid = ModuleA.Id(1)
val bid : ModuleB.Id = aid
fails to compile with
[error] found : Schema.ModuleA.Id
[error] required: Schema.ModuleB.Id
[error] val bid : ModuleB.Id = aid