Slick (the Scala Language-Integrated Connection Kit) is a framework for type-safe, composable data access in Scala. This library adds tools to use type-safe IDs for your classes so you can no longer join on bad id field or mess up order of fields in mappings. It also provides a way to create service layer with methods (like querying all, querying by id, saving or deleting) for all classes with such IDs in just 4 lines of code.
Idea for type-safe ids was derived from Slick creator's presentation on ScalaDays 2013.
This library is used in Advanced play-slick Typesafe Activator template.
ScalaDoc API for 0.6.4-SNAPSHOT.
Unicorn is Open Source under Apache 2.0 license.
Authors:
Feel free to use it, test it and to contribute! For some helpful tips'n'tricks, see contribution guide.
For core latest version (Scala 2.10.x/2.11.x and Slick 2.1.x) use:
libraryDependencies += "org.virtuslab" %% "unicorn-core" % "0.6.3"
For play version (Scala 2.10.x/2.11.x, Slick 2.1.x, Play 2.3.x):
libraryDependencies += "org.virtuslab" %% "unicorn-play" % "0.6.3"
Or see our Maven repository.
For Slick 2.0.x see version 0.5.x
.
For Slick 1.x see version 0.4.x
.
See our migration guide.
From version 0.5.0 forward dependency on Play! framework and play-slick
library is no longer necessary.
If you are using Play! anyway, examples below shows how to make use of unicorn
then.
package model
import org.virtuslab.unicorn.LongUnicornPlay._
import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._
import scala.slick.lifted.Tag
/** Id class for type-safe joins and queries. */
case class UserId(id: Long) extends AnyVal with BaseId
/** Companion object for id class, extends IdMapping
* and brings all required implicits to scope when needed.
*/
object UserId extends IdCompanion[UserId]
/** User entity.
*
* @param id user id
* @param email user email address
* @param lastName lastName
* @param firstName firstName
*/
case class User(id: Option[UserId],
email: String,
firstName: String,
lastName: String) extends WithId[UserId]
/** Table definition for users. */
class Users(tag: Tag) extends IdTable[UserId, User](tag, "USERS") {
// use this property if you want to change name of `id` column to uppercase
// you need this on H2 for example
override val idColumnName = "ID"
def email = column[String]("EMAIL", O.NotNull)
def firstName = column[String]("FIRST_NAME", O.NotNull)
def lastName = column[String]("LAST_NAME", O.NotNull)
override def * = (id.?, email, firstName, lastName) <> (User.tupled, User.unapply)
}
package repositories
import model._
import org.virtuslab.unicorn.LongUnicornPlay._
import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._
/**
* Repository for users.
*
* It brings all base service methods with it from [[BaseIdRepository]], but you can add yours as well.
*
* Use your favourite DI method to instantiate it in your application.
*/
class UsersRepository extends BaseIdRepository[UserId, User, Users](TableQuery[Users])
package repositories
import model.User
import org.virtuslab.unicorn.BasePlayTest
class UsersRepositoryTest extends BasePlayTest {
"Users repository" should "save and query users" in rollback { implicit session =>
// setup
val repository = new UsersRepository
repository.create
val user = User(None, "test@email.com", "Krzysztof", "Nowak")
val userId = repository save user
val userOpt = repository findById userId
userOpt.map(_.email) shouldEqual Some(user.email)
userOpt.map(_.firstName) shouldEqual Some(user.firstName)
userOpt.map(_.lastName) shouldEqual Some(user.lastName)
userOpt.flatMap(_.id) shouldNot be(None)
}
}
If you do not want to include Play! but still want to use unicorn, unicorn-core
will make it available for you.
First you have to bake your own cake to provide unicorn
with proper driver (in example case H2):
package com.example
import org.virtuslab.unicorn.{HasJdbcDriver, LongUnicornCore}
import scala.slick.driver.H2Driver
object Unicorn extends LongUnicornCore with HasJdbcDriver {
val driver = H2Driver
}
Then you can use that cake to import driver and types provided by unicorn
as shown in next sections.
package com.example.tables
import package com.example.Unicorn._
import package com.example.Unicorn.driver.simple._
/** Id class for type-safe joins and queries. */
case class UserId(id: Long) extends AnyVal with BaseId
/** Companion object for id class and ordering fpr Id */
object UserId extends IdCompanion[UserId]
/** User entity.
*
* @param id user id
* @param email user email address
* @param lastName lastName
* @param firstName firstName
*/
case class UserRow(id: Option[UserId],
email: String,
firstName: String,
lastName: String) extends WithId[UserId]
/** Table definition for users. */
class Users(tag: Tag) extends IdTable[UserId, UserRow](tag, "USERS") {
// use this property if you want to change name of `id` column to uppercase
// you need this on H2 for example
override val idColumnName = "ID"
def email = column[String]("EMAIL", O.NotNull)
def firstName = column[String]("FIRST_NAME", O.NotNull)
def lastName = column[String]("LAST_NAME", O.NotNull)
override def * = (id.?, email, firstName, lastName) <> (UserRow.tupled, UserRow.unapply)
}
package com.example.repositories
import com.example.tables._
import play.api.db.slick.Config.driver.simple._
import org.virtuslab.unicorn.repositories._
/**
* Repository for users.
*
* It brings all base service methods with it from [[service.BaseIdRepository]], but you can add yours as well.
*
* Use your favourite DI method to instantiate it in your application.
*/
class UsersRepository extends BaseIdRepository[UserId, UserRow, Users](TableQuery[Users])
package com.example.test
import com.example.repositories.UserRepository
class UsersRepositoryTest extends BaseTest {
val userRepository = new UserRepository
"Users Service" should "save and query users" in rollback { implicit session =>
// setup
usersQuery.ddl.create
val user = UserRow(None, "test@email.com", "Krzysztof", "Nowak")
val userId = userRepository save user
val userOpt = userRepository findById userId
userOpt.map(_.email) shouldEqual Some(user.email)
userOpt.map(_.firstName) shouldEqual Some(user.firstName)
userOpt.map(_.lastName) shouldEqual Some(user.lastName)
userOpt.flatMap(_.id) shouldNot be(None)
}
}
All reviews examples used Long
as underlying Id
type. From version 0.6.0
there is possibility to define own.
Let's use String
as our type for id
. So we should bake unicorn with String
parametrization.
object StringPlayUnicorn extends UnicornPlay[String]
object StringUnicorn extends UnicornCore[String] with HasJdbcDriver {
override val driver = H2Driver
}
Usage is same as in Long
example. Main difference is that you should import classes from self-baked cake.
The only concern is that id
is auto-increment so we can't use arbitrary type there.
We plan to solve this problem in next versions.