In Lift with MongoDB, storing lists of heterogeneous data - mongodb

I need to make a web service that can will contain lists of objects. One list can contain objects of many types. Here, for example, I have a library of media items. Each item can be either a link or a video, each with their own metadata.
I want to do this with the Lift web framework, because I need something that compiles to WAR and I've used Lift before.
I thought that using MongoDB for this sort of storage would work as, by definition, it's supposed to be able to handle collections of heterogeneous items.
I can define types of BSON objects to be stored in Lift records, but I can't seem to stick away from creating only one type of object in one record/collection. Ideally, I'd like each "thing" (for lack of a better word) in my Library to be either a video or a link. For example:
[
{
"type" : "Video",
"title" : "Story",
"videoID" : "123ab4",
"description": "Feature-length epic",
"time" : 12.6
},
{
"type" : "link",
"url" : "http://www.google.com",
"title": "Search for stuff"
}
]
I should be able to do it with the right type of inheritance, but the way all of the record objects's parents inherit from the object throws me off. Can I get this to work? Having a collection of different things that Lift can use as such?
Here's what I have so far. I haven't tested it, but even if it works what it does is NOT what I want.
import net.liftweb.record._
import net.liftweb.record.field._
import net.liftweb.mongodb._
import net.liftweb.mongodb.record._
import net.liftweb.mongodb.record.field._
class VideoShelf private () extends BsonRecord[VideoShelf] {
def meta = VideoShelf
object title extends StringField (this, 256)
object videoID extends StringField (this, 32 )
object description extends StringField (this, 256)
object time extends DecimalField(this, 0 )
}
object VideoShelf extends VideoShelf with BsonMetaRecord[VideoShelf]
class LinkShelf private () extends BsonRecord[LinkShelf] {
def meta = LinkShelf
object url extends StringField(this, 128)
object title extends StringField(this, 256)
}
object LinkShelf extends LinkShelf with BsonMetaRecord[LinkShelf]
class MediaLibrary private () extends MongoRecord[MediaLibrary] with ObjectIdPk[MediaLibrary] {
def meta = MediaLibrary
///////////////////////////////////////
///////////////////////////////////////
// What I want is this record type to
// contain either of these:
///////////////////////////////////////
object videoshelf extends BsonRecordField(this, VideoShelf)
object linkshelf extends BsonRecordField(this, LinkShelf )
}
object MediaLibrary extends MediaLibrary with MongoMetaRecord[MediaLibrary]
How can I get this?

After seeking more, I found this post: https://groups.google.com/forum/#!topic/liftweb/LmkhvDgrgrI
That led me to this conclusion, which I think is correct, though it has not been tested yet. I may be missing some pieces for it to fully work.
import net.liftweb.record._
import net.liftweb.record.field._
import net.liftweb.mongodb._
import net.liftweb.mongodb.record._
import net.liftweb.mongodb.record.field._
/**
* The base record type for library objects.
*/
trait MediaLibrary[T <: MediaLibrary[T]] extends MongoRecord[T] with ObjectIdPk[T] {
self: T =>
}
/**
* Items in the library that are videos.
*/
class VideoItem extends MediaLibrary[VideoItem] {
def meta = VideoItem
object title extends StringField (this, 256)
object videoID extends StringField (this, 32 )
object description extends StringField (this, 256)
object time extends DecimalField(this, 0 )
}
object VideoItem extends VideoItem with MongoMetaRecord[VideoItem]
/**
* Items in the library that are links.
*/
class LinkItem extends MediaLibrary[LinkItem] {
def meta = LinkItem
object url extends StringField (this, 256)
object title extends StringField (this, 256)
}
object LinkItem extends LinkItem with MongoMetaRecord[LinkItem]
UPDATE
I've also just found out that there is a MongoDB-specific record that's a list of case classes. That seems to be exactly what I need! It's the power of Scala and Mongo being used hand in hand! That's what I wanted from the start. I'll have to try that tomorrow.

Related

Akka Typed and having base class for MessageAdapters

I am trying something with Akka Typed and Scala, actually something very simple as concept but I could not make it work so may be you can help me.
All my Actors will have one common Signal, so I try to place it to a base class and let my all Actors share it but it the compiler refuse it in the MessageAdapter....
So my code looks like following....
object ContractActor {
sealed trait ContractEvent extends BaseEvent
final case class onApprove(payload: Payload) extends ContractEvent
}
class ContractActor(ctx: ActorContext[ContractEvent]) extends BaseActor {
val listingAdapter = : ActorRef[Receptionist.Listing] = ctx.
messageAdapter(
listing => onAddRelatedEvent(listing)
}
and base actor
object BaseActor {
trait BaseEvent;
final case class onAddRelatedEvent(listing: Receptionist.Listing) extends BaseEvent
}
The compiler complains about onAddRelatedEvent is not known on ContractEvent which surprise me because ContractEvent extends BaseEvent....
What am I missing here....
Class ContractActor extending BaseActor does not automatically bring BaseActor's companion object into scope. To bring it into scope, just import it inside class ContractActor:
import BaseActor._
Alternatively, you could move the inner trait/case class into BaseActor's companion class.

How do I get circe to have an either/or output scenario for the generated Json?

Here's what I intend - let's say I have a field called medical_payments - it can "either" be a limit if one elects or waived
{
"medical_payments":
{
"limit_value":"one_hundred"
}
}
If it's elected as a waiver then it should be:
{
"medical_payments":
{
"waived":true
}
}
So far here's what I have:
sealed trait LimitOrWaiver
case class Limit(limit_key: String) extends LimitOrWaiver
case class Waived(waived: Boolean) extends LimitOrWaiver
case class Selection(medical_payments: LimitOrWaiver)
Sample data:
Selection(medical_payments = Limit("one_hundred")).asJson
Output:
{
"medical_payments":
{
"Limit": { "limit_value":"one_hundred" } // additional object added
}
}
Similarly for Selection(medical_payments = Waived(true)).asJson an additional Waived:{...} is added to the Json.
I'd like it to be an either/or. What's the best way to achieve this?
The only way that I've been able to think of (not to my liking) is to use forProductN functions per the doc and manually do all this - but it's way to cumbersome for a large Json.
You can almost accomplish this with generic derivation using the configuration in generic-extras:
sealed trait LimitOrWaiver
case class Limit(limitValue: String) extends LimitOrWaiver
case class Waived(waived: Boolean) extends LimitOrWaiver
case class Selection(medicalPayments: LimitOrWaiver)
import io.circe.generic.extras.Configuration, io.circe.generic.extras.auto._
import io.circe.syntax._
implicit val codecConfiguration: Configuration =
Configuration.default.withDiscriminator("type").withSnakeCaseMemberNames
And then:
scala> Selection(medicalPayments = Limit("one_hundred")).asJson
res0: io.circe.Json =
{
"medical_payments" : {
"limit_value" : "one_hundred",
"type" : "Limit"
}
}
(Note that I've also changed the Scala case class member names to be Scala-idiomatic camel-case, and am handling the transformation to snake-case in the configuration.)
This isn't exactly what you want, since there's that extra type member, but circe's generic derivation only supports round-trip-able encoders / decoders, and without a discriminator of some kind—either a member like this or the extra object layer you point out in the question—it's impossible to round-trip values of arbitrary ADTs through JSON.
This might be fine—you might not care about the extra type in your object. If you do care, you can still use derivation with a little extra work:
import io.circe.generic.extras.Configuration, io.circe.generic.extras.auto._
import io.circe.generic.extras.semiauto._
import io.circe.ObjectEncoder, io.circe.syntax._
implicit val codecConfiguration: Configuration =
Configuration.default.withDiscriminator("type").withSnakeCaseMemberNames
implicit val encodeLimitOrWaiver: ObjectEncoder[LimitOrWaiver] =
deriveEncoder[LimitOrWaiver].mapJsonObject(_.remove("type"))
And:
scala> Selection(medicalPayments = Limit("one_hundred")).asJson
res0: io.circe.Json =
{
"medical_payments" : {
"limit_value" : "one_hundred"
}
}
If you really wanted you could even make this automatic, so that type would be removed from any ADT encoders you derive.

phantom cassandra multiple tables throw exceptions

I'm using phantom to connect cassandra in play framework. Created the first class following the tutorial. Everything works fine.
case class User(id: String, page: Map[String,String])
sealed class Users extends CassandraTable[Users, User] {
object id extends StringColumn(this) with PartitionKey[String]
object page extends MapColumn[String,String](this)
def fromRow(row: Row): User = {
User(
id(row),
page(row)
)
}
}
abstract class ConcreteUsers extends Users with RootConnector {
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs id).one()
}
def create(id:String, kv:(String,String)): Future[ResultSet] = {
insert.value(_.id, id).value(_.page, Map(kv)).consistencyLevel_=(ConsistencyLevel.QUORUM).future()
}
}
class UserDB(val keyspace: KeySpaceDef) extends Database(keyspace) {
object users extends ConcreteUsers with keyspace.Connector
}
object UserDB extends ResourceAuthDB(conn) {
def createTable() {
Await.ready(users.create.ifNotExists().future(), 3.seconds)
}
}
However, when I try to create another table following the exact same way, play throws the exception when compile:
overriding method session in trait RootConnector of type => com.datastax.driver.core.Session;
How could I build create another table? Also can someone explain what causes the exception? Thanks.
EDIT
I moved the connection part together in one class:
class UserDB(val keyspace: KeySpaceDef) extends Database(keyspace) {
object users extends ConcreteUsers with keyspace.Connector
object auth extends ConcreteAuthInfo with keyspace.Connector
}
This time the error message is:
overriding object session in class AuthInfo; lazy value session in trait Connector of
type com.datastax.driver.core.Session cannot override final member
Hope the message helps identify the problem.
The only problem I see here is not to do with connectors, it's here:
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs id).one()
}
This should be:
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs page).one()
}
Try this, I was able to compile. Is RootConnector the default one or do you define another yourself?
It took me 6 hours to figure out the problem. It is because there is a column named "session" in the other table. It turns out that you need to be careful when selecting column names. "session" obviously gives the above exception. Cassandra also has a long list of reserved keywords. If you accidentally use one of them as your column name, phantom will not throw any exceptions (maybe it should?). I don't know if any other keywords are reserved in phantom. A list of them will be really helpful.

Embedded JSON Objects not saved in BsonRecordListField using fromJSON

i'm using lift-mongodb-record 2.4 to manage MongoRecords for a RESTful JSON Webservice. Everything works really good, except one issue i ran into: Embedded BsonRecordListFields are not saved automatically.
This is my test-JSON:
{"name":"test","control_points":[{"dx":64,"dy":97},{"dx":358,"dy":64},{"dx":211,"dy":80.5}]}
But printing the model through println(Artifact.fromJSON(request.body).get) will only print
class com.test.model.Artifact={name=test, control_points=}
these are my model classes:
class ControlPoint private () extends BsonRecord[ControlPoint] {
def meta = ControlPoint
object dx extends DoubleField(this)
object dy extends DoubleField(this)
}
object ControlPoint extends ControlPoint with BsonMetaRecord[ControlPoint]
class Artifact private () extends MongoRecord[Artifact] with ObjectIdPk[Artifact] {
def meta = Artifact
object name extends StringField(this, 1024)
object control_points extends BsonRecordListField(this,ControlPoint)
}
object Artifact extends Artifact with MongoMetaRecord[Artifact] {}
why is the embedded stuff not saved? do I miss anything here?
thanks a lot!
Martin
As pointed out on the lift mailing list lift-json parses the number as ints because they don't have a decimal point. I just changed the fields to IntField and parsed all date to Integers through parseInt on client-side.

MongoDB field name in Lift's Record Field

I have a pre-existing database, and this code:
class User private() extends MongoRecord[User] with StringPk[User] {
def meta = User
object createdAt extends DateTimeField(this)
object lastLogin extends DateTimeField(this)
object password extends StringField(this, 128)
object salt extends StringField(this, 128)
}
object User extends User with MongoMetaRecord[User]
The problem is, in my database, the field createdAt is called created-at, and similar with lastLogin. Looking into the docs, there are several object fields that could be doing the job (label, name, title, uniqueFieldId), so which one would it be?
The member name seems to be the one looking for. As in
object createdAt extends DateTimeField(this) {
def name = "created-at"
}