Reputation: 1120
I'm trying to write a nice generic persistence service with one implementation for mongo using Reactive Mongo and I'm struggling with providing the implicit reader/writer for my record class children. Here's the code.
First the base record class (each persisted record must implement it.
abstract class Record {
val _id: BSONObjectID = BSONObjectID.generate()
}
Second one record child case class (very simple) with its writer/reader (both possible way to do it, Macros vs custom in comment)
case class TestRecord() extends Record {}
object TestRecord {
// implicit object TestRecordWriter extends BSONDocumentWriter[TestRecord] {
// def write(testRecord: TestRecord): BSONDocument = BSONDocument()
// }
//
// implicit object TestRecordReader extends BSONDocumentReader[TestRecord] {
// def read(doc: BSONDocument): TestRecord = TestRecord()
// }
implicit def reader = Macros.reader[TestRecord]
implicit def writer = Macros.writer[TestRecord]
}
And then the service itself
class MongoPersistenceService[R <: Record] @Inject()()
(implicit ec: ExecutionContext, tag: ClassTag[R]) {
val collectionName: String = tag.runtimeClass.getSimpleName
def db: Future[DefaultDB] = MongoConnectionWrapper.getMongoConnection("mongodb://127.0.0.1", "27017")
.flatMap(_.database("testDb"))
def collection: Future[BSONCollection] = db.map(_.collection(collectionName))
def persist(record: R): Future[Unit] = {
collection.flatMap(_.insert(record)).map(_ => {})
}
def read(id: BSONObjectID): Future[R] = {
for {
coll <- collection
record <- coll.find(BSONDocument("_id" -> id)).one[R].mapTo[R]
} yield record
}
}
And here's my failing test:
import scala.concurrent.ExecutionContext.Implicits.global
class MongoPersistenceServiceSpec extends WordSpec with Matchers with BeforeAndAfter {
val persistenceService = new MongoPersistenceService[TestRecord]()
"persist" when {
"called" should {
"succeeds when passing a not persisted record" in {
val testRecord = TestRecord()
persistenceService.persist(testRecord)
val persistedRecord = Await.result(persistenceService.read(testRecord._id), Duration(1000, "millis"))
assert(persistedRecord.eq(testRecord))
}
}
}
}
Compiler complains with following messages:
Error:(33, 32) could not find implicit value for parameter writer: reactivemongo.bson.BSONDocumentWriter[R]
collection.flatMap(_.insert(record)).map(_ => {})
Error:(33, 32) not enough arguments for method insert: (implicit writer: reactivemongo.bson.BSONDocumentWriter[R], implicit ec: scala.concurrent.ExecutionContext)scala.concurrent.Future[reactivemongo.api.commands.WriteResult].
Unspecified value parameters writer, ec.
collection.flatMap(_.insert(record)).map(_ => {})
Error:(39, 57) could not find implicit value for parameter reader: reactivemongo.bson.BSONDocumentReader[R]
record <- coll.find(BSONDocument("_id" -> id)).one[R].mapTo[R]
Error:(39, 57) not enough arguments for method one: (implicit reader: reactivemongo.bson.BSONDocumentReader[R], implicit ec: scala.concurrent.ExecutionContext)scala.concurrent.Future[Option[R]].
Unspecified value parameters reader, ec.
record <- coll.find(BSONDocument("_id" -> id)).one[R].mapTo[R]
Anybody has a idea what I might be missing? I'm still new to scala so help me find the issue in here.
I already tried writing custom BSONWriter/BSONReader instead of the BSONDocumentWriter/BSONDocumentReader provided here without success.
Upvotes: 1
Views: 1023
Reputation: 18424
In short, Scala doesn't search everywhere throughout your code for implicits. They need to be brought into scope, either through imports, passing them as parameters, or, as I would suggest here, through a context bound:
class MongoPersistenceService[R <: Record : BSONDocumentReader : BSONDocumentWriter]
This is a kind of short hand for (a) requiring that an implicit BSONDocumentReader[R]
(and writer) can be found at the time that the class is constructed with a specific R
, and (b) bringing those implicits into scope within the class's implementation so they can be implicitly passed to other methods like insert
.
In order to fulfill that new requirement (a) you may have to import TestRecord._
within your test.
Upvotes: 2
Reputation: 18177
Your persist
function doesn't have access to the fact that you have defined those implicit functions. The signature should be something like this:
def persist(record: R)(implicit def writer: BSONDocumentWriter[R]): Future[Unit]
And wherever you call persist
and pass a TestRecord
make sure that the implicit functions are in scope.
Upvotes: 1