Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building a generic DAO for slick

I was tired of always doing something like the following in order to do database access using slick for each of my domain entities.

database withSession {
  implicit session =>
    val entities = TableQuery[EntityTable]
    val id = //Some ID
    val q = for {
      e <- entities if e.id === id
    } yield (e)
    val entity = q.first
}

(Note: EntityTable was defined like described here)

So I decided that I want a generic database access object that handles this for me. The usage should look something like

[...]
val entityDAO = new GenericDAO[Entity, EntityTable, String]()
[...]

database withSession {   implicit session =>
    val id = // Some ID
    val entity = entityDAO.get(id)
}

My try of the implementation of the GenericDAO looks like this

class GenericDAO[T, TB, PK](implicit session: Session) {
  val entities = TableQuery[TB] // Line 1

  def get(id: PK): T = {
    val q = for {
      e <- entities
    } yield (e)
    val res: T = q.first
    res
  }
}

But line 1 leaves me with a compiler error stating that something is wrong with the TB argument.

Multiple markers at this line - type arguments [TB] conform to the bounds of none of the overloaded alternatives of value apply: [E <: scala.slick.lifted.AbstractTable[]]=> scala.slick.lifted.TableQuery[E,E#TableElementType] [E <: scala.slick.lifted.AbstractTable[]](cons: scala.slick.lifted.Tag => E)scala.slick.lifted.TableQuery[E,E#TableElementType] - wrong number of type parameters for overloaded method value apply with alternatives: [E <: scala.slick.lifted.AbstractTable[]]=> scala.slick.lifted.TableQuery[E,E#TableElementType] [E <:
scala.slick.lifted.AbstractTable[
]](cons: scala.slick.lifted.Tag => E)scala.slick.lifted.TableQuery[E,E#TableElementType]

Any suggesions on this issue? Or maybe I am wrong and it is supposed to be implemented in another way. I am open for any solution. Thanks!

like image 393
Coxer Avatar asked Feb 12 '14 13:02

Coxer


1 Answers

First of all, you could write

val entities = TableQuery[EntityTable] // put in a central place for re-use

and then

database.withSession(
    (for {
      e <- entities if e.id === /*Some ID*/
    } yield e).first()(_)
)

or this

database.withSession(entities.filter(_.id === /*Some ID*/).first()(_))

or this

val get = entities.findBy(_.id) // <- reuse this
database.withSession(get(/*Some ID*/).first()(_))

for brevity. This probably makes your whole DAO unnecessary (which is great :)!).

Regarding the error message you got. TableQuery[TB]is a macro, which is a short-hand for TableQuery(tag => new TB(tag)), TB must be a Table and support object creation. You cannot just use the TableQuery macro on an unconstrained type parameter your got from a DAO wrapper. You could constraint TB <: Table[_] but it still wouldn't support object creation, which you cannot constraint in Scala. You could only provide a factory to your DAO (a common pattern is to fetch one as an implicit argument), but that all does not make sense, when you can just write your TableQuery once and store it in a globally accessible place.

Update:

The shortcut works for all of these methods the same way. It's plain Scala. You just turn the method into a function and pass it to the higher-order function withSession, which requires a function from session to anything. Just be aware, that some Slick methods have an empty argument list, which requires ()(_) to turn them into a function and some only have the implicit argument list, which requires only (_). E.g. database.withSession(entities.filter(_.id === /*Some ID*/).delete(_)).

If you wonder about the _. Scala distinguishes methods from functions. def foo(a: A, b: B, ...): R is a method but can be turned into a function of type (A,B,C) => R using foo _. This conversion is called eta expansion and googling for it will turn up more info. Sometimes when a function expected, but you provide a method, the Scala compiler infers the _ and you don't have to write it explicitly. You can also provide some parameters and use _ in place of the parameters you don't want to apply yet. In that case you partially apply the method and get a function back. This is what we do here. We use _ in the place where the methods usually expect a session and get a function back that takes a session. When exactly you have to use _ or (_) or ()(_) has to do with the method signatures and the details interplay between implicit argument lists, nullary methods, methods with empty argument lists, which is general Scala knowledge worth researching at some point.

like image 136
cvogt Avatar answered Sep 23 '22 12:09

cvogt