Jeep87c
Jeep87c

Reputation: 1120

Provided implicit reader/writer for case class not found

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

Answers (2)

Joe K
Joe K

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

Tyler
Tyler

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

Related Questions