-
-
Save cvogt/9239494 to your computer and use it in GitHub Desktop.
// Please comment in case of typos or bugs | |
import scala.slick.driver.H2Driver._ | |
val db = Database.for...(...) | |
case class Record( ... ) | |
class Records(tag: Tag) extends Table[Record](tag,"RECORDS"){ | |
... | |
def * = ... <> (Record.tupled,Record.unapply) | |
// place additional methods here which return values of type Column | |
// (compute artificial columns based on other columns) based on a | |
// records row | |
def foo = (col1+col2, col3) | |
... | |
} | |
object records extends TableQuery(new Records(_)){ | |
// Only place methods here which return a not-yet executed Query or | |
// (individually meaningful) Column which is based on the WHOLE table. | |
// Seldom useful, often better placed in RecordQueryExentions | |
def foos = this.map(_.foo) // <- can only be used on the whole table | |
... | |
} | |
// usage: | |
db.withSession{ records.map(_.foo).run } | |
db.withSession{ records.foos.run } | |
implicit class RecordQueryExtensions(val q: Query[Records,Record]) extends AnyVal{ | |
// Only place methods here which return a not-yet executed Query or | |
// (individually meaningful) Column. | |
// Methods placed here can be chained/combined. | |
... | |
def foos = q.map(_.foo) | |
def byId(id: Column[Long]) = q.filter(_.id === id) | |
} | |
// usage: | |
db.withSession{ records.filter(_.age < 30).foos.run } | |
object RecordsDAO{ | |
// place methods here that require a database connection | |
// i.e. do not compose without executing queries, e.g. | |
// methods take Session argument | |
// usage: | |
// db.withSession{ RecordsDAO.foos } | |
// or | |
// db.withSession{ implicit session => Records.DAO.foos } | |
def foos(implicit s:Session) = records.foos.run | |
val byIdCompiled = Compiled(records.byId) | |
def byId(id: Long)(implicit s:Session) = byIdCompiled(id).run.headOption | |
... | |
} | |
// usage: | |
db.withSession{ RecordsDAO.foos } | |
db.withSession{ implicit session => Records.DAO.foos } | |
// Alternative DAO implementation: | |
case class RecordsDAO(implicit s:Session){ | |
// centralized implicit session into class argument | |
def foos = records.foos.run | |
... | |
} | |
// usage: | |
db.withSession{ RecordsDAO().foos } | |
db.withSession{ implicit session => RecordsDAO().foos } | |
// Another alternative DAO implementation: | |
object RecordsDAO{ | |
// in user code | |
// no withSession boilerplate but no | |
// control over foos / transaction management | |
def foos = db.withSession{ records.foos.run } | |
// or: | |
def foos = db.withSession{ implicit s => records.foos.run } | |
// or: | |
def foos = db.withSession{ records.foos.list()(_) } | |
... | |
} | |
// usage: | |
RecordsDAO.foos | |
// Alternative Table class implementation using a mapped projection case class | |
// Allows projection case classes to be nested and re-used in queries and multiple tables. | |
// Uses the recently born CaseClassShape suggested here: | |
// https://github.com/slick/slick/pull/692 | |
case class RecordProjection( ... : Column[...], ... : Column[...], ... ){ | |
// place additional methods here which return values of type Column | |
// (compute artificial columns based on other columns) based on a | |
// records row | |
def foo = (col1+col2, col3) | |
... | |
} | |
implicit object RecordShape extends CaseClassShape(RecordProjection.tupled,Record.tupled) | |
class Records extends Table[Record](...){ | |
def projection = RecordProjection(column(...),column(...),...) // column types can be inferred | |
def * = projection | |
} | |
val records = TableQuery[Records].map(_.projection) | |
// usage: | |
db.withSession{ records.map(_.foo).run } |
@francisdb
The byId you showed returns a query, so RecordQueryExtensions. The compiled version is not composable anymore, so RecordDAO makes sense. Updated the code to include byId in appropriate locations.
Regarding slick-dao, I would have to see some real world use cases in order to tell.
@cvogt this is extremely helpful, thank you!
So, in a play-slick project, should each controller action be a DBAction, thereby creating an implicit Session at line 186?
So, for example, a controller action to retrieve all records might be
def getRecords = DBAction { implicit s =>
RecordsDAO.foos
// or, RecordsDAO().foos
}
@freekh does this line up with your understanding as well?
@cvogt: where can I introduce a transaction in this pattern?
Also, @cvogt, where should one put a pre-compiled query, and how to use it from the DAO?
Update: Nevermind! I see the example above val byIdCompiled = Compiled(records.byId)
The advantage of the slick-dao is that it implements the basic CRUD in a DAO so we don't have to manipulate queries or any infrastructure/DB code in your domain. Nothing more than that.
I do that because I want everything to go through a DAO, basic CRUD and queries. Otherwise the domain gets cluttered with some locally defined queries, some DAOs, extensions and some on.
Typo on lines 19: s/RecordQueryExentions/RecordQueryExtensions/
Hi Christopher, thanks for the template. I have some more questions:
is the lowercase r on the records object on purpose?
where would I put this?
def byId(id: Column[Long]) = for {
r <- Records if r.id === id
} yield r
and the compiled version?
and the version that takes a session and calls firstOption?
What do you think about this project? https://github.com/rcavalcanti/slick-dao
Anything you would do differently?
You probably want to reduce access to some of these classes/objects/methods? (private)
Other tips on removing boilerplate?