1

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?

eje211
  • 2,385
  • 3
  • 28
  • 44
  • Do you really need to map this to records? Why don't you just use plain MongoDB driver and get documents out of data store? Do you need these to became Scala objects? – Piotr Gwiazda Feb 10 '14 at 21:36
  • First of all, I want to do it "the right way." If using the plain MongoDB driver is hackier, then I'd rather not use it. (I don't know if it is, but that is a criterion for me.) Second, I want the web app to ask Scala to create an object and then immediately return the object's ID to the web page so that I know that the object is in the database by the time it gets on the page. In my current temporary implementation (which does not use Scala), the object is displayed on the page first and then saved. I had a problem a few days ago when it turned out that the object was silently not saved. – eje211 Feb 10 '14 at 21:51
  • Both Record and Mongo Java driver will report errors depending on your write concern settings. So you might see no errors when you hoped to see them. – yǝsʞǝla Feb 11 '14 at 02:46

1 Answers1

1

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.

eje211
  • 2,385
  • 3
  • 28
  • 44
  • This code looks fine to me, I've done very similar inheritance but on MongoRecord level and it worked fine. – yǝsʞǝla Feb 11 '14 at 02:41
  • Thanks. I'm new to using a completely dynamic system like Mongo from a static one like Scala. It's easier the other way round, like with Jython. – eje211 Feb 11 '14 at 17:21