Case class inheritance is prohibited, but how to model dependency from library? - scala

I have three distinct Scala projects, one of them consists of only models which are used in both of the other Scala projects (hereinafter referred to as library). One of the three projects is a Play web application and I want to persist the models from the library in MongoDB (code resides in Play app).
So in my library I have the following model:
case class MyUser(id: UUID = UUID.randomUUID(), timestamp: Instant = Instant.now, name: String)
To persist instances of MyUser in my Play app, I would usually go with this model:
case class MyUser(var _id: Option[BSONObjectID] = None,
id: UUID,
timestamp: Instant,
name: String,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends Temporal
Now I am searching for a way to morph MyUser of my library to the corresponding class in my Play app, so I thought I would do something like this:
case class MyUser(var _id: Option[BSONObjectID] = None,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends lib.MyUser with Temporal
However, now I get the following error:
case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes.
I thought about doing something like this:
case class MyUser(var _id: Option[BSONObjectID] = None,
user: lib.MyUser,
var created: Option[DateTime] = None,
var updated: Option[DateTime] = None
) extends Temporal
How can I design this the right way?

First, what the error tells you is that you shouldn't have a case class for a class which is extended. The thing it says about extractors is basically that, should the need arise to pattern match on a parent class (which is what the case of case class comes from, easy pattern matching), you can always write custom extractors for it.
However, this does not apply in your case, since you cannot change the fact that your lib exposes case classes.
The other solution, is to consider that there is no inheritance between your two classes. Indeed, one is the representation of a user, while the other is the representation of a storage unit containing a user. So it looks like your proposed solution has some meaning after all.
Still, it might seem cumbersome to have to add a field, both in the serialized object (in Mongo) and in the JVM class, where you will need to always do mongoUser.user to get a lib.MyUser from a MyUser...
One elegant (IMHO) solution is to keep this field user: lib.MyUser, but to have your serialization flatten this field to the top level of the BSON object, so that a serialized MyUser will look like
{
_id: ObjectId(_),
id: 123456789abc-1234-1234-1234,
...
}
as if you had inherited the fields of lib.MyUser.
deserialization
Now, whenever you want to get a lib.MyUser from Mongo, for read purposes only, you can deserialize it directly in this format, ignoring the added fields. If you need to do some updates on it however, you will have to deserialize it as a MyUser, to be able to update this particular BSON document.
scala usage
When you have deserialized the object as a MyUser (say, for an update), you might still want an easy access to all fields exposed in lib.MyUser, without having to access the field user every time. This can be done with an implicit conversion.
generalization
By the way, you can do this for any object that you want serialized, in a generic way...
To sum it all up
case class MongoRepr[T](_id: Option[BSONObjectId]
value: T,
created: Option[DateTime],
updated: Option[DateTime])
object MongoRepr {
//implement flattened (de)serialization, this depends on your
// mongo driver and what you're using for serialization (JSON/BSON)
def handler[T](implicit handlerT: BSONHandler[T]): BSONHandler[MongoRepr[T]] = ???
implicit def toValue[T](repr: MongoRepr[T]): T = repr.value
}

Related

Common Case Class Variables

Does anyone have any thoughts/better way to handle common fields that cross multiple case classes...for example I have the following case class;
case class Customer(
name: String,
refId: String = ReferenceIdGenerator.generateRefId("CUSTOMER"),
createdAt: DateTime = DateTime.now,
updatedAt: DateTime = DateTime.now,
id: Option[Long] = None)
The last four variables are repeated in every "model/domain" case class in my example application. Is there a better way to remove that common boilerplate code?
One thought would be to create a "common" case class and at least the boilerplate code is limited, for example;
case class CommonFields(refId: String...etc)
case class Customer(name: String, common: CommonFields)
Thoughts?
One thought would be to create a "common" case class and at least the boilerplate code is limited, for example
Yes, this is the correct solution, assuming this works well with whatever you use to interact with the database. If you are using Slick, see also Extending SLICK Tables in a DRY manner.

Scala idiom for partial models?

I am writing a HTTP REST API and I want strongly typed model classes in Scala e.g. if I have a car model Car, I want to create the following RESTful /car API:
1) For POSTs (create a new car):
case class Car(manufacturer: String,
name: String,
year: Int)
2) For PUTs (edit existing car) and GETs, I want tag along an id too:
case class Car(id: Long,
manufacturer: String,
name: String,
year: Int)
3) For PATCHes (partial edit existing car), I want this partial object:
case class Car(id: Long,
manufacturer: Option[String],
name: Option[String],
year: Option[Int])
But keeping 3 models for essentially the same thing is redundant and error prone (e.g. if I edit one model, I have to remember to edit the other models).
Is there a typesafe way to maintain all 3 models? I am okay with answers that use macros too.
I did manage to combine the first two ones as following
trait Id {
val id: Long
}
type PersistedCar = Car with Id
I would go with something like that
trait Update[T] {
def patch(obj: T): T
}
case class Car(manufacturer: String, name: String, year: Int)
case class CarUpdate(manufacturer: Option[String],
name: Option[String],
year: Option[Int]) extends Update[Car] {
override def patch(car: Car): Car = Car(
manufacturer.getOrElse(car.manufacturer),
name.getOrElse(car.name),
year.getOrElse(car.year)
)
}
sealed trait Request
case class Post[T](obj: T) extends Request
case class Put[T](id: Long, obj: T) extends Request
case class Patch[T, U <: Update[T]](patch: U) extends Request
With Post & Put everything is straightforward. With Patch a bit more complicated. I'm pretty sure CarUpdate class can be replaced with auto generated with macros.
If you'll update you Car model, you'll definitely will not forget about patch, because it will fail at compile time. However this two models looks too "copy-paste-like".
You could represent your models as Shapeless records, then the id is simply one more field on the front, and the mapping to/from options can be done generically using ordinary Shapeless type-level programming techniques. It should also be possible to generically serialize/deserialize such things to JSON (I have done this in the past, but the relevant code belongs to a previous employer). But you would definitely be pushing the boundaries and doing complex type-level programming; I don't think mature library solutions with this approach exist yet.
Actually I managed to solve this using a little library that I wrote:
https://github.com/pathikrit/metarest
Using above library, this simply becomes:
import com.github.pathikrit.MetaRest._
#MetaRest case class Car(
#get #put id: Long,
#get #post #put #patch manufacturer: String,
#get #post #put #patch name: String,
#get #post #put #patch year: Int)
)
While I do agree with Paul's comment (yes, you'd have a lot of duplicated fields, but that's because you are decoupling the external representation of the fields to the internal representation of the fields, which is a good thing in case you want to change your internal representation without changing the API), a possible way to achieve what you want could be (which, if I understood correctly, is to have a single representation):
case class CarAllRepresentationsInOne(
id: Option[Long] = None,
manufacturer: Option[String] = None,
name: Option[String] = None,
year: Option[Int] = None)
Since you have default values for everything set to None, you can instantiate this CClass from all the routes with the only disadvantages being of having to use named parameters during instantiation and checking for None in all the usages of the fields.
But I would strongly recommend having different types for your internal representation and for each possible external request resource: it may seem like duplication of code at the beginning, but the way you model cars inside your world should be separated by the resources used by the external world, in order to decouple them and allow you to change internal representation without changing the api contract with the outside when new needs arise.

How best to keep a cached list of member fields, one each for a family of case classes in Scala

This is a follow up to the following question: Fastest way to get the names of the fields of a case class in Scala
I'm trying to find a simple way to provide fast custom serialization (lets say to a list of tuples of (String, Object), which can be converted into a db row in production or an in memory map in unit testing) to a family of case classes in Scala, and it seems that keeping a cached list of a fields of the class may be a promising way of doing this. However, I'm not sure about the cleanest way to do this. I know I can do something like the following:
case class Playlist(
val id: Option[Long],
val title: Option[String],
val album: Option[String],
val artist: Option[String],
val songId: Option[UUID]) {
def serialize = Playlist.fields.map(f => (f.getName, f.get(this)))
}
object Playlist {
val empty = Playlist(None, None, None, None, None)
val fields = Playlist.empty.getClass.getDeclaredFields.toList
fields foreach { _.setAccessible(true) }
}
There a are a couple of things I don't like about this, however:
I don't want to have to use empty from the companion class just to get a cached list of fields
I don't want to have to declare the serialization logic for each case class for which I want this serialization behavior. There are probably a few ways of getting around this, but I'm not sure of the cleanest way that will give correct behavior (worried about mixing reflection and inheritance)
What's the cleanest way to achieve this in Scala?
I think it would be simplest to keep a cache map of Class[_] -> fields separately from any individual case class, such as in a global singleton with a method serialize(instance). This way you don't have to write any extra code in the classes you wish to serialize.
Another way could be to create a trait to mixin to the case classes' companion objects, with the cached list of fields, and an implicit wrapper class to add the serialize method. You can use an implicit ClassTag to initialize fields:
abstract class MyCompanion[T](implicit ctag: ClassTag[T]) {
private val fields = ctag.runtimeClass.getDeclaredFields.toList
fields foreach { _.setAccessible(true) }
implicit class AddSerializeMethod(obj: T) {
def serialize = fields.map(f => (f.getName, f.get(obj)))
}
}
case class C(...) { ... }
object C extends MyCompanion[C]
Unfortunately, it seems you can't make AddSerializeMethod a value class this way.

How to combine class cases?

I am using play framework and slick, play framework uses a case map in the form validation but there are values that I don't need validated as they are not inputed by the user, e.g. the ID & Date which is done is provided on the backend.
By the end I would like to have a class case like this, to provide to Slick and use with my Table.
case class Order(id: Long, order: String, date: Date)
For Play's Form validation I would provide a seperate case class:
case Class inputableOrder(order: String)
Can I then create the Order class that will grab the variables from inputableOrder and added it the Order class?
case class Order(id: Long, date: Date) // <- some way to add the variable from inputableOrder?
I'm just trying to prevent repetition but I still need two different case classes (one for form validation and another for working with the database).
Is there a way to modify an existing case Class , remove a variable or modify a variable type?
I think you have several options here:
Make InputableOrder a part of Order:
case class InputableOrder(order: String) // ...
case class Order(input: InputableOrder, id: Long, date: Date) // ..
This is probably the most idiomatic solution. But it can be inflexible if you later realize that InputableOrder needs something that shouldn't be in Order.
Make Order a subclass of InputableOrder. In this case there is some code repetition when passing arguments to the superclass, and the superclass can't be a case class, so you have to declare it as a regular class and create an extractor yourself:
class InputableOrder(val order: String) // ...
object InputableOrder {
def unapply(o: InputableOrder): Option[String] = Some(o.order);
// if you have more than one constructor arguments, return
// a tuple like Option[(String,String)] etc.
}
case class Order(override val order: String, id: Long, date: Date)
extends InputableOrder(order) // ...
Again, the same problems can arise as with the previous point.
Make the classes distinct and create helper methods to convert between them. The choice depends on your design, but I find this solution to be most flexible:
case class InputableOrder(order: String);
case class Order(order: String, id: Long, date: java.util.Date) {
// An additional constructor to use when converting from Inputable:
def this(other: InputableOrder, id: Long, date: java.util.Date) =
this(other.order, id, date);
// Update this instance with `other`:
def this(that: Order, other: InputableOrder) =
this(other, that.id, that.date);
def toInput = InputableOrder(order);
}
This way you can create an Order from an InputableOrder just by supplying the missing fields and vice versa. You need to write these helper methods/constructors once, but using them is then easy.
You can also use implicit methods such as
implicit def toInput(other: InputableOrder): Order = other.toInput;
to make things even easier.

What is the best way to deal with compsite keys when using Salat with MongoDB?

I'm using Salat with MongoDB and I'm trying to convert to natural keys to avoid duplicates in the database. The case class I'm using looks somewhat like:
case class Foo(someRelatedId: String, email: String ...)
I would like to add a natural key that consists of someRelatedId+email and have MongoDB use that instead of the default ObjectId. From the documentation I get the feeling it is possible, but I'm still groping around for a working solution. This is in a large part due to my lack of proficiency with Scala itself, I'm sure.
Update: I have a working solution now, but I'm still wondering if it is the best way to go
case class Foo(someRelatedId: String, email: String, naturalKey: String)
object Foo {
def apply((someRelatedId: String, email: String) {
apply(someRelatedId, email, someRelatedId+email)
}
}
And then in package.scala I map to a custom salat context:
implicit val ctx = new Context() {
val name = Some("Custom Context")
}
ctx.registerGlobalKeyOverride(remapThis = "naturalKey", toThisInstead = "_id")
This way I avoid having a mandatory (meaningless) _id field in my domain classes, but I do have to overload apply() on the companion object, which seems a bit clunky.
main Salat developer here.
Like Milan suggested, create a case class for your composite key:
case class FooKey(someRelatedId: String, email: String)
case class Foo(#Key("_id") naturalKey: FooKey) {
// use #Persist if you want these fields serialized verbatim to Mongo - see https://github.com/novus/salat/wiki/Annotations for details
#Persist val email = naturalKey.email
#Persist val someRelatedId = naturalKey.someRelatedId
}
object FooDAO extends SalatDAO[Foo, FooKey](collection = /* some Mongo coll */ )
If you object to "_id" as a field name, you can use a global override in the context to remap "_id" to "naturalKey", or supply ad hoc #Key overrides on each object.
I don't personally like giving the _id a different name in your models as then your Mongo queries must use the serialized key "_id" while all your business logic must use the case class field name ("naturalKey" or whatever), but YMMV.
P.S. Our mailing list is at http://groups.google.com/group/scala-salat - I'll see your question quicker there than Stack Overflow.