alifirat
alifirat

Reputation: 2927

Misunderstanding error about ReactiveMongo

I defined the following class which I want to modelize :

case class Record(
                     recordKey: String,
                     channels: Map[String, Channel],
                     )

  object Record {
    implicit val RecordFormat = Json.format[Record]
  }

Now, I want to get one object of this type from reactive mongo like this (in an another class):

import scala.concurrent.duration._
import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import reactivemongo.api._
import reactivemongo.api.collections.bson.BSONCollection
import reactivemongo.bson.BSONDocument

object Test {
  val collection = connect()
  val timeout = 10.seconds
  def connect() : BSONCollection = {
    val config = ConfigFactory.load()
    val driver = new MongoDriver
    val connection = driver.connection(List(config.getString("mongodb.uri")))
    val db = connection("/toto")
    db.collection("foo")
  }

  def findRecord(recordKey : String) : Record = {
    return Test.collection
      .find(BSONDocument("recordKey"->recordKey))
      .one[Record]
  }

But this code doesn't compile :

 could not find implicit value for parameter reader: reactivemongo.bson.BSONDocumentReader[Record]

Could someone explain how to fix this issue ?

I also test that :

def findRecord(recordKey : String) : Record = {
    val futureRecord : Future[Option[Record]] =
      Test.collection
        .find(BSONDocument("recordKey"->recordKey))
        .one[Record]
    return Await.result(futureRecord, 10.seconds).getOrElse(null)
  }

I also added my build.sbt :

libraryDependencies ++= Seq(
  "org.apache.spark" % "spark-streaming_2.10" % "1.5.2",
  "org.apache.spark" % "spark-streaming-kafka_2.10" % "1.5.2",
  "org.slf4j" % "slf4j-api" % "1.7.13",
  "org.slf4j" % "slf4j-simple" % "1.7.13",
  "com.amazonaws" % "aws-java-sdk" % "1.10.12",
  "com.typesafe.play" % "play-json_2.10" % "2.4.6",
  "com.typesafe" % "config" % "1.3.0",
  "org.scalaj" %% "scalaj-http" % "2.2.1",
  "com.typesafe.akka" % "akka-actor_2.10" % "2.3.14",
  "org.reactivemongo" %% "reactivemongo" % "0.11.9",
  "com.github.nscala-time" %% "nscala-time" % "2.6.0"
)

Note that is it not a Play App.

Upvotes: 0

Views: 890

Answers (1)

Barry
Barry

Reputation: 1820

You need to define a BSONDocumentReader for your case class Record. Here is a link to the Documentation. Very similar to Play JSON Readers and Writers Reactive Mongo needs to understand how to convert back and forth between your domain object and BSONDocument. Similar to Play JSON as well you can write these out in a more manual style(Write a BSONDocumentReader instance and a BSONDocumentWriter instance) and customize every detail and apply transformations etc. Similar in style to Play JSON's format you used above ReactiveMongo does provide helpful macros to generate these classes for you.

For your Record class you would need to add an implicit val like this to your object:

import reactivemongo.bson._

implicit val recordHandler: BSONHandler[BSONDocument, Record] = Macros.handler[Record]

/* Or only one of these [if your only ever writing or reading this data etc:
 implicit val recordReader: BSONDocumentReader[Record] = Macros.reader[Record]
 implicit val recordWriter: BSONDocumentWriter[Record] = Macros.writer[Record]
*/

I would say try to start with the Macros and see if those meet your needs. If you need more control of the processing/transformation you can define your own BSONDocumentReader and BSONDocumentWriter instances.

updated record class

import play.api.libs.json.Json
import reactivemongo.bson._


case class Channel(label: String,amplitude: Double,position: Option[String])

object Channel {
  implicit val ChannelFormat = Json.format[Channel]
  implicit val channelHandler: BSONHandler[BSONDocument, Channel] =   Macros.handler[Channel]
 }

object RecordType extends Enumeration {
 type RecordType = Value
 val T1 = Value
 implicit val enumFormat = new Format[RecordType] {
  def reads(json: JsValue) = JsSuccess(RecordType.withName(json.as[String]))
  def writes(enum: RecordType) = JsString(enum.toString)
 }
 implicit  object RecordTypeReader extends BSONDocumentReader[RecordType] {
  def read(doc: BSONDocument) : RecordType  = {
     RecordType.withName(doc.getAs[String]("recordType").get)
  }
}
implicit object RecordTypeWriter extends BSONDocumentWriter[RecordType] {
  def write(recordType: RecordType) : BSONDocument = BSONDocument(
    "recordType" -> BSONString(recordType.toString)
  )
 }
}

case class Record(recordKey: String,recordType: RecordType.Value,channels: Map[String, Channel])

object Record {
 implicit val RecordFormat = Json.format[Record]
 implicit val recordHandler: BSONHandler[BSONDocument, Record] =  Macros.handler[Record]
}

Upvotes: 2

Related Questions