I'm new to Slick. I'm creating a test suite for a Java application with Scala, ScalaTest and Slick. I'm using slick to prepare data before the test and to do assertions on the data after the test. The database used has some tables with more than 22 columns. I use slick-codegen to generate my schema code.
For tables with more than 22 columns, slick-codegen does not generate a case class, but a HList-based custom type and a companion ‘constructor’ method. As I understand it, this is because the limitation that tuples and case classes can only have 22 fields. The way the code is generated, the fields of a Row-object can only be accessed by index.
I have a couple of questions about this:
For what I understand, the 22 fields restriction for case classes is already fixed in Scala 2.11, right?
If that's the case, would it be possible to customize slick-codegen to generate case classes for all tables? I looked into this: I managed to set override def hlistEnabled = false in an overridden SourceCodeGenerator. But this results in Cannot generate tuple for > 22 columns, please set hlistEnable=true or override compound. So I don’t get the point of being able to disbale HList. May be the catch is in the ‘or override compound’ part, but I don't understand what that means.
Searching the internet on slick and 22 columns, I came across some solutions based on nested tuples. Would it be possible to customize the codegen to use this approach?
If generating code with case classes with > 22 fields is not a viable option, I think it would be possible to generate an ordinary class, which has an ‘accessor’ function for each column, thus providing a ‘mapping’ from index-based access to name-based access. I’d be happy to implement the generation for this myself, but I think I need some pointers where to start. I think it should be able to override the standard codegen for this. I already use an overridden SourceCodeGenerator for some custom data types. But apart from this use case, the documentation of the code generator does not help me that much.
I would really appreciate some help here. Thanks in advance!
I ended up further customizing slick-codegen. First, I'll answer my own questions, then I'll post my solution.
Answers to questions
The 22 arity limit might be lifted for case classes, it is not for tuples. And slick-codegen also generates some tuples, which I was not fully aware of when I asked the question.
Not relevant, see answer 1. (This might become relevant if the 22 arity limit gets lifted for tuples as well.)
I chose not to investigate this further, so this question remains unanswered for now.
This is the approach I took, eventually.
Solution: the generated code
So, I ended up generating "ordinary" classes for tables with more than 22 columns. Let me give an example of what I generate now. (Generator code follows below.) (This example has less than 22 columns, for brevity and readability reasons.)
case class BigAssTableRow(val id: Long, val name: String, val age: Option[Int] = None)
type BigAssTableRowList = HCons[Long,HCons[String,HCons[Option[Int]]], HNil]
object BigAssTableRow {
def apply(hList: BigAssTableRowList) = new BigAssTableRow(hlist.head, hList.tail.head, hList.tail.tail.head)
def unapply(row: BigAssTableRow) = Some(row.id :: row.name :: row.age)
}
implicit def GetResultBoekingenRow(implicit e0: GR[Long], e1: GR[String], e2: GR[Optional[Int]]) = GR{
prs => import prs._
BigAssTableRow.apply(<<[Long] :: <<[String] :: <<?[Int] :: HNil)
}
class BigAssTable(_tableTag: Tag) extends Table[BigAssTableRow](_tableTag, "big_ass") {
def * = id :: name :: age :: :: HNil <> (BigAssTableRow.apply, BigAssTableRow.unapply)
val id: Rep[Long] = column[Long]("id", O.PrimaryKey)
val name: Rep[String] = column[String]("name", O.Length(255,varying=true))
val age: Rep[Option[Int]] = column[Option[Int]]("age", O.Default(None))
}
lazy val BigAssTable = new TableQuery(tag => new BigAssTable(tag))
The hardest part was finding out how the * mapping works in Slick. There's not much documentation, but I found this Stackoverflow answer rather enlightening.
I created the BigAssTableRow object to make the use of HList transparent for the client code. Note that the apply function in the object overloads the apply from the case class. So I can still create entities by calling BigAssTableRow(id: 1L, name: "Foo"), while the * projection can still use the apply function that takes an HList.
So, I can now do things like this:
// I left out the driver import as well as the scala.concurrent imports
// for the Execution context.
val collection = TableQuery[BigAssTable]
val row = BigAssTableRow(id: 1L, name: "Qwerty") // Note that I leave out the optional age
Await.result(db.run(collection += row), Duration.Inf)
Await.result(db.run(collection.filter(_.id === 1L).result), Duration.Inf)
For this code it's totally transparent wether tuples or HLists are used under the hood.
Solution: how this is generated
I'll just post my entire generator code here. It's not perfect; please let me know if you have suggestions for improvement! Huge parts are just copied from the slick.codegen.AbstractSourceCodeGenerator and related classes and then slightly changed. There are also some things that are not directly related to this question, such as the addition of the java.time.* data types and the filtering of specific tables. I left them in, because they might be of use. Also note that this example is for a Postgres database.
import slick.codegen.SourceCodeGenerator
import slick.driver.{JdbcProfile, PostgresDriver}
import slick.jdbc.meta.MTable
import slick.model.Column
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
object MySlickCodeGenerator {
val slickDriver = "slick.driver.PostgresDriver"
val jdbcDriver = "org.postgresql.Driver"
val url = "jdbc:postgresql://localhost:5432/dbname"
val outputFolder = "/path/to/project/src/test/scala"
val pkg = "my.package"
val user = "user"
val password = "password"
val driver: JdbcProfile = Class.forName(slickDriver + "$").getField("MODULE$").get(null).asInstanceOf[JdbcProfile]
val dbFactory = driver.api.Database
val db = dbFactory.forURL(url, driver = jdbcDriver, user = user, password = password, keepAliveConnection = true)
// The schema is generated using Liquibase, which creates these tables that I don't want to use
def excludedTables = Array("databasechangelog", "databasechangeloglock")
def tableFilter(table: MTable): Boolean = {
!excludedTables.contains(table.name.name) && schemaFilter(table.name.schema)
}
// There's also an 'audit' schema in the database, I don't want to use that one
def schemaFilter(schema: Option[String]): Boolean = {
schema match {
case Some("public") => true
case None => true
case _ => false
}
}
// Fetch data model
val modelAction = PostgresDriver.defaultTables
.map(_.filter(tableFilter))
.flatMap(PostgresDriver.createModelBuilder(_, ignoreInvalidDefaults = false).buildModel)
val modelFuture = db.run(modelAction)
// customize code generator
val codegenFuture = modelFuture.map(model => new SourceCodeGenerator(model) {
// add custom import for added data types
override def code = "import my.package.Java8DateTypes._" + "\n" + super.code
override def Table = new Table(_) {
table =>
// Use different factory and extractor functions for tables with > 22 columns
override def factory = if(columns.size == 1) TableClass.elementType else if(columns.size <= 22) s"${TableClass.elementType}.tupled" else s"${EntityType.name}.apply"
override def extractor = if(columns.size <= 22) s"${TableClass.elementType}.unapply" else s"${EntityType.name}.unapply"
override def EntityType = new EntityTypeDef {
override def code = {
val args = columns.map(c =>
c.default.map( v =>
s"${c.name}: ${c.exposedType} = $v"
).getOrElse(
s"${c.name}: ${c.exposedType}"
)
)
val callArgs = columns.map(c => s"${c.name}")
val types = columns.map(c => c.exposedType)
if(classEnabled){
val prns = (parents.take(1).map(" extends "+_) ++ parents.drop(1).map(" with "+_)).mkString("")
s"""case class $name(${args.mkString(", ")})$prns"""
} else {
s"""
/** Constructor for $name providing default values if available in the database schema. */
case class $name(${args.map(arg => {s"val $arg"}).mkString(", ")})
type ${name}List = ${compoundType(types)}
object $name {
def apply(hList: ${name}List): $name = new $name(${callArgs.zipWithIndex.map(pair => s"hList${tails(pair._2)}.head").mkString(", ")})
def unapply(row: $name) = Some(${compoundValue(callArgs.map(a => s"row.$a"))})
}
""".trim
}
}
}
override def PlainSqlMapper = new PlainSqlMapperDef {
override def code = {
val positional = compoundValue(columnsPositional.map(c => if (c.fakeNullable || c.model.nullable) s"<<?[${c.rawType}]" else s"<<[${c.rawType}]"))
val dependencies = columns.map(_.exposedType).distinct.zipWithIndex.map{ case (t,i) => s"""e$i: GR[$t]"""}.mkString(", ")
val rearranged = compoundValue(desiredColumnOrder.map(i => if(columns.size > 22) s"r($i)" else tuple(i)))
def result(args: String) = s"$factory($args)"
val body =
if(autoIncLastAsOption && columns.size > 1){
s"""
val r = $positional
import r._
${result(rearranged)} // putting AutoInc last
""".trim
} else {
result(positional)
}
s"""
implicit def $name(implicit $dependencies): GR[${TableClass.elementType}] = GR{
prs => import prs._
${indent(body)}
}
""".trim
}
}
override def TableClass = new TableClassDef {
override def star = {
val struct = compoundValue(columns.map(c=>if(c.fakeNullable)s"Rep.Some(${c.name})" else s"${c.name}"))
val rhs = s"$struct <> ($factory, $extractor)"
s"def * = $rhs"
}
}
def tails(n: Int) = {
List.fill(n)(".tail").mkString("")
}
// override column generator to add additional types
override def Column = new Column(_) {
override def rawType = {
typeMapper(model).getOrElse(super.rawType)
}
}
}
})
def typeMapper(column: Column): Option[String] = {
column.tpe match {
case "java.sql.Date" => Some("java.time.LocalDate")
case "java.sql.Timestamp" => Some("java.time.LocalDateTime")
case _ => None
}
}
def doCodeGen() = {
def generator = Await.result(codegenFuture, Duration.Inf)
generator.writeToFile(slickDriver, outputFolder, pkg, "Tables", "Tables.scala")
}
def main(args: Array[String]) {
doCodeGen()
db.close()
}
}
Update 2019-02-15: *with the release of Slick 3.3.0, as answered by #Marcus there's built-in support for code generation of tables with > 22 columns.
As of Slick 3.2.0, the simplest solution for >22 param case class is to define the default projection in the * method using mapTo instead of the <> operator (per documented unit test):
case class BigCase(id: Int,
p1i1: Int, p1i2: Int, p1i3: Int, p1i4: Int, p1i5: Int, p1i6: Int,
p2i1: Int, p2i2: Int, p2i3: Int, p2i4: Int, p2i5: Int, p2i6: Int,
p3i1: Int, p3i2: Int, p3i3: Int, p3i4: Int, p3i5: Int, p3i6: Int,
p4i1: Int, p4i2: Int, p4i3: Int, p4i4: Int, p4i5: Int, p4i6: Int)
class bigCaseTable(tag: Tag) extends Table[BigCase](tag, "t_wide") {
def id = column[Int]("id", O.PrimaryKey)
def p1i1 = column[Int]("p1i1")
def p1i2 = column[Int]("p1i2")
def p1i3 = column[Int]("p1i3")
def p1i4 = column[Int]("p1i4")
def p1i5 = column[Int]("p1i5")
def p1i6 = column[Int]("p1i6")
def p2i1 = column[Int]("p2i1")
def p2i2 = column[Int]("p2i2")
def p2i3 = column[Int]("p2i3")
def p2i4 = column[Int]("p2i4")
def p2i5 = column[Int]("p2i5")
def p2i6 = column[Int]("p2i6")
def p3i1 = column[Int]("p3i1")
def p3i2 = column[Int]("p3i2")
def p3i3 = column[Int]("p3i3")
def p3i4 = column[Int]("p3i4")
def p3i5 = column[Int]("p3i5")
def p3i6 = column[Int]("p3i6")
def p4i1 = column[Int]("p4i1")
def p4i2 = column[Int]("p4i2")
def p4i3 = column[Int]("p4i3")
def p4i4 = column[Int]("p4i4")
def p4i5 = column[Int]("p4i5")
def p4i6 = column[Int]("p4i6")
// HList-based wide case class mapping
def m3 = (
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 :: HNil
).mapTo[BigCase]
def * = m3
}
EDIT
So, if you then want the slick-codegen to produce huge tables using the mapTo method described above, you override the relevant parts to the code generator and add in a mapTo statement:
package your.package
import slick.codegen.SourceCodeGenerator
import slick.{model => m}
class HugeTableCodegen(model: m.Model) extends SourceCodeGenerator(model) with GeneratorHelpers[String, String, String]{
override def Table = new Table(_) {
table =>
// always defines types using case classes
override def EntityType = new EntityTypeDef{
override def classEnabled = true
}
// allow compound statements using HNil, but not for when "def *()" is being defined, instead use mapTo statement
override def compoundValue(values: Seq[String]): String = {
// values.size>22 assumes that this must be for the "*" operator and NOT a primary/foreign key
if(hlistEnabled && values.size > 22) values.mkString("(", " :: ", s" :: HNil).mapTo[${StringExtensions(model.name.table).toCamelCase}Row]")
else if(hlistEnabled) values.mkString(" :: ") + " :: HNil"
else if (values.size == 1) values.head
else s"""(${values.mkString(", ")})"""
}
// should always be case classes, so no need to handle hlistEnabled here any longer
override def compoundType(types: Seq[String]): String = {
if (types.size == 1) types.head
else s"""(${types.mkString(", ")})"""
}
}
}
You then structure the codegen code in a separate project as documented so that it generates the source at compile time. You can pass your classname as an argument to the SourceCodeGenerator you're extending:
lazy val generateSlickSchema = taskKey[Seq[File]]("Generates Schema definitions for SQL tables")
generateSlickSchema := {
val managedSourceFolder = sourceManaged.value / "main" / "scala"
val packagePath = "your.sql.table.package"
(runner in Compile).value.run(
"slick.codegen.SourceCodeGenerator", (dependencyClasspath in Compile).value.files,
Array(
"env.db.connectorProfile",
"slick.db.driver",
"slick.db.url",
managedSourceFolder.getPath,
packagePath,
"slick.db.user",
"slick.db.password",
"true",
"your.package.HugeTableCodegen"
),
streams.value.log
)
Seq(managedSourceFolder / s"${packagePath.replace(".","/")}/Tables.scala")
}
There are few options available as you have already found out - nested tuples, conversion from Slick HList to Shapeless HList and then to case classes and so on.
I found all those options too complicated for the task and went with customised Slick Codegen to generate simple wrapper class with accessors.
Have a look at this gist.
class MyCodegenCustomisations(model: Model) extends slick.codegen.SourceCodeGenerator(model){
import ColumnDetection._
override def Table = new Table(_){
table =>
val columnIndexByName = columns.map(_.name).zipWithIndex.toMap
def getColumnIndex(columnName: String): Option[Int] = {
columnIndexByName.get(columnName)
}
private def getWrapperCode: Seq[String] = {
if (columns.length <= 22) {
//do not generate wrapper for tables which get case class generated by Slick
Seq.empty[String]
} else {
val lines =
columns.map{c =>
getColumnIndex(c.name) match {
case Some(colIndex) =>
//lazy val firstname: Option[String] = row.productElement(1).asInstanceOf[Option[String]]
val colType = c.exposedType
val line = s"lazy val ${c.name}: $colType = values($colIndex).asInstanceOf[$colType]"
line
case None => ""
}
}
Seq("",
"/*",
"case class Wrapper(private val row: Row) {",
"// addressing HList by index is very slow, let's convert it to vector",
"private lazy val values = row.toList.toVector",
""
) ++ lines ++ Seq("}", "*/", "")
}
}
override def code: Seq[String] = {
val originalCode = super.code
originalCode ++ this.getWrapperCode
}
}
}
This issue is solved in Slick 3.3:
https://github.com/slick/slick/pull/1889/
This solution provides def * and def ? and also supports plain SQL.
Related
I'm trying to figure out if a member field in any given case class is also a case class. Taken from this answer, given an instance or an object, I can pass it along and determine if it's a case class:
def isCaseClass(v: Any): Boolean = {
import reflect.runtime.universe._
val typeMirror = runtimeMirror(v.getClass.getClassLoader)
val instanceMirror = typeMirror.reflect(v)
val symbol = instanceMirror.symbol
symbol.isCaseClass
}
However, what I'd like, is to take a case class, extract all of its member fields, and find out which ones are case classes themselves. Something in this manner:
def innerCaseClasses[A](parentCaseClass:A): List[Class[_]] = {
val nestedCaseClasses = ListBuffer[Class[_]]()
val fields = parentCaseClass.getClass.getDeclaredFields
fields.foreach(field => {
if (??? /*field is case class */ ) {
nestedCaseClasses += field.getType
}
})
nestedCaseClasses.toList
}
I thought maybe I could extract the fields, their classes, and use reflection to instantiate a new instance of that member field as its own class. I'm not 100% how to do that, and it seems like perhaps there's an easier way. Is there?
Ah! I've figured it out (simplified the function which tells the determination):
import reflect.runtime.universe._
case class MyThing(str:String, num:Int)
case class WithMyThing(name:String, aThing:MyThing)
val childThing = MyThing("Neat" , 293923)
val parentCaseClass = WithMyThing("Nate", childThing)
def isCaseClass(v: Any): Boolean = {
val typeMirror = runtimeMirror(v.getClass.getClassLoader)
val instanceMirror = typeMirror.reflect(v)
val symbol = instanceMirror.symbol
symbol.isCaseClass
}
def innerCaseClasses[A](parentCaseClass:A): Unit = {
val fields = parentCaseClass.asInstanceOf[Product].productIterator
fields.foreach(field => {
println(s"Field: ${field.getClass.getSimpleName} isCaseClass? " + isCaseClass(field))
})
}
innerCaseClasses(parentCaseClass)
printout:
Field: String isCaseClass? false
Field: MyThing isCaseClass? true
I would like to understand this error:
found : row.type (with underlying type _#TableElementType)
required: _1#TableElementType
Looks like I was very close, but what is this "1" in _1#TableElementType? Can I convert one in the other?
Edit: useful bits of codes for context (Play + Slick):
abstract class GenericDAO[T <: AbstractTable[_]](...) {
def table: TableQuery[T]
def insert(model: T#TableElementType) = db run (table += model)
}
trait TableObject[T <: AbstractTable[_]] {
def rowFromJson(jsObject: JsObject): T#TableElementType
def dao(driver: JdbcProfile, db: Database): GenericDAO[T]
}
// Controller Action with an instance implementing `tableObject` above:
val tableObject = tableObjectFactory("test")
val row = tableObject.rowFromJson(request.body.asJson.get)
val dao = tableObject.dao(driver, db) // tableObject has a DOA extending GenericDAO
dao.insert(row)
Example of tableObject:
object TestTable extends TableObject[Test] {
def dao(driver: JdbcProfile, db: Database) = new TestDAO(driver, db)
def rowFromJson(j: JsObject): TestRow = { TestRow(...) }
class TestDAO(...) extends GenericDAO[Test](driver, db) { ... }
}
I use a factory to get the right one from the url:
object TableObjectFactory {
def tableObjectFactory(name: String) = {
name match {
case "test" => TestTable
case "projects" => ProjectsTable
case "people" => PeopleTable
...
}
}
}
Although it doesn't explain much, it works if I make the DAO parse the request body and insert, instead of producing the row object separately and applying one of the DAO's methods on it.
I got all kinds of similar errors with names such as _$1#TableElementType, _1$u#TableElementType etc., but I think they are compiler aliases for different instances of the same class.
So the solution was to do
val j: JsValue = request.body.asJson.get
val tableObject: TableObject[_] = tableObjectFactory(table)
val dao = tableObject.dao(driver, db)
val res: Future[Int] = dao.insert(j)
where this new insert method now is abstract in GenericDAO, and in the concrete implementations takes a JsValue and parses it, then inserts:
class TestDAO(override val driver: JdbcProfile, override val db: Database) extends GenericDAO[Test](driver, db) {
import this.driver.api._
val table = TableQuery[Test]
//def insert(model: TestRow) = db run (table += model) // NO!
def insert(j: JsValue): Future[Int] = {
val row = TestRow(
(j \ "id").as[Int],
(j \ "name").as[String],
(j \ "value").as[Float],
(j \ "other").as[String]
)
db run (table += row)
}
}
At the same time, it makes Play forms completely useless, which is a good thing anyway.
I'm using scala and slick here, and I have a baserepository which is responsible for doing the basic crud of my classes.
For a design decision, we do have updatedTime and createdTime columns all handled by the application, and not by triggers in database. Both of this fields are joda DataTime instances.
Those fields are defined in two traits called HasUpdatedAt, and HasCreatedAt, for the tables
trait HasCreatedAt {
val createdAt: Option[DateTime]
}
case class User(name:String,createdAt:Option[DateTime] = None) extends HasCreatedAt
I would like to know how can I use reflection to call the user copy method, to update the createdAt value during the database insertion method.
Edit after #vptron and #kevin-wright comments
I have a repo like this
trait BaseRepo[ID, R] {
def insert(r: R)(implicit session: Session): ID
}
I want to implement the insert just once, and there I want to createdAt to be updated, that's why I'm not using the copy method, otherwise I need to implement it everywhere I use the createdAt column.
This question was answered here to help other with this kind of problem.
I end up using this code to execute the copy method of my case classes using scala reflection.
import reflect._
import scala.reflect.runtime.universe._
import scala.reflect.runtime._
class Empty
val mirror = universe.runtimeMirror(getClass.getClassLoader)
// paramName is the parameter that I want to replacte the value
// paramValue is the new parameter value
def updateParam[R : ClassTag](r: R, paramName: String, paramValue: Any): R = {
val instanceMirror = mirror.reflect(r)
val decl = instanceMirror.symbol.asType.toType
val members = decl.members.map(method => transformMethod(method, paramName, paramValue, instanceMirror)).filter {
case _: Empty => false
case _ => true
}.toArray.reverse
val copyMethod = decl.declaration(newTermName("copy")).asMethod
val copyMethodInstance = instanceMirror.reflectMethod(copyMethod)
copyMethodInstance(members: _*).asInstanceOf[R]
}
def transformMethod(method: Symbol, paramName: String, paramValue: Any, instanceMirror: InstanceMirror) = {
val term = method.asTerm
if (term.isAccessor) {
if (term.name.toString == paramName) {
paramValue
} else instanceMirror.reflectField(term).get
} else new Empty
}
With this I can execute the copy method of my case classes, replacing a determined field value.
As comments have said, don't change a val using reflection. Would you that with a java final variable? It makes your code do really unexpected things. If you need to change the value of a val, don't use a val, use a var.
trait HasCreatedAt {
var createdAt: Option[DateTime] = None
}
case class User(name:String) extends HasCreatedAt
Although having a var in a case class may bring some unexpected behavior e.g. copy would not work as expected. This may lead to preferring not using a case class for this.
Another approach would be to make the insert method return an updated copy of the case class, e.g.:
trait HasCreatedAt {
val createdAt: Option[DateTime]
def withCreatedAt(dt:DateTime):this.type
}
case class User(name:String,createdAt:Option[DateTime] = None) extends HasCreatedAt {
def withCreatedAt(dt:DateTime) = this.copy(createdAt = Some(dt))
}
trait BaseRepo[ID, R <: HasCreatedAt] {
def insert(r: R)(implicit session: Session): (ID, R) = {
val id = ???//insert into db
(id, r.withCreatedAt(??? /*now*/))
}
}
EDIT:
Since I didn't answer your original question and you may know what you are doing I am adding a way to do this.
import scala.reflect.runtime.universe._
val user = User("aaa", None)
val m = runtimeMirror(getClass.getClassLoader)
val im = m.reflect(user)
val decl = im.symbol.asType.toType.declaration("createdAt":TermName).asTerm
val fm = im.reflectField(decl)
fm.set(??? /*now*/)
But again, please don't do this. Read this stackoveflow answer to get some insight into what it can cause (vals map to final fields).
Consider the Favorites table object below, we want to write a query to find Favorites by their type (defined below). We have also defined a Typemapper, to map a FavoriteType to a String for the database
import scala.slick.driver.PostgresDriver.simple._
//Other imports have been omitted in this question
object Favorites extends Table[Favorite]("favorites") {
// Convert the favoriteTypes to strings for the database
implicit val favoriteMapping: TypeMapper[FavorietType] = MappedTypeMapper.base[FavorietType, String](
favType => FavorietType.values.find(_ == favType).get.mapping,
mapping => FavorietType.values.find(_.mapping == mapping).get
)
def favoriteType = column[FavoriteType]("type")
//other columns here
This is the query I want to write (however it does not compile)
def queryByFavoriteType(ftype : FavoriteType)(implicit s: Session) = {
for(
f <- Favorieten if f.favoriteType === ftype
) yield f
}
}
Here I have defined de different FavoriteType objects (this is outside the Favorieten Object)
sealed case class FavorietType(mapping: String) {
override def toString = mapping.capitalize
}
object FavoriteType {
object Exam extends FavoriteType("examen")
object Topic extends FavoriteType("onderwerp")
object Paper extends FavoriteType("profielwerkstuk")
val values = Seq(Exam , Topic , Paper )
}
The problem I have here is that the query does not compile:
value === is not a member of scala.slick.lifted.Column[models.gebruiker.FavorietType]
It appears that === can not be used to compare a User-defined type, is this true? Is there an alternative way to do this?
Edit
Related issue: before I had my TypeMapper without explicit type, it was defined as implicit val favoriteMapping = MappedTypeMapper.base[FavorietType, String]( ...
When I would write a query that would compare a FavoriteType.Exam (for example) such as
def queryByFavoriteExam()(implicit s: Session) = {
for(f <- Favorieten if f.favorietType === FavorietType.Exam) yield f
}
This would result in the error could not find implicit value for evidence parameter of type scala.slick.lifted.TypeMapper[models.gebruiker.FavorietType.Exam.type]
The solution for this is the same as the one presented below
When in doubt with Slick, go check out the unit tests. After reading their docs on mapping in a custom type and then looking at their unit tests, I got your query code to compile by changing it to:
def queryByFavoriteType(ftype : FavoriteType)(implicit s: Session) = {
for(f <- Favorites if f.favoriteType === (ftype:FavoriteType)) yield f
}
Also, I had imported the H2Driver just to get things to compile (import scala.slick.driver.H2Driver.simple._). I was assuming that you also had imported whatever driver it is that you need for your db.
EDIT
My full code example is as follows:
import scala.slick.driver.PostgresDriver.simple._
import scala.slick.session.Session
sealed case class FavoriteType(mapping: String) {
override def toString = mapping.capitalize
}
case class Favorite(ft:FavoriteType, foo:String)
object FavoriteType {
object Exam extends FavoriteType("examen")
object Topic extends FavoriteType("onderwerp")
object Paper extends FavoriteType("profielwerkstuk")
val values = Seq(Exam , Topic , Paper )
}
object Favorites extends Table[Favorite]("favorites") {
// Convert the favoriteTypes to strings for the database
implicit val favoriteMapping = MappedTypeMapper.base[FavoriteType, String](
{favType => FavoriteType.values.find(_ == favType).get.mapping},
{mapping => FavoriteType.values.find(_.mapping == mapping).get}
)
def favoriteType = column[FavoriteType]("type")
def foo = column[String]("foo")
def * = favoriteType ~ foo <> (Favorite.apply _, Favorite.unapply _)
def queryByFavoriteType(ftype : FavoriteType)(implicit s: Session) = {
for(f <- Favorites if f.favoriteType === (ftype:FavoriteType)) yield f
}
}
Having a trait
trait Persisted {
def id: Long
}
how do I implement a method that accepts an instance of any case class and returns its copy with the trait mixed in?
The signature of the method looks like:
def toPersisted[T](instance: T, id: Long): T with Persisted
This can be done with macros (that are officially a part of Scala since 2.10.0-M3). Here's a gist example of what you are looking for.
1) My macro generates a local class that inherits from the provided case class and Persisted, much like new T with Persisted would do. Then it caches its argument (to prevent multiple evaluations) and creates an instance of the created class.
2) How did I know what trees to generate? I have a simple app, parse.exe that prints the AST that results from parsing input code. So I just invoked parse class Person$Persisted1(first: String, last: String) extends Person(first, last) with Persisted, noted the output and reproduced it in my macro. parse.exe is a wrapper for scalac -Xprint:parser -Yshow-trees -Ystop-after:parser. There are different ways to explore ASTs, read more in "Metaprogramming in Scala 2.10".
3) Macro expansions can be sanity-checked if you provide -Ymacro-debug-lite as an argument to scalac. In that case all expansions will be printed out, and you'll be able to detect codegen errors faster.
edit. Updated the example for 2.10.0-M7
It is not possible to achieve what you want using vanilla scala. The problem is that the mixins like the following:
scala> class Foo
defined class Foo
scala> trait Bar
defined trait Bar
scala> val fooWithBar = new Foo with Bar
fooWithBar: Foo with Bar = $anon$1#10ef717
create a Foo with Bar mixed in, but it is not done at runtime. The compiler simply generates a new anonymous class:
scala> fooWithBar.getClass
res3: java.lang.Class[_ <: Foo] = class $anon$1
See Dynamic mixin in Scala - is it possible? for more info.
What you are trying to do is known as record concatenation, something that Scala's type system does not support. (Fwiw, there exist type systems - such as this and this - that provide this feature.)
I think type classes might fit your use case, but I cannot tell for sure as the question doesn't provide sufficient information on what problem you are trying to solve.
Update
You can find an up to date working solution, which utilizes a Toolboxes API of Scala 2.10.0-RC1 as part of SORM project.
The following solution is based on the Scala 2.10.0-M3 reflection API and Scala Interpreter. It dynamically creates and caches classes inheriting from the original case classes with the trait mixed in. Thanks to caching at maximum this solution should dynamically create only one class for each original case class and reuse it later.
Since the new reflection API isn't that much disclosed nor is it stable and there are no tutorials on it yet this solution may involve some stupid repitative actions and quirks.
The following code was tested with Scala 2.10.0-M3.
1. Persisted.scala
The trait to be mixed in. Please note that I've changed it a bit due to updates in my program
trait Persisted {
def key: String
}
2. PersistedEnabler.scala
The actual worker object
import tools.nsc.interpreter.IMain
import tools.nsc._
import reflect.mirror._
object PersistedEnabler {
def toPersisted[T <: AnyRef](instance: T, key: String)
(implicit instanceTag: TypeTag[T]): T with Persisted = {
val args = {
val valuesMap = propertyValuesMap(instance)
key ::
methodParams(constructors(instanceTag.tpe).head.typeSignature)
.map(_.name.decoded.trim)
.map(valuesMap(_))
}
persistedClass(instanceTag)
.getConstructors.head
.newInstance(args.asInstanceOf[List[Object]]: _*)
.asInstanceOf[T with Persisted]
}
private val persistedClassCache =
collection.mutable.Map[TypeTag[_], Class[_]]()
private def persistedClass[T](tag: TypeTag[T]): Class[T with Persisted] = {
if (persistedClassCache.contains(tag))
persistedClassCache(tag).asInstanceOf[Class[T with Persisted]]
else {
val name = generateName()
val code = {
val sourceParams =
methodParams(constructors(tag.tpe).head.typeSignature)
val newParamsList = {
def paramDeclaration(s: Symbol): String =
s.name.decoded + ": " + s.typeSignature.toString
"val key: String" :: sourceParams.map(paramDeclaration) mkString ", "
}
val sourceParamsList =
sourceParams.map(_.name.decoded).mkString(", ")
val copyMethodParamsList =
sourceParams.map(s => s.name.decoded + ": " + s.typeSignature.toString + " = " + s.name.decoded).mkString(", ")
val copyInstantiationParamsList =
"key" :: sourceParams.map(_.name.decoded) mkString ", "
"""
class """ + name + """(""" + newParamsList + """)
extends """ + tag.sym.fullName + """(""" + sourceParamsList + """)
with """ + typeTag[Persisted].sym.fullName + """ {
override def copy(""" + copyMethodParamsList + """) =
new """ + name + """(""" + copyInstantiationParamsList + """)
}
"""
}
interpreter.compileString(code)
val c =
interpreter.classLoader.findClass(name)
.asInstanceOf[Class[T with Persisted]]
interpreter.reset()
persistedClassCache(tag) = c
c
}
}
private lazy val interpreter = {
val settings = new Settings()
settings.usejavacp.value = true
new IMain(settings, new NewLinePrintWriter(new ConsoleWriter, true))
}
private var generateNameCounter = 0l
private def generateName() = synchronized {
generateNameCounter += 1
"PersistedAnonymous" + generateNameCounter.toString
}
// REFLECTION HELPERS
private def propertyNames(t: Type) =
t.members.filter(m => !m.isMethod && m.isTerm).map(_.name.decoded.trim)
private def propertyValuesMap[T <: AnyRef](instance: T) = {
val t = typeOfInstance(instance)
propertyNames(t)
.map(n => n -> invoke(instance, t.member(newTermName(n)))())
.toMap
}
private type MethodType = {def params: List[Symbol]; def resultType: Type}
private def methodParams(t: Type): List[Symbol] =
t.asInstanceOf[MethodType].params
private def methodResultType(t: Type): Type =
t.asInstanceOf[MethodType].resultType
private def constructors(t: Type): Iterable[Symbol] =
t.members.filter(_.kind == "constructor")
private def fullyQualifiedName(s: Symbol): String = {
def symbolsTree(s: Symbol): List[Symbol] =
if (s.enclosingTopLevelClass != s)
s :: symbolsTree(s.enclosingTopLevelClass)
else if (s.enclosingPackageClass != s)
s :: symbolsTree(s.enclosingPackageClass)
else
Nil
symbolsTree(s)
.reverseMap(_.name.decoded)
.drop(1)
.mkString(".")
}
}
3. Sandbox.scala
The test app
import PersistedEnabler._
object Sandbox extends App {
case class Artist(name: String, genres: Set[Genre])
case class Genre(name: String)
val artist = Artist("Nirvana", Set(Genre("rock"), Genre("grunge")))
val persisted = toPersisted(artist, "some-key")
assert(persisted.isInstanceOf[Persisted])
assert(persisted.isInstanceOf[Artist])
assert(persisted.key == "some-key")
assert(persisted.name == "Nirvana")
assert(persisted == artist) // an interesting and useful effect
val copy = persisted.copy(name = "Puddle of Mudd")
assert(copy.isInstanceOf[Persisted])
assert(copy.isInstanceOf[Artist])
// the only problem: compiler thinks that `copy` does not implement `Persisted`, so to access `key` we have to specify it manually:
assert(copy.asInstanceOf[Artist with Persisted].key == "some-key")
assert(copy.name == "Puddle of Mudd")
assert(copy != persisted)
}
While it's not possible to compose an object AFTER it's creation, you can have very wide tests to determine if the object is of a specific composition using type aliases and definition structs:
type Persisted = { def id: Long }
class Person {
def id: Long = 5
def name = "dude"
}
def persist(obj: Persisted) = {
obj.id
}
persist(new Person)
Any object with a def id:Long will qualify as Persisted.
Achieving what I THINK you are trying to do is possible with implicit conversions:
object Persistable {
type Compatible = { def id: Long }
implicit def obj2persistable(obj: Compatible) = new Persistable(obj)
}
class Persistable(val obj: Persistable.Compatible) {
def persist() = println("Persisting: " + obj.id)
}
import Persistable.obj2persistable
new Person().persist()