Return a mapped object using Slick - scala

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.

Related

Slick using mapped column type in update statement

I have a trouble in using slick MappedColumnType, the code snippet is as following:
private trait UserTable {
self: HasDatabaseConfigProvider[JdbcProfile] =>
import driver.api._
lazy val userTable = TableQuery[User]
class UserTable(tag: Tag)
extends Table[User](tag, "user") {
implicit def mapper = MappedColumnType.base[JsObject, String](
{ scope: JsObject => scope.toString }, { s: String => Json.parse(s).as[JsObject] }
)
val id = column[Long]("id", O.PrimaryKey, O.AutoInc)
val name = column[String]("name")
val hobby = column[JsObject]("hobby")
def * = (id, name, hobby) <> (User.tupled, User.unapply)
}
}
My User case class is defined as follow:
case class User(id: Long, name: String: hobby: JsObject)
I have corresponding insert and update statement for my database. However the following update statement is not working for me.
def updateQuery(id: Long, newUser: User) = {
userTable.filter(x => x.id === id)
.map(x => (x.hobby))
.update(newUser.hobby)
It will throw a compile error:
No matching Shape found.
Slick does not know how to map the given types.
Possible causes: T in Table[T] does not match your * projection. Or you use an unsupported type in a Query (e.g. scala List).
I think it's pretty straight-forward. Is there something I did wrong?

What's the best practice of simple Slick INSERT/GET?

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")

How to use type mappers with Slick Multi-DB Pattern?

I'm using Slick 3.0 and following the Slick Multi-DB Pattern so that the actual DB driver is abstracted. I use several type mappings which are defined in a single object TypeMappers. Now I want to abstract these type mappings from the particular DB driver too. This is why I moved TypeMappers into a dedicated trait. I believe this is the right approach, but I'm struggling how to import TypeMappers so that the implicits are visible for class User. Any help would be great.
trait TypeMappersTrait { this: Driver =>
import driver.api._
object TypeMappers {
implicit val JavaUtilDateTypeMapper = MappedColumnType.base[java.util.Date, Long](_.getTime, new java.util.Date(_))
implicit val URLMapper = MappedColumnType.base[URL, String](_.toString, new URL(_))
implicit val WrappedByteArrayTypeMapper = MappedColumnType.base[WrappedArray[Byte], Array[Byte]](_.toArray, wrapByteArray(_))
}
}
/** A User contains a name, picture and ID */
case class User(name: String, picture: Picture, id: Option[Int] = None)
/** UserComponent provides database definitions for User objects */
trait UserComponent { this: DriverComponent with PictureComponent =>
import driver.simple._
class Users(tag: Tag) extends Table[(String, Int, Option[Int])](tag, "USERS") {
def id = column[Option[Int]]("USER_ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("USER_NAME", O.NotNull)
def pictureId = column[Int]("PIC_ID", O.NotNull)
def * = (name, pictureId, id)
}
val users = TableQuery[Users]
private val usersAutoInc =
users.map(u => (u.name, u.pictureId)) returning users.map(_.id)
def insert(user: User)(implicit session: Session): User = {
val pic =
if(user.picture.id.isEmpty) insert(user.picture)
else user.picture
val id = usersAutoInc.insert(user.name, pic.id.get)
user.copy(picture = pic, id = id)
}
}
Firstly, I'm assuming you've got a typo in TypeMappersTrait and Driver should in fact be DriverComponent (so this self-type matches annotation up with UserComponent).
Then you can just un-nest your implicit type mappers from object TypeMappers so that they're sitting directly inside TypeMappersTrait, and pull them in via:
trait UserComponent extends TypeMappersTrait

Slick 2.0 Generic CRUD operations

I've been looking around on how to implement a generic trait for commons CRUD and other kinds of operations, I looked at this and this and the method specified are working well.
What I would like to have is a generic method for insertion, my class looks like this at the moment (non generic implementation):
object CampaignModel {
val campaigns = TableQuery[Campaign]
def insert(campaign: CampaignRow)(implicit s: Session) = {
campaigns.insert(campaign)
}
}
What I tried so far, following the first link, was this (generic implementation):
trait PostgresGeneric[T <: Table[A], A] {
val tableReference = TableQuery[T]
def insertGeneric(row: ? What type goes here ?)(implicit s: Session) = tableReference.insert(row)
}
When I inspect the insert method it looks like the right type should be T#TableElementType but my knowledge is pretty basic and I can't wrap my head around types, I tried T and A and the compiler says that the classtype does not conform to the trait one's.
Other infos, the tables are generated with the slick table generation tools
case class CampaignRow(id: Long, name: Option[String])
/** Table description of table campaign. Objects of this class serve as prototypes for rows in queries. */
class Campaign(tag: Tag) extends Table[CampaignRow](tag, "campaign") {
def * = (id, name) <>(CampaignRow.tupled, CampaignRow.unapply)
/** Maps whole row to an option. Useful for outer joins. */
def ? = (id.?, name).shaped.<>({
r => import r._; _1.map(_ => CampaignRow.tupled((_1.get, _2)))
}, (_: Any) => throw new Exception("Inserting into ? projection not supported."))
/** Database column id AutoInc, PrimaryKey */
val id: Column[Long] = column[Long]("id", O.AutoInc, O.PrimaryKey)
/** Database column name */
val name: Column[Option[String]] = column[Option[String]]("name")
}
I managed to make it work, this is my generic trait:
import scala.slick.driver.PostgresDriver
import scala.slick.driver.PostgresDriver.simple._
import path.to.RichTable
trait PostgresGeneric[T <: RichTable[A], A] {
val tableReference: TableQuery[T]
def insert(row: T#TableElementType)(implicit s: Session) =
tableReference.insert(row)
def insertAndGetId(row: T#TableElementType)(implicit s: Session) =
(tableReference returning tableReference.map(_.id)) += row
def deleteById(id: Long)(implicit s: Session): Boolean =
tableReference.filter(_.id === id).delete == 1
def updateById(id: Long, row: T#TableElementType)(implicit s: Session): Boolean =
tableReference.filter(_.id === id).update(row) == 1
def selectById(id: Long)(implicit s: Session): Option[T#TableElementType] =
tableReference.filter(_.id === id).firstOption
def existsById(id: Long)(implicit s: Session): Boolean = {
(for {
row <- tableReference
if row.id === id
} yield row).firstOption.isDefined
}
}
Where RichTable is an abstract class with an id field, this, with the upper bound constraint is useful to get the id field of T#TableElementType (see this for more info):
import scala.slick.driver.PostgresDriver.simple._
import scala.slick.jdbc.{GetResult => GR}
abstract class RichTable[T](tag: Tag, name: String) extends Table[T](tag, name) {
val id: Column[Long] = column[Long]("id", O.PrimaryKey, O.AutoInc)
}
And my campaign table now looks like this:
import scala.slick.driver.PostgresDriver.simple._
import scala.slick.jdbc.{GetResult => GR}
import scala.slick.lifted.TableQuery
case class CampaignRow(id: Long, name: Option[String])
class Campaign(tag: Tag) extends RichTable[CampaignRow](tag, "campaign") {
def * = (id, name) <>(CampaignRow.tupled, CampaignRow.unapply)
def ? = (id.?, name).shaped.<>({
r => import r._; _1.map(_ => CampaignRow.tupled((_1.get, _2)))
}, (_: Any) => throw new Exception("Inserting into ? projection not supported."))
override val id: Column[Long] = column[Long]("id", O.AutoInc, O.PrimaryKey)
val name: Column[Option[String]] = column[Option[String]]("name")
}
The model implementing the generic trait looks like this:
object CampaignModel extends PostgresGeneric[Campaign, CampaignRow] {
override val tableReference: PostgresDriver.simple.TableQuery[Tables.Campaign] =
TableQuery[Campaign]
def insertCampaign(row: CampaignRow) = {
insert(CampaignRow(0, "test"))
}
}

Scala type resolution of inner class

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