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.
Related
I'm rather new to scala and trying to learn slick and started with play-slick-example where everything was understandable.
I went off and created my own entities, tables and queries.
First trick was to work around getting autoincremeted id upon insertion, but example code covers it, though idiom may be improved.
Next thing to work around was to move all common code to one place. By common code I mean basic CRUD operations that are basically copy-paste for all entities.
So I went and created base Entity
trait Entity[T <: Entity[T, ID], ID] {
val id: Option[ID]
def withId(id: ID): T
}
With that I went and created BaseRepo that should contain all common code:
abstract class BaseRepo[T <: Entity[T, ID], ID] {
protected val dbConfigProvider: DatabaseConfigProvider
val dbConfig = dbConfigProvider.get[JdbcProfile]
import dbConfig._
import profile.api._
type TableType <: Keyed[ID] with RelationalProfile#Table[T]
protected val tableQuery: TableQuery[TableType]
}
Where dbConfigProvider is injected into implementations and allows to import proper config (not sure it's needed, but example has it like that). Keyed is another trait that represents Tables with columns id:
trait Keyed[ID] {
def id: Rep[ID]
}
Everything looks good for now. To extend BaseRepo one would need to properly assign TableType and tableQuery and everything should work.
I start with following implementation:
case class Vehicle(override val id: Option[Long], name: String, plate: String, modelId: Long)
extends Entity[Vehicle, Long] {
override def withId(id: Long): Vehicle = this.copy(id = Some(id))
}
And following repo:
#Singleton
class VehicleRepository #Inject()(override val dbConfigProvider: DatabaseConfigProvider)
(implicit ec: ExecutionContext)
extends BaseRepo [Vehicle, Long]{
import dbConfig._
import profile.api._
type TableType = Vehicles
val tableQuery = TableQuery[Vehicles]
class Vehicles(tag:Tag) extends Table[Vehicle](tag:Tag, "vehicles") with Keyed[Long] {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def plate = column[String]("plate")
def modelId = column[Long]("modelId")
def * = (id.?, name,plate,modelId) <> ((Vehicle.apply _).tupled, Vehicle.unapply)
}
Everything still looks great!
Now I add all() to BaseRepo:
def all() = db.run {
tableQuery.result
}
And it works! I can list all my Vehicle entities through injected repo:VehicleRepository with simple repo.all() (Well, i get Future to be precise, but who cares)
Next, I go and try to generalize insert with autoincremented id and put it into BaseRepo:
def create(item: T, ec:ExecutionContext) = db.run{
((tableQuery returning tableQuery.map(_.id)) += item)
.map(id => item.withId(id))(ec)
}
Don't mind explicit ExecutionContext here, but anyway, this does not work and Error I get is frustrating:
Slick does not know how to map the given types.
Possible causes: T in Table[T] does not match your * projection,
you use an unsupported type in a Query (e.g. scala List),
or you forgot to import a driver api into scope.
Required level: slick.lifted.FlatShapeLevel
Source type: slick.lifted.Rep[ID]
Unpacked type: T
Packed type: G
]
If I move this method back into VehicleRepository (replacing T with Vehicle) everything works like a charm.
Some hours of digging later I understand that tableQuery.map takes some (implicit shape: Shape[_ <: FlatShapeLevel, F, T, G]) as implicit parameter and I literally have no idea where does that come from into the scope of VehicleRepository and why is it not available in my BaseRepo
Any comment or advice on how to work around this or maybe some other approaches to generalize CRUDs with Slick would be appriciated!
I'm using Play-2.8 Slick-3.3.2 play-slick-5.0.0 scala-2.13.1
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!
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.
After upgrading to Slick 3.0 and Play! 2.4, I got this pretty dependency injection feature, but I faced with serialization problems.
My Application is simple rest server.
This is the exception which I get
type mismatch;
found : play.api.libs.json.OWrites[ReportsDatabase.this.PostEntity]
required: play.api.libs.json.Writes[ApiRoot.this.noiseModel.PostEntity]
Note: implicit value PostWrites is not applicable here because it comes after the application point and it lacks an explicit result type
This is my entity
val posts = TableQuery[Posts]
case class PostEntity(id: Long, user: Long, text: String, date: LocalDate, lat: Double, lon: Double, pictureID: Long, soundId: Long)
class Posts(tag: Tag) extends Table[PostEntity](tag, "post") {
implicit val dateColumnType = MappedColumnType.base[LocalDate, String](dateFormatter.print(_), dateFormatter.parseLocalDate)
def id = column[Long]("id", O.AutoInc, O.PrimaryKey)
def userId = column[Long]("userId")
def text = column[String]("text")
def date = column[LocalDate]("date_post")
def lat = column[Double]("lat")
def lon = column[Double]("lon")
def pictureId = column[Long]("pictureID")
def soundId = column[Long]("soundId")
def * = (id, userId, text, date, lat, lon, pictureId, soundId) <>(PostEntity.tupled, PostEntity.unapply)
def user = foreignKey("post_user_FK", userId, users)(_.id)
}
Here is method to get a list of posts
def getPostList: Future[Seq[PostEntity]] = db.run(posts.result)
My controller starts like this
class ApiRoot #Inject() (noiseDao: NoiseModel, noiseModel: ReportsDatabase) extends Controller {
import noiseModel._
implicit val PostWrites = Json.writes[noiseModel.PostEntity]
def getPostStream = Action.async { implicit request =>
noiseDao.getPostList.map{posts =>
Ok(toJson(posts))
}
}
def getPost(id: Long) = Action.async { implicit request =>
noiseDao.getPost(id).map{ post =>
Ok(toJson(post))
}
}
I haven't found any information in the Internet regarding this problem. Found questions, but any answers.
My guess is to move implicit val PostWrites to companion object Posts or closer to the DI library (don't know Play that much to offer more help).
It happens because of the way DI works in general - first an instance, and then all the goodies are available that are inside the instance.
I had the exact same problem upgrading my application to Play! 2.4. If you use the slick code generator for your entities you need to create a custom generator like the one in this answer https://stackoverflow.com/a/32070115
(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.