Reputation: 1062
I am developing a ORM-style library on top of ReactiveMongo. Currently I've tried to implement a nested document representation - however I've been stuck with Scala's type inference for my class. I am quite new to Scala so every help is welcome.
This is my attempt:
trait MongoDocument[T <: MongoDocument[T]] {
self: T =>
val db: DefaultDB
val collection: String
var fields: List[MongoField[T,_]] = List.empty
def apply(doc: TraversableBSONDocument): T = { // loads the content of supplied document
fields.foreach { field =>
doc.get(field.field).foreach(field.load(_))
}
this
}
}
trait MongoMetaDocument[T <: MongoDocument[T]] extends MongoDocument[T] {
self: T =>
type DocType = T
protected val clazz = this.getClass.getSuperclass
def create: T = clazz.newInstance().asInstanceOf[T]
def find(id: String): Future[Option[T]] = {
db(collection).find(BSONDocument("_id" -> BSONObjectID(id)))
.headOption().map(a => a.map(create.apply(_)))
}
}
abstract class MongoField[Doc <: MongoDocument[Doc], T](doc: MongoDocument[Doc], val field: String) {
var value: Option[T] = None
def is: Option[T] = value
def apply(in: T) { value = Some(in) }
def unset() { value = None }
def load(bson: BSONValue)
doc.fields ::= this
}
class MongoInteger[Doc <: MongoDocument[Doc]](doc: Doc, field: String)
extends MongoField[Doc, Int](doc, field) {
def load(bson: BSONValue) { value = Try(bson.asInstanceOf[BSONNumberLike].toInt).toOption }
}
class MongoDoc[Doc <: MongoDocument[Doc], SubDocF <: MongoMetaDocument[SubDoc], SubDoc <: MongoDocument[SubDoc]](doc: Doc, field: String, meta: SubDocF)
extends MongoField[Doc, SubDocF#DocType](doc, field) {
def load(bson: BSONValue) {
value = Try(bson.asInstanceOf[TraversableBSONDocument]).toOption.map { doc =>
meta.create.apply(doc)
}
}
}
Assuming that I have following code:
class SubEntity extends MongoDocument[SubEntity] {
val db = Db.get
val collection = ""
val field = new MongoInteger(this, "field")
}
object SubEntity extends SubEntity with MongoMetaDocument[SubEntity]
I would like to write another entity as:
class Another extends MongoDocument[Another] {
val db = Db.get
val collection = "test"
val subEntity = new MongoDoc(this, "subEntity", SubEntity)
}
object Another extends Another with MongoMetaDocument[Another]
Where Db.get
just returns a DefaultDB
.
However Scala cannot infer the types for the MongoDoc
instance even though I thought they might be inferrable (as Doc
is easily inferred, SubDocF
can be inferred to SubEntity.type
and as SubEntity
mixins just MongoMetaDocument[SubEntity]
then the SubDoc
type has to be SubEntity
). If I use following code, everything goes just fine:
val subEntity = new MongoDoc[Another,SubEntity.type,SubEntity](this, "subEntity", SubEntity)
Is there some solution that the types need not be set explicitly? As I need to build an instance of a class that extends from MongoDocument
trait, I was trying to solve that by using the metaobject that has the create
method.
Currently the only workaround I've come up with makes use of implicitly
but that would make the entity definition a bit more nasty.
Thank you for helping me to sort this out (or for giving me some hint how to arrange my class hierarchy that this won't be a problem)
Upvotes: 3
Views: 1145
Reputation: 11366
You'll get better type inference if you get rid of the "F-bounded Polymorphism". (that is when you are finding yourself writing type arguments of the form "T <: Foo[T]"), and instead use abstract type members in the traits which are reified in the concrete classes.
You'll find that inference works here:
trait MongoDocument
type DocType <: MongoDocument
def apply(doc: TraversableBSONDocument): DocType = ??? // loads the content of supplied document
}
trait MongoMetaDocument extends MongoDocument {
protected val clazz = this.getClass.getSuperclass
def create: DocType = clazz.newInstance().asInstanceOf[DocType]
def find(id: String): Future[Option[DocType]] = ???
}
abstract class MongoField[Doc <: MongoDocument, T](doc: MongoDocument, field: String) {
type DocType <: MongoDocument
var value: Option[T]
def is: Option[T] = value
def apply(in: T) { value = Some(in) }
def unset() { value = None }
def load(bson: BSONValue)
}
class MongoDoc[Doc <: MongoDocument, SubDocF <: MongoMetaDocument, SubDoc <: MongoDocument](doc: Doc, field: String, meta: SubDocF)
extends MongoField[Doc, SubDocF#DocType](doc, field) {
type DocType = Doc
def load(bson: BSONValue) {
value = Try(bson.asInstanceOf[TraversableBSONDocument]).toOption.map { doc =>
???
}
}
}
class SubEntity extends MongoDocument {
type DocType = SubEntity
???
}
object SubEntity extends SubEntity with MongoMetaDocument
class Another extends MongoDocument {
type DocType = Another
val subEntity = new MongoDoc(this, "subEntity", SubEntity)
}
object Another extends Another with MongoMetaDocument
Upvotes: 2