Slick is a powerful library for functional database access, leveraging Scala language features such as implicit conversions and macro’s to allow type-safe interaction with databases.
As I got to work with Slick more extensively, one thing that started to annoy me pretty soon is that I had to repeat boilerplate code for generic CRUD methods such as insert, update, delete, etc. At the same time, it didn’t look really easy to provide a CRUD trait for models, given that Slick’s design is heavily centered around the idea of drivers encapsulating the implementation details for all generic concerns (querying, insertion, etc.) and is therefore already making use of large portions of abstractions.
Lifted embedding (currently the only method that allows insert, update and delete operations) requires to have a table definition declaring all the columns and associated methods, like so:
delete operations are arguably always the same for any kind of entity and don’t add much value when repeated over time.
After a bit of tinkering, I ended up with the following trait which does a quite good job add keeping Slick models dry:
This trait works in the following way:
- since we don’t want to have to impose anything too special on the case classes, a structural type definition is used to require model definitions (the case classes) to have an identifier being an
Option[Long]. The field is optional given that the ID should be provided by the database at insertion time and not the other way around
- there doesn’t seem to be a way to get the implicit conversion scope provided by the Slick drivers to be applied in this case, hence the implicit conversions (
tableToQueryand friends) need to be called explicitly
- the example above only works with the driver provided by the
play.api.db.slick.Config.driver.simple._import, but it is easy to adapt it to other drivers
There seems to be an interesting side-effect to using this trait, which is that SBT’s incremental compiler seems to get confused with the type of the table definition and fails to compile the table definition object after a change is made right away. Triggering a change in another class, or cleaning the project gets things to work again.