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.
Related
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.
I am having some issues getting the following example to compile.
import scala.slick.driver.MySQLDriver.simple._
case class Age(value: Int)
case class User(id: Long, age: Option[Age])
object Dao {
implicit val ageColumnType: TypedType[Age] = MappedColumnType.base[Age, Int](_.value, Age(_))
class UserTable(tag: Tag) extends Table[User](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def age = column[Option[Age]]("age")
def * = (id, age) <> (User.tupled, User.unapply)
}
val users = TableQuery[UserTable]
def byId(id: Long)(implicit session: Session): Option[User] = {
users.filter(_.age === Some(Age(21))).firstOption
}
}
But the compiler is failing with the following error:
Example.scala:16:28: value === is not a member of slick.lifted.Rep[Option[Age]]
Does the right way to do this involve using OptionColumnExtensionMethods or something? It is strange that the type-classes for TypedType[Option[T]] would not kick in here, however.
Here is a list of some other resources I dug up, but none of them seem to deal with a container type around a custom column type using mappedColumnType.
Filtering when using custom column type in Slick
Slick: Option column filtering
Slick and filtering by Option columns
Figured it out and figured it was worth posting here.
The following line had too broad of a type signature.
implicit val ageColumnType: TypedType[Age]
Obviously, the implicit scope no longer contained the right evidence to infer the various column operators needed in the filter query, including the === method. Instead, I just needed a more specific type:
implicit val ageColumnType: BaseColumnType[Age]
As per the comment on the original question, the ==? solution also works once this change is made. Thanks!
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?
I got the play-slick module up and running and am also using evolution in order to create the required tables in the database during application start.
For evolution to work it is required to write a 1.sql script which contains the table definitions that I want to create. At the moment it looks like this:
# --- !Ups
CREATE TABLE Users (
id UUID NOT NULL,
email varchar(255) NOT NULL,
password varchar(255) NOT NULL,
firstname varchar(255),
lastname varchar(255),
username varchar(255),
age varchar(255),
PRIMARY KEY (id)
);
# --- !Downs
DROP TABLE Users;
So far so good but for Slick to work correctly it also need to know the definition of my table. So I have a UserDAO object which looks like this:
class UserDAO #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
private val Users = TableQuery[UsersTable]
def all(): Future[Seq[User]] = db.run(Users.result)
def insert(user: User): Future[User] = db.run(Users += user).map { _ => user }
//Table definition
private class UsersTable(tag:Tag) extends Table[User](tag,"users"){
def id = column[UUID]("id", O.PrimaryKey)
def email = column[String]("email")
def password = column[String]("password")
def firstname = column[Option[String]]("firstname")
def lastname = column[Option[String]]("lastname")
def username = column[Option[String]]("username")
def age = column[Int]("age")
def * = (id, email,password,firstname,lastname,username,age) <> ((User.apply _).tupled, User.unapply)
}
}
I basically have the same table definition in two different places now. Once in the 1.sql script and once in the UserDAO class.
I really don’t like this design at all! Having the same table definitions in two different places doesn't seem to be right.
Is there some way to generate the evolution scripts from the table definitions inside UserDAO classes? Or is there a completely different way to generate the table definitions during startup (perhaps only using slick)? I really would like to only use the slick table definition and get rid of the annoying SQL scripts.
I am using play-2.4 and play-slick-1.0
Thanks a lot.
Great question - I was in the same boat as you!
I'd have just the DAO and this code:
TableQuery[UsersTable].schema.create
which'll create the database table for you. No need for the .sql.
Correspondingly, to drop, use .drop instead of .create.
You can also combine table creation of several tables using reduceLeft. Here's how I do it:
lazy val allTables = Array(
TableQuery[AcceptanceTable].schema,
[... many more ...]
TableQuery[UserTable].schema
).reduceLeft(_ ++ _)
/** Create all tables in database */
def create = {
allTables.create
}
/** Delete all tables in database */
def drop = {
allTables.drop
}
All that will need the driver API in scope such as:
val profile = slick.driver.H2Driver
import profile.api._
(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.