Please bear with me, there is some context until the OP makes sense. I'm using Slick 3.1.x and the slick code generator. btw The whole source code can be found in the play-authenticate-usage-scala github project. For this project I'd like to have a slick generic Dao to avoid repeating the same boilerplate code for every model.
I have a postgres sql script that creates the database using evolutions here: 1.sql
I then invoke a generator that generates the following data model: Tables.scala
To be able to provide generic dao slick implementations for the model classes I need them to comply to some basic abstractions e.g.
- Entity trait: Every entity has an
id
e.g. needed for dao'sfindById
- AutoIncEntity trait declares the method
def copyWithNewId(id : PK) : Entity[PK]
. This is needed for the dao's implementation ofcreateAndFetch
that persists a new entity and retrieves the auto generatedid
PK in one step.
This copyWithNewId
is the point of the OP. Note that it is called copyWithNewId
and not copy
to avoid infinite recursion. To be able to implement the GenericDaoAutoIncImpl that allows inserting and immediately fetching the auto generated id
, the entity row requires a copy(id = id)
method coming from the <Model>Row
case class that at the point of defining the GenericDaoAutoIncImpl
it is not yet known. The relevant implementation is the following:
override def createAndFetch(entity: E): Future[Option[E]] = {
val insertQuery = tableQuery returning tableQuery.map(_.id)
into ((row, id) => row.copyWithNewId(id))
db.run((insertQuery += entity).flatMap(row => findById(row.id)))
}
And this requires me to implement the copyWithNewId
method in every AutoInc
id
generated model and that is not nice e.g.
// generated code and modified later to adapt it for the generic dao
case class UserRow(id: Long, ...) extends AutoIncEntity[Long] with Subject {
override def copyWithNewId(id : Long) : Entity[Long] = this.copy(id = id)
}
However if I could - using some Scala trick - define my <Model>Row
case classes subclass of a Base class that is copyable and copies itself except for the passed id
i.e. IdCopyable
with copy(id = id)
then I would not need to implement over and over this copyWithNewId
for every <Model>Row
generated case class.
Is there a way to abstract or "pull up" refactor copy(id = id)
for any case class that contains an id
attribute? is there any other recommended solution?
UPDATE 1 The following pretty much summarizes the problem I have:
scala> abstract class BaseA[A <: BaseA[_]] { def copy(id : Int) : A }
defined class BaseA
scala> case class A(id: Int) extends BaseA[A]
<console>:12: error: class A needs to be abstract, since method copy in class BaseA of type (id: Int)A is not defined
case class A(id: Int) extends BaseA[A]
^
scala> case class A(id: Int); val a = A(5); a.copy(6)
defined class A
a: A = A(5)
res0: A = A(6)
UPDATE 2 Using the proposed solution below I get the following compilation errors:
[error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/GenericDaoAutoIncImpl.scala:26: could not find implicit value for parameter gen: shapeless.Generic.Aux[E,Repr]
[error] val insertQuery = tableQuery returning tableQuery.map(_.id) into ((row, id) => row.copyWithNewId(id))
[error] ^
[error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/GenericDaoAutoIncImpl.scala:27: value id is not a member of insertQuery.SingleInsertResult
[error] db.run((insertQuery += entity).flatMap(row => findById(row.id)))
[error] ^
[error] two errors found
UPDATE 3 using and adapting the proposed lenses solution below I get the following compiler errors:
import shapeless._, tag.@@
import shapeless._
import tag.$at$at
/**
* Identifyable base for all Strong Entity Model types
* @tparam PK Primary key type
* @tparam E Actual case class EntityRow type
*/
trait AutoIncEntity[PK, E <: AutoIncEntity[PK, E]] extends Entity[PK] { self: E =>
//------------------------------------------------------------------------
// public
//------------------------------------------------------------------------
/**
* Returns the entity with updated id as generated by the database
* @param id The entity id
* @return the entity with updated id as generated by the database
*/
def copyWithNewId(id : PK)(implicit mkLens: MkFieldLens.Aux[E, Symbol @@ Witness.`"id"`.T, PK]) : E = {
(lens[E] >> 'id).set(self)(id)
}
}
I then get the following compiler error:
[error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/GenericDaoAutoIncImpl.scala:26: could not find implicit value for parameter mkLens: shapeless.MkFieldLens.Aux[E,shapeless.tag.@@[Symbol,String("id")],PK]
[error] val insertQuery = tableQuery returning tableQuery.map(_.id) into ((row, id) => row.copyWithNewId(id))
[error] ^
[error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/GenericDaoAutoIncImpl.scala:27: value id is not a member of insertQuery.SingleInsertResult
[error] db.run((insertQuery += entity).flatMap(row => findById(row.id)))
[error] ^