I'm stuck trying to define mapped tables using traits in slick 3.1.0. Since there is nothing mentioned in the official docs, I'm not even sure whether this is possible or not. Here is what I have so far:
Table definition:
class PersonTable(tag: Tag) extends Table[PersonModel](tag, "person") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def firstName = column[String]("first_name", O.Length(PersonDb.FirstNameColumnLength))
def lastName = column[String]("last_name", O.Length(PersonDb.LastNameColumnLength))
def * = (id.?, firstName, lastName) <> (PersonModelImpl.tupled, PersonModelImpl.unapply _)
}
PersonModel:
trait PersonModel {
def id: Option[Int]
def firstName: String
def lastName: String
}
PersonModelImpl:
case class PersonModelImpl(
override val id: Option[Int],
override val firstName: String,
override val lastName: String)
extends PersonModel
Compiling the code above causes an error:
Compilation error[type mismatch;
found : slick.lifted.MappedProjection[models.PersonModelImpl,(Option[Int], String, String]
required: slick.lifted.ProvenShape[models.PersonModel]]
However changing ...extends Table[PersonModel]... to ...extends Table[PersonModelImpl]... in the table definition works flawlessly.
So basically my question is:
Is it possible to use traits as TableElementType in mapped tables?
If yes, what am I doing wrong?
Answers:
Yes, but it requires having the correct projection function (*).
You have the wrong type on *. In order for the implicit to resolve to the right MappedProjection, the types have to match exactly.
You should be able to resolve both by doing:
def * = {
val applyAsPersonModel: (Option[Int], String, String) => PersonModel =
(PersonModelImpl.apply _)
val unapplyAsPersonModel: PersonModel => Option[(Option[Int], String, String)] =
{
// TODO: Handle any other subclasses of PersonModel you expect.
case personImpl: PersonModelImpl => PersonModelImpl.unapply(personImpl)
}
(id.?, firstName, lastName) <> (applyAsPersonModel.tupled, unapplyAsPersonModel)
}
Note the TODO. You'll get an exception if you try to insert any non-PersonModelImpl instances unless you add additional case statements to that partial function. Alternatively, you can just create a new PersonModelImpl instance to pass to unapply:
val unapplyAsPersonModel: PersonModel => Option[(Option[Int], String, String)] =
{ person: PersonModel =>
PersonModelImpl.unapply(
PersonModelImpl(person.id, person.firstName, person.lastName)
)
}
Related
While domain modeling how can I ensure that two types — User and Place don't interchange their name fields, which are of type String.
trait User {
val firstName: String
val lastName: String
}
object User {
final class Live(val firstName: String,val lastName: String) extends User
def apply(firstName: String, lastName: String): User = new Live(firstName, lastName)
}
trait Place {
val name: String
}
object Place {
final class Live(val name: String) extends Place
def apply(name: String): Place = new Live(name)
}
val a = User("Tushar", "Mathur")
val b = Place("Mumbai")
val c = Place(a.firstName)
// How do I disable this ^
This is supported in Scala as in the example below. There are some libraries that can reduce boilerplate but I'm just showing the simplest option:
// define more restrictive String-like types. AnyVal construction can be free from
// overhead with some caveats.
case class UserName(name: String) extends AnyVal
case class PlaceName(name: String) extends AnyVal
// define your classes (I've changed them a bit for brevity):
case class User(name: UserName)
case class Place(name: PlaceName)
// implicits for convenience of construction:
implicit val strToUserName = UserName(_)
implicit val strToPlaceName = PlaceName(_)
// test it out:
scala> val u = User("user")
u: User = User(UserName(user))
scala> val p = Place("place")
p: Place = Place(PlaceName(place))
// as expected you CAN'T do this:
scala> User(p.name)
<console>:17: error: type mismatch;
found : PlaceName
required: UserName
User(p.name)
^
// comparison test:
scala> p.name == u.name
<console>:16: warning: comparing case class values of types PlaceName and UserName using `==' will always yield false
p.name == u.name
^
res3: Boolean = false
// you can still get at the string value:
scala> p.name
res6: PlaceName = PlaceName(place)
scala> p.name.name
res5: String = place
trying work with Sangria and Slick. New to both of them.
I have bunch of tables which share a list of common fields. Slick's representation of this is below:
case class CommonFields(created_by: Int = 0, is_deleted: Boolean = false)
trait CommonModel {
def commonFields: CommonFields
def created_by = commonFields.created_by
def is_deleted = commonFields.is_deleted
}
case class User(id: Int,
name: String,
commonFields: CommonFields = CommonFields()) extends CommonModel
Slick tables:
abstract class CommonTable [Model <: CommonModel] (tag: Tag, tableName: String) extends Table[Model](tag, tableName) {
def created_by = column[Int]("created_by")
def is_deleted = column[Boolean]("is_deleted")
}
case class CommonColumns(created_by: Rep[Int], is_deleted: Rep[Boolean])
implicit object CommonShape extends CaseClassShape(
CommonColumns.tupled, CommonFields.tupled
)
class UsersTable(tag: Tag) extends CommonTable[User](tag, "USERS") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def * = (id,
name,
CommonColumns(created_by, is_deleted)) <> (User.tupled, User.unapply)
}
val Users = TableQuery[UsersTable]
The problem is with Graphql:
lazy val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User]()
When I try to create UserType using derivedObjectType Macro, it complains that
Can't find suitable GraphQL output type for .CommonFields. If you have defined it already, please consider making it implicit and ensure that it's available in the scope.
[error] lazy val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User](
How do I tell Sangria/Graphql how to handle this nested list of fields (from CommonFields) ?
Please help.
You are deriving the Type for User but user also has CommonFields which you have not derived. So it's not able to find Type information for CommonFields. Derive both and make the derivation implicit for CommonFields.
implicit val CommonFieldsType: ObjectType[Unit, CommonFields] = deriveObjectType[Unit, CommonFields]
implicit val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User]
I have a trouble in using slick MappedColumnType, the code snippet is as following:
private trait UserTable {
self: HasDatabaseConfigProvider[JdbcProfile] =>
import driver.api._
lazy val userTable = TableQuery[User]
class UserTable(tag: Tag)
extends Table[User](tag, "user") {
implicit def mapper = MappedColumnType.base[JsObject, String](
{ scope: JsObject => scope.toString }, { s: String => Json.parse(s).as[JsObject] }
)
val id = column[Long]("id", O.PrimaryKey, O.AutoInc)
val name = column[String]("name")
val hobby = column[JsObject]("hobby")
def * = (id, name, hobby) <> (User.tupled, User.unapply)
}
}
My User case class is defined as follow:
case class User(id: Long, name: String: hobby: JsObject)
I have corresponding insert and update statement for my database. However the following update statement is not working for me.
def updateQuery(id: Long, newUser: User) = {
userTable.filter(x => x.id === id)
.map(x => (x.hobby))
.update(newUser.hobby)
It will throw a compile error:
No matching Shape found.
Slick does not know how to map the given types.
Possible causes: T in Table[T] does not match your * projection. Or you use an unsupported type in a Query (e.g. scala List).
I think it's pretty straight-forward. Is there something I did wrong?
final class ContactInfo extends StaticAnnotation{}
case class Person(val id: String,
val firstName: String,
val lastName: String,
#ContactInfo val phoneNumbers: Seq[String],
#ContactInfo val email: String)
def getContactInfoFields[T: TypeTag]: Seq[String] = {
???
}
Expected output getContactInfoFields[Person] = ("phoneNumbers", "email")
Riffing off the answer to a similar question on SO I've tried
def getContactInfoFields[T: TypeTag]: Seq[String] = {
val fields = typeOf[T].members.collect{ case s: TermSymbol => s }.
filter(s => s.isVal || s.isVar)
fields.filter(_.annotations.exists(_.isInstanceOf[ContactInfo]))
.map(x=>x.name.toString).toSeq
}
However in practice this is returning an empty sequence. What am I missing?
You could represent this information at the type level.
sealed trait ContactInfo
case class PhoneNumbers(numbers: Seq[String]) extends ContactInfo
case class Email(email: String) extends ContactInfo
case class Person(id: String, firstName: String, lastName: String, phoneNumbers: PhoneNumbers, email: Email)
def contactInfo[T: TypeTag] = typeOf[T].members.filter(!_.isMethod).map(_.typeSignature).collect {
case t if t <:< typeOf[ContactInfo] => t.typeSymbol.name.toString
}
Calling contactInfo[Person] returns Iterable[String] = List(Email, PhoneNumbers)
Thank you everyone for all your help! I've managed to come up with a working solution. As it turns out I was trying to compare JavaUniverse.reflection.Types to Scala Universe reflections types which is why the filter statement was failing. The below function returns as expected
def listContactInfoFields(symbol: TypeSymbol): Seq[String] = {
val terms = symbol.asClass.primaryConstructor.typeSignature.paramLists.head
val annotatedFields = terms.filter(_.annotations.exists(_.tree.tpe =:= typeOf[ContactInfo]))
annotatedFields.map(_.name.toString)
}
I need to use Slick 3.1.1 for a Postgres based project, but I have a hard time to write clean code for the following super simple usage:
Assume I have a Task model:
case class Task(id: Option[UUID], foo: Int, bar: String)
The id: UUID is the primary key, so I should NOT provide it (id = None) when doing database INSERT. However, I do need it when doing GET which maps a database row to a Task object.
Therefore, the Slick Table class becomes very ugly:
class Tasks(tag: Tag) extends Table[Task](tag, "tasks") {
def id = column[UUID]("id", O.SqlType("UUID"), O.PrimaryKey)
def foo = column[Int]("foo")
def bar = column[String]("bar")
def insert: MappedProjection[Task, (Int, String)] =
(foo, bar).shaped.<>(
{ tuple =>
Task.tupled(None, tuple._1, tuple._2)
}, { (task: Task) =>
Task.unapply(task).map { tuple =>
(tuple._2, tuple._3)
}
}
)
override def * : ProvenShape[Task] =
(id.?,
foo,
bar).shaped.<>(Task.tupled, Task.unapply)
}
If case class Task has 10 elements, I then have to write (tuple._1, tuple._2, tuple._3, ......) My co-workers will slap my face if I submit a PR like above. Please suggest!
If you'll let the database to autoincrement your IDs, that Table definition could be shortened significantly:
import slick.driver.PostgresDriver.api._
import java.util.UUID
case class Task(id: Option[UUID], foo: Int, bar: String)
class Tasks(tag: Tag) extends Table[Task](tag, "tasks") {
def id = column[Option[UUID]]("id", O.SqlType("UUID"), O.PrimaryKey, O.AutoInc)
def foo = column[Int]("foo")
def bar = column[String]("bar")
def * = (id, foo, bar) <> (Task.tupled, Task.unapply)
}
This could be improved further by moving the id field to the end of the case class and giving it the default None value. This way you won't have to provide it every time you want to instantiate the Task:
case class Task(foo: Int, bar: String, id: Option[UUID] = None)
val firstTask = Task(123, "John")
val secondTask = Task(456, "Paul")