How can we overcome the 22 limit when calling procedures with Slick?
We currently have:
val q3 = sql"""call getStatements(${accountNumber})""".as[Transaction]
The problem is that we have to return more than 22 columns and Transaction case class cannot have more than 22 columns since when we do JSONFormat we get an error:
[error] E:\IdeaProjects\admin\Transaction.scala:59: No unapply or unapplySeq function found
[error] implicit val jsonFormat = Json.format[Transaction]
Any suggestions?
Alright - so if you can actually modify your Transaction case class than there is a better solution than HList (which to be honest may be a little cumbersome to operate with later on).
So here is the thing: let's imagine you have User table with following attributes:
id
name
surname
faculty
finalGrade
street
number
city
postCode
Above columns may not make sense but let's use them as example. The most straightforward way to deal with above is to create a case class:
case class User(
id: Long,
name: String,
... // rest of the attributes here
postCode: String)
which would be mapped from table on the application side.
Now what you can also do is to do this:
case class Address(street: String, number: String, city: String, postCode: String)
case class UniversityInfo(faculty: String, finalGrade: Double)
case class User(id: Long, name: String, surname: String, uniInfo: UniversityInfo, address: Address)
This composition will help you to avoid problem with too many columns (which is basically problem with too many attributes in your case class/tuple). Apart from that - I would argue that it is always (very often?) beneficial to do this if you have many columns - if for nothing else than simply for readability purposes.
How to do the mapping
class User(tag: Tag) extends Table(tag, "User") {
// cricoss info
def id = column[Long]("id")
def name = column[String]("name")
// ... all the other fields
def postCode = column[String]("postCode")
def * = (id, name, surname, uniInfoProjection, addressProjection) <>((User.apply _).tupled, User.unapply)
def uniInfoProjection = (faculty, finalGrade) <>((UniversityInfo.apply _).tupled, UniversityInfo.unapply)
def addressProjection = (street, number, city, city) <>((Address.apply _).tupled, Address.unapply)
}
The same can be done with custom SQL mapping.
implicit val getUserResult = GetResult(r =>
User(r.nextLong, r.nextString, r.nextString,
UniversityInfo(r.nextString, r.nextDouble),
Adress(r.nextString, r.nextString, r.nextString, r.nextString))
)
So to put things simply - try to segregate your fields into multiple nested case classes and your problem should go away (with added benefit of improved readability). If you do that approaching tuple/case class limit should virtually never be a problem (and you shouldn't even need to use HList).
Related
I am trying to do this (on Play Framework):
db.run(users.filter(_.id === id).map(_.deleted).update(Option(DateTime.now)))
But it's throwing a compilation 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, 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[Option[org.joda.time.DateTime]] Unpacked type: T
Packed type: G
Version of Slick 3.0.3.
How can i fix this bug?
class UserTable(tag: Tag) extends Table[User](tag, "user") {
def id = column[Int]("id")
def name = column[String]("name")
def age = column[Int]("age")
def deleted = column[Option[DateTime]]("deleted")
override def * =
(id, name, age, deleted) <> ((User.apply _).tupled, User.unapply)
}
case class User(
id: Int = 0,
name: String,
age: Int,
deleted: Option[DateTime] = None
)
When you define your table, there is a column type mapper in scope for DateTime. Something like
implicit val dtMapper = MappedColumnType.base[org.joda.time.DateTime, Long](
dt => dt.getMillis,
l => new DateTime(l, DateTimeZone.UTC)
)
for example. This mapper must also be in scope at the location where you construct your query, otherwise Slick will not know how to convert what you've written into a SQL query.
You can either import the mapper, if you would like to keep the query separate from the table, or define the query in the same file where you define UserTable. A common pattern is to wrap the table in a trait, say UserRepository, and define both the table and the queries within that trait.
See this page for more information on how implicit scope works.
Say I have 2 case classes:
case class Basic(id: String, name) extends SomeBasicTrait
case class Info (age: Int, country: String, many other fields..) extends SomeInfoTrait
and want to create a case class that has all fields from both of those case classes. This is a possible way:
case class Full(bs: Basic, meta: Info) extends SomeBasicTrait with SomeInfoTrait {
val id = bs.id
val name = bs.name
val age = meta.age
val country = meta.country
// etc
}
But it's a lot of boilerplate code. Is there any way to avoid this?
I couldn't find a way to achieve this in Shapeless but maybe there is..
[Update]
#jamborta 's comment helps and is basically this:
case class FullTwo(id: String, name: String, age:Int, country:String)
val b = Basic("myid", "byname")
val i = Info(12, "PT")
Generic[FullTwo].from(Generic[Basic].to(b) ++ Generic[Info].to(i))
The problem with this solution is that it still requires defining every field in the arguments of the FullTwo class, so that every time a change to Basic or Info is made, we also have to remember to change FullTwo as well.
Is there any way to create dynamically at compile time a case class equal to FullTwo?
We're using case classes to represent the JSON objects transferred between the client and server. It's been working great except for one sticking point we've been living with for quite a while now and I wonder if anyone has a clever way around it.
Let's say I have a user object that has id, first name, last name and email address. Once a user has been saved to the database, he has an id (Int) assigned to him, so for all communication between the client and server dealing with existing users, the id is a required field. In fact, there is only one case when the id field is not required and that's when the user is first being saved. The way we currently deal with this is with a case class that looks like this:
case class User(id: Option[Int], firstName: String, lastName: String, email:String)
In all cases except the initial save, that id is Some and for the initial save id is always None so we find ourselves using id.getOrElse(0) quite often. (Sometimes we'll do a .get but it feels dirty.)
What I would love to have is an object with an id: Int field for existing users and an object with no id field at all for new users, but without declaring all the other fields twice in two separate case classes. However, I'm not seeing a way to do that conveniently. I'm also not fond of using a 'magic' number for the id field of new users.
Does anyone have a better solution to this issue?
case class User[+IdOpt <: Option[Int]](idOpt: IdOpt, firstName: String, lastName: String, email:String)
object User {
// Type aliases for convenience and code readability
type New = User[None.type]
type Saved = User[Some[Int]]
type Value = User[Option[Int]] // New or Saved
implicit class SavedOps(val user: Saved) extends AnyVal {
def id: Int = user.idOpt.get
}
}
Tests:
scala> val billNew = User(None, "Bill", "Gate", "bill#microsoft.com")
billNew: User[None.type] = User(None,Bill,Gate,bill#microsoft.com)
scala> billNew.id
<console>:17: error: value id is not a member of User[None.type]
billNew.id
^
scala> val billSaved = billNew.copy(idOpt = Some(1))
billSaved: User[Some[Int]] = User(Some(1),Bill,Gate,bill#microsoft.com)
scala> billSaved.id
res1: Int = 1
This is what we ended up going with for now.
trait Resource[T <: Option[Int]] {
def idOpt: T
}
object Resource {
type IsSome = Some[Int]
implicit class SomeOps[R <: Resource[IsSome]](val resource: R) {
def id: Int = resource.idOpt.get
}
}
This allows us to use it like this:
case class User[T <: Option[Int]](idOpt:T, firstName:String, lastName:String, email:String) extends Resource[T]
case class Company[T <: Option[Int]](idOpt:T, companyName: String) extends Resource[T]
val u1 = User(None, "Bubba", "Blue", "bubba#shrimp.com")
val u2 = User(Some(1), "Forrest", "Gump", "forrest#shrimp.com")
u1.id // <-- won't compile
u2.id // <-- compiles
Having a magic number is not a terrible idea if you hide it from the user. in fact it is a common pattern, Slick uses it for example. You can just ignore the id value for the objects to be inserted.
So you can start by making the the constructor package private
case class User private[db](id: Int, firstName: String, lastName: String, email:String)
And then provide a companion object for users to create it without id
object User{
def apply(firstName: String, lastName: String, email: String): User = User(-1, firstName, lastName, email)
}
And now you can construct it as if id wasn't required
val user = User("first","last","email")
there is Person model case class which is exposed to the client via REST endpoint.
scala> case class Person(firstname: String, lastname: String)
defined class Person
Now one of the internal service wants to "enrich" this Person case class and add social security number to it. BUT that enrichment is only to populate social security number from one service and pass it along to another one.
Immediate thought is to add ssn property to Person as follow - but that's not desired given Person is exposed to endpoint consumer and SSN MUST NOT be visible to any user calling REST endpoint which exposes Person data.
case class Person(firstname: String, lastname: String, ssn: String)
So another thought is to enrich Person case class as follow
scala> implicit class ExtendedPerson(person: Person){
| val SSN : String = ""
| }
defined class ExtendedPerson
then Service1.findPerson API will have implicit in the scope and will want to prepare Person as follow
val person = Person(firstName="Joe",lastname="Doe", ssn ="123-456-0000")
and then
Service2.moreWorkWithPerson(person,other arguments)
then Service2.moreWorkWithPerson will want to get ssn=123-456-0000 from person argument.
what's the right way to achieve this in Scala without modifying definition of Person case class? Can shapeless help? any pointers would be appreciated?
SSN as val in ExtendedPerson makes not much sense, instead it should be a function which queries your DB using the persons first and lastname. Given the fact that your DB is slow and you might call Person.SSN more than once I would suggest not to use this pattern as it will query your DB everytime you call .SSN. Instead I would write an implicit converter for Person to convert more explicitly.
case class ExtendedPerson(basicInfo: Person,ssn: String)
implicit class PersonHelper(person: Person) extends AnyVal{
def extended = {
val ssn = DB.querySSN(...)
ExtendedPerson(person,ssn)
}
}
val person: ExtendedPerson = Person("John","Doe").extended
//person = ExtendedPerson(Person("John","Doe"),"123-456-0000"")
Note: The code is written out of mind but should be logically correct
It seems that you are overthinking this. What's wrong with this?
case class Person(firstname: String, lastname: String, ssn: Option[String] = None);
Now, in REST handler you do return Person("John", "Doe"), and in the other service: return Person("John", "Doe", Some("111-11-1111"))
Maybe you should just expose a different class to the client.
case class Person(firstname: String, lastname: String, ssn: String) {
def toClient = ClientPerson(firstname, lastname)
}
case class ClientPerson(firstname: String, lastname: String)
object RESTService {
def getPerson(id: String): ClientPerson = {
val p: Person = DB.getPerson(id)
p.toClient
}
}
Suppose I have a case class like
case class Person(fname:String, lname:String, nickName:Option[String] = None)
On creating an instance like Person("John", "Doe"), I want nickName to be automatically assigned to fname, if one is not given. Eg:
val p1 = Person("John", "Doe")
p1.nickName.get == "John"
val p2 = Person("Jane", "Doe", "Joe")
p2.nickName.get == "Joe"
How can auto assignment of one field from another field be achieved?
Trying the solutions below in repl. I think this is something to do with repl
scala> case class Person(fname: String, lname:String, nickName:Option[String])
defined class Person
scala> object Person { def apply(fname:String, lname:String):Person = {Person(fname, lname, Some(fname))}}
console:9: error: too many arguments for method apply: (fname: String, lname: String)Person in object Person
object Person { def apply(fname:String, lname:String):Person = {Person(fname, lname, Some(fname))}}
You can overload the constructor of the case class
case class Foo(bar: Int, baz: Int) {
def this(bar: Int) = this(bar, 0)
}
new Foo(1, 2)
new Foo(1)
So you can check the case if nickName is none.
You can also overload it's apply method in the same way. In that way, then you can use
Foo(1,2)
Foo(1)
Technical Solution (don't)
On the current definition of case classes, you can override the constructor of the case class and the apply method of its companion object, as described in the answer of Facundo Fabre.
You will get something like this:
object Person {
def apply(fname:String, lname:String): Person = Person(fname, lname, fname)
}
case class Person(fname:String, lname:String, nickName: String) {
def this(fname:String, lname:String) = this(fname, lname, fname)
}
This is technical correct and quite clever coding. But for my taste its a little bit too clever, because it breaks an important property:
CaseClass.unapply(CaseClass.apply(x1,x2,x3)) == (x1,x2,x3)
In other words: When I construct a case class using apply with some tuple of parameter and then deconstruct it using unapply I expect to get the original tuple I put into apply (ignoring currying and option type).
But in this case, this property is not true any more:
Person.unapply(Person("John", "Smith"))
// result: Option[(String, String, String)] = Some((John,Smith,John))
Deconstruction using unapply is used for pattern matching (match{ case ... => ...). And this is a common use case for case classes.
So while the code is quite clever, it might confuse other people and break properties their code relies on.
Rephrase the problem (my suggestion)
When overly clever code is needed, it is often a good idea to rethink, what problem one tries to solve. In this case, I would suggest to distinguish between the nick name the user has chosen and a nick the system assigns to the user. In this case I would then just build a case class like this:
case class NickedPerson(fname : String, lname : String, chosenNick : Option[String] = None) {
val nick = chosenNick.getOrElse(fname)
}
You can then just use the field nick to access the computed nick name, or use chosenNick if you want to know if the user has provided that nick name:
NickedPerson("John", "Smith").nick
// result: String = "John"
NickedPerson("John", "Smith", Some("Agent")).nick
// result: String = "Agent"
The basic properties about case classes are not changed by this code.
Just explaining how to overload apply from companion object (in addition to #Facundo Fabre answer):
scala> :paste
// Entering paste mode (ctrl-D to finish)
object Person {
def apply(fname:String, lname:String): Person = Person(fname, lname, fname)
}
case class Person(fname:String, lname:String, nickName: String)
// Exiting paste mode, now interpreting.
defined object Person
defined class Person
scala> Person("aaa", "bbb")
res24: Person = Person(aaa,bbb,aaa)
scala> Person("aaa", "bbb", "ccc")
res25: Person = Person(aaa,bbb,ccc)
You could also define default value using muti-parameter list constructor, but it's hard to use such case class (no toString and pattern matching for last parameter), so won't recommend (but it's good if you want simmilar thing for methods):
scala> case class Person(fname:String, lname:String)(val nickName: String = fname)
defined class Person
Another funny solution (just to play), which I wouldn't recommend to use in real code:
scala> case class Person(fname:String, lname:String, var nickName: String = null){nickName = Option(nickName).getOrElse(fname)}
defined class Person
scala> Person("aaa", "bbb")
res32: Person = Person(aaa,bbb,aaa)