Common Model Fields With Slick 3 (Part I)
Since Slick isn't an ORM it doesn't provide some common features that we expect to have out of the box. One of those features is the ability to have common fields for all our models. For the sake of demonstration let's assume we want to all our models to have three common fields: id
, createdAt
and active
. The first two are self-explanatory and the active
field can be used to avoid issuing DELETE
s.
We want to be able to easily share these three common fields while minimizing the amount of boilerplate and/or code duplication. The ideal solution should make it easy to share common database functionality: paging, sorting and filtering as a few examples; and minimize the use of exotic Slick facilities.
case class CommonFields(
id: String = UUID.randomUUID.toString,
createdAt: Timestamp = new Timestamp(System.currentTimeMillis),
active: Boolean = true
)
case class CommonColumns(
id: Rep[String],
createdAt: Rep[Timestamp],
active: Rep[Boolean]
)
implicit object CommonShape extends CaseClassShape(
CommonColumns.tupled, CommonFields.tupled
)
CommonFields
defines the common fields we want on every model and CommonColumns
has those fields "lifted" into columns. Finally we have a CaseClassShape
that provides a way pack and unpack the fields.
Let's give this a spin and define a simple User
model that has these common fields. We'll also create a small trait to get access to the common fields as if they were defined on the model directly.
trait CommonModel {
def commonFields: CommonFields
def id = commonFields.id
def createdAt = commonFields.createdAt
def active = commonFields.active
}
case class User(
firstName: String,
lastName: String,
commonFields: CommonFields = CommonFields()
) extends CommonModel
Now that we've got a DRY-er approach to model creation we want to apply the same principles to the Table
and TableQuery
instances we'll need for our User
model.
abstract class CommonTable[Model <: CommonModel](
tag: Tag,
tableName: String
) extends Table[Model](tag, tableName) {
def id = column[String]("id", O.PrimaryKey)
def createdAt = column[Timestamp]("createdAt")
def active = column[Boolean]("active")
}
class Users(tag: Tag) extends CommonTable[User](tag, "users") {
def firstName = column[String]("firstName")
def lastName = column[String]("lastName")
def * = (
firstName, lastName, CommonColumns(id, createdAt, active)
) <> (User.tupled, User.unapply)
}
In defining the table we avoid having to rewrite all the common column definitions and only need to reference them when creating the default table projection. Although we have a nested case class we get to interact and treat the common fields as though they were all fields defined directly on our model. As one example look at the SQL generated for creating the table by Slick:
scala> TableQuery[Users].schema.create.statements.foreach(println)
create table "users" (
"firstName" VARCHAR NOT NULL,
"lastName" VARCHAR NOT NULL,
"id" VARCHAR NOT NULL PRIMARY KEY,
"createdAt" TIMESTAMP NOT NULL,
"active" BOOLEAN NOT NULL
)
All of our columns were added to the schema as we would expect and they would if we had defined them directly on the model/table. In a future post I'm going to go into more detail about how you can share common querying by building on the constructs described in this post.
Built With: Slick 3.1.1, PostgreSQL JDBC Driver 4 9.4.1207, Scala 2.10.5