Slick schema/design guidelines - scala

Suppose I have such schema (simplified), using slick 2.1:
// Banks
case class Bank(id: Int, name: String)
class Banks(tag: Tag) extends Table[Bank](tag, "banks") {
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def * = (id, name) <> (Bank.tupled, Bank.unapply)
}
lazy val banks = TableQuery[Banks]
Each bank has, say, 1:1 BankInfo, which I keep in separate table:
// Bank Info
case class BankInfo(bank_id: Int, ...)
class BankInfos(tag: Tag) extends Table[BankInfo](tag, "bank_infos") {
def bankId = column[Int]("bank_id", O.PrimaryKey)
...
}
lazy val bankInfos = TableQuery[BankInfos]
And each bank has associated 1:M BankItems:
// Bank Item
case class BankItem(id: Int, bank_id: Int, ...)
class BankItems(tag: Tag) extends Table[BankItem](tag, "bank_items") {
def id = column[Int]("id", O.PrimaryKey)
def bankId = column[Int]("bank_id")
...
}
lazy val bankItems = TableQuery[BankItems]
So, if I used ORM, I would have had convenient accessors for associated data, something like bank.info or bank.items. I've read "Migrating from ORMs", and I understand that Slick doesn't support full relation mapping, though I've seen example where foreignKey was used.
Basically, where should I place my code to access related data (I want to access all BankItems for some Bank, and its BankInfo). Should it be implemented in case classes, or in Table classes, or elsewhere? Can someone give me a practical advice on what is a "standard practice" in this case?

Related

Missing arguments for method unapply in slick

I am developing an application using Scala and Slick. I have a table named CarAdvertisement which has a model
case class CarAdvertisementModel(id: Int, title: String, fuel: String, price: Int, isNew: Boolean, mileage: Option[Int], firstRegistration : Option[LocalDate])
I am trying to declare my schema using slick. My code is as follows
private class CarAdvertisement(tag: Tag) extends Table[CarAdvertisementModel](tag, "CAR_ADVERTISEMENT") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def title = column[String]("title")
def fuel = column[String]("fuel")
def price = column[Int]("price")
def isNew = column[Boolean]("isNew")
def mileage = column[Option[Int]]("mileage")
def firstRegistration = column[Option[LocalDate]]("firstRegistration")
def * = (id, title, fuel, price, isNew, mileage,firstRegistration) <> ((CarAdvertisementModel.apply _).tupled, CarAdvertisementModel.unapply)
}
However, the last line
CarAdvertisementModel.unapply)
gives me an error as
Missing arguments for method unapply(CarAdvertisementModel)
Can you please let me know as to what I am missing here?
Are you sure that you don't have also a error like
could not find implicit value for parameter tt:
slick.ast.TypedType[Option[java.time.LocalDate]]
def firstRegistration = columnOption[LocalDate]
If you have one, try adding something like this to your code
private class CarAdvertisement(tag: Tag) extends Table[CarAdvertisementModel](tag, "CAR_ADVERTISEMENT") {
// fast hack to support LocalDate
implicit val localDateColumnType = MappedColumnType.base[LocalDate, java.sql.Date](java.sql.Date.valueOf, _.toLocalDate)
// the rest of the code
Or you might try to use the latest code of the Slick with merged PR #1349 Support java time. Unfortunately AFAIK there is still no official release with those changes.

Custom column (Object) type in Slick

it seems that I can't find anywhere how to properly use custom column types in Slick and I've been struggling for a while. Slick documentation
suggests MappedColumnType but I found it useable only for simple use-cases like primitive type wrappers (or it's probably just me not knowing how to use it properly).
Let's say that I have Jobs table in my DB described by JobsTableDef class. In that table, I have columns companyId and responsibleUserId which are Foreign keys for Company and User objects in their respective tables (CompaniesTableDef, UsersTableDef).
class JobsTableDef(tag: Tag) extends Table[Job] (tag, "jobs") {
def id = column[Long]("id", O.AutoInc, O.PrimaryKey)
def title = column[String]("title")
def companyId = column[Long]("companyId")
def responsibleUserId = column[Long]("responsibleUserId")
def companyFK = foreignKey("COMPANY_ID_FK", companyId, companies)(i => i.id)
def responsibleUserFK = foreignKey("RESPONSIBLE_USER_FK", responsibleUserId, users)(i => i.id)
val companies = TableQuery[CompaniesTableDef]
val users = TableQuery[UsersTableDef]
override def * = (id, title, companyId, responsibleUserId) <> (Job.tupled, Job.unapply)
}
class CompaniesTableDef(tag: Tag) extends Table[Company] (tag, "companies") {
def id = column[Long]("id", O.AutoInc, O.PrimaryKey)
def name = column[String]("name")
def about = column[String]("about")
override def * = (id, name, about) <> (Company.tupled, Company.unapply)
}
class UsersTableDef(tag: Tag) extends Table[User] (tag, "users"){
def id = column[Long]("id", O.AutoInc, O.PrimaryKey)
def username = column[String]("username", O.Unique)
override def * = (id, username) <> (User.tupled, User.unapply)
}
What I would like to achieve is to automatically 'deserialize' Company and User represented by their IDs in Jobs table. For example:
class JobsTableDef(tag: Tag) extends Table[Job] (tag, "jobs") {
def id = column[Long]("id", O.AutoInc, O.PrimaryKey)
def title = column[String]("title")
def company = column[Company]("companyId")
def responsibleUser = column[User]("responsibleUserId")
def companyFK = foreignKey("COMPANY_ID_FK", companyId, companies)(i => i.id.?)
def responsibleUserFK = foreignKey("RESPONSIBLE_USER_FK", responsibleUserId, users)(i => i.id.?)
val companies = TableQuery[CompaniesTableDef]
val users = TableQuery[UsersTableDef]
override def * = (id, title, company, responsibleUser) <> (Job.tupled, Job.unapply)
}
given that my Job class is defined like this:
case class Job(
id: Long,
title: String,
company: Company,
responsibleUser: User,
)
Currently, I'm doing it in old-fashioned way of getting Job from the DB, reading companyId and responsibleUserId, then querying the DB again and manually constructing another Job object (of course, I could also join tables, get the data as tuple and then construct Job object). I seriously doubt that this is the way go. Is there a smarter and more elegant way to instruct Slick to automagically fetch linked objects from another tables?
EDIT: I'm using Play 2.6.12 with Slick 3.2.2
After a couple of days of deeper investigation, I've concluded that it's currently impossible in Slick. What I was looking for could be described as auto joining tables described through custom column types. Slick indeed supports custom column types (embodied through MappedColumnType, as described in docs) but it works only for relatively simple types which aren't composed of another objects deserialized from DB (at least automatically, you could always try to fetch another object from the DB and then Await.result() the resulting Future object, but I guess that's not a good practice).
So, to answer myself, 'auto joining' in Slick isn't possible, falling back to manual joins with manual object construction.

How to organize Slick code into separately testable units with a configurable database?

I have a codebase of several applications with a shared database that use Slick for database access. Parts of the code like common table mappings are in a common library. I want to be able to run two kinds of tests on those projects:
tests that use the real database
other tests that use an in-memory H2 database
I have tried several ways to organize my code to support this, but have always hit a wall at some point. My latest attempt is using the DatabaseConfig approach and passes around a database definition (including database, profile and table definitions) to the objects that do database operations (let's call them Services). This way, I can - in theory - easily test a service by passing in the database definition I want to test it with. In practice, I get problems in the interaction between services because types don't fit properly.
I have created a simplified example that shows the kind of problems I get:
import slick.basic.DatabaseConfig
import slick.jdbc.JdbcProfile
import slick.lifted.Rep
import scala.concurrent.Future
trait HasProfile {
val profile: JdbcProfile
}
trait HasId {
val id: Rep[Long]
}
trait Table_Person {
this: HasProfile =>
case class PersonRow(personId: Long, lastName: String)
import profile.api._
class Person(tableTag: Tag) extends Table[PersonRow](tableTag, "person") with HasId {
val personId = column[Long]("person_id", O.PrimaryKey, O.AutoInc)
val lastName = column[String]("last_name")
override val id = personId
val * = (personId, lastName).mapTo[PersonRow]
}
lazy val Person = TableQuery[Person]
}
class DatabaseWrapper {
private val databaseConfig: DatabaseConfig[JdbcProfile] = DatabaseConfig.forConfig("PersonDatabase")
def db = databaseConfig.db
object Schema extends HasProfile with Table_Person {
override val profile = databaseConfig.profile
}
}
class PersonService(val databaseWrapper: DatabaseWrapper) {
import databaseWrapper.Schema._
val loadService = new LoadService(databaseWrapper)
def load(id: Long): Future[Option[PersonRow]] = loadService.load[PersonRow](Person, id)
}
class LoadService(val databaseWrapper: DatabaseWrapper) {
import databaseWrapper.Schema.profile.api._
def load[R](tableQuery: TableQuery[_ <: Table[R] with HasId], id: Long): Future[Option[R]] = databaseWrapper.db.run(tableQuery.filter(_.id === id).result.headOption)
}
This code gives me the following compile error:
Error:(51, 79) type mismatch;
found : slick.lifted.TableQuery[PersonService.this.databaseWrapper.Schema.Person]
required: PersonService.this.loadService.databaseWrapper.Schema.profile.api.TableQuery[_ <: PersonService.this.loadService.databaseWrapper.Schema.profile.api.Table[PersonService.this.databaseWrapper.Schema.PersonRow] with HasId]
(which expands to) slick.lifted.TableQuery[_ <: PersonService.this.loadService.databaseWrapper.Schema.profile.Table[PersonService.this.databaseWrapper.Schema.PersonRow] with HasId]
def load(id: Long): Future[Option[PersonRow]] = loadService.load[PersonRow](Person, id)
It seems the type checker does not realize that PersonService.this.loadService.databaseWrapper is the same as PersonService.this.databaseWrapper.
Is there a way around this? Does this approach even make sense, or are there any better approaches to structure the code?

Defining projection to map to nested case classes

I have these case classes:
case class PolicyHolder(id : String, firstName : String, lastName : String)
case class Policy(address : Future[Address], policyHolder : Future[PolicyHolder], created : RichDateTime, duration : RichDuration )
I then have a slick schema defined for Policy
class PolicyDAO(tag: Tag) extends Table[Policy](tag, "POLICIES") with DbConfig {
def address = column[String]("ADDRESS", O.PrimaryKey)
def policyHolder = foreignKey("POLICY_HOLDER_FK", address, TableQuery[PolicyHolderDAO])(_.id)
def created = column[RichDateTime]("CREATED")
def duration = column[String]("DURATION")
def * = (address, policyHolder, created, duration) <> (Policy.apply, Policy.unapply)
}
What is the best way for me to define this projection correctly to map the policyHolder field inside of my Policy case class from the foreign key value to an actual instance of the PolicyHolder case class.
Our solution to this problem is to place the foreign key id in the case class, and to then use a lazy val or a def (the latter possibly being backed by a cache) to retrieve the record using the key. This is assuming that your PolicyHolders are stored in a separate table - if they're denormalized but you want to treat them as separate case classes then you can have the lazy val / def in Policy construct a new case class instead of retrieving the record using the foreign key.
class PolicyDAO(tag: Tag) extends Table[Policy](tag, "POLICIES") with DbConfig {
def address = column[String]("ADDRESS", O.PrimaryKey)
def policyHolderId = column[String]("POLICY_HOLDER_ID")
def created = column[RichDateTime]("CREATED")
def duration = column[String]("DURATION")
def * = (address, policyHolderId, created, duration) <> (Policy.apply, Policy.unapply)
}
case class Policy(address : Future[Address], policyHolderId : Future[String], created : RichDateTime, duration : RichDuration ) {
lazy val policyHolder = policyHolderId.map(id => PolicyHolderDAO.get(id))
}
We also used a common set of create/update/delete methods to account for the nesting, so that when a Policy is committed its inner PolicyHolder will also be committed; we used a CommonDAO class that extended Table and had the prototypes for the create/update/delete methods, and then all DAOs extended CommonDAO instead of Table and overrode create/update/delete as necessary.
Edit: To cut down on errors and reduce the amount of boilerplate we had to write, we used Slick's code generation tool - this way the CRUD operations could be automatically generated from the schema

Where can I define methods to be called on Tables?

(I'm a complete beginner with Scala and Slick, so code review of any kind is appreciated)
I have the following class and Slick Table defined:
case class Foo(title: String, description: String, id: Int = 0)
class FooTable(tag: Tag) extends Table[Foo](tag, "FOO") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def title = column[String]("TITLE", O.NotNull)
def description = column[String]("DESCRIPTION")
def * = (title, description, id) <> (Foo.tupled, Foo.unapply)
}
I want to add a method which will return a List of Foos which match a specified title. Something like this:
def findByTitle(title: String) = DB.withSession { implicit s: Session =>
<FooTable TableQuery>.filter(_.title === title)
}
I'd then be able to use the method like this:
val foos = TableQuery[FooTable]
DB.withSession { implicit s: Session =>
val anId = foos.findByTitle("bar")
}
How/where can I add a method which can act on a TableQuery for a particular Table? Is this even the correct way to be arranging my application?
implicit class FooTableExtensions(q: Query[FooTable,Foo]){
def findByTitle(t: String) = q.filter(_.title === t)
}
foos.findByTitle("Bar")
See Scala eXchange 2013 talk our website.
For pre-compiled queries it may be useful to have a DAO though, where you can cache the pre-compiled query. See Scala Days 2013 talk. Syntax changed since then though. Check the manual for Compiled.
I think what you want is to introduce a DAO (data access object), depending on your needs you could let the companion object of the FooTable class be the DAO which would let you call FooTable.findByTitle() from the rest of your codebase.