Search code examples
mongodbscalaimplicitscala-macrosmongo-scala-driver

Magnet pattern for Scala MongoDB driver


The documentation describes using the magnet pattern to get implicit conversion to BSON types. See on this page http://mongodb.github.io/mongo-java-driver/4.1/driver-scala/bson/scala-documents/. I have tried defining an implicit object that extends BsonTransformer, but it fails to find a codec for the type. Am I missing something / has someone got this working? Sample code below, assume the insert method is being called.

case class CustomType(specialString: String)

implicit object TransformCustomType extends BsonTransformer[CustomType] {

  def apply(value: CustomType): BsonString = 
      BsonString(value.specialString)
}

lazy val db: MongoDatabase = client.getDatabase(dbName).withCodecRegistry(DEFAULT_CODEC_REGISTRY)
lazy val testCollection: MongoCollection[CustomType] = db.getCollection[CustomType](collectionName)

def insert: Future[Completed] = testCollection.insertOne(CustomType("a")).toFuture

Error - org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class com.bla.BlaClass$CustomType.

*note that I am aware this can be done with

val codecRegistry = fromRegistries(fromProviders(classOf[CustomType]))

but I am just using this example to ask to learn the magnet pattern for a messier case.


Solution

  • If there is an implicit BsonTransformer[T] (instance of type class BsonTransformer) in a scope then there are implicit conversions

    • T => CanBeBsonValue (CanBeBsonValue is a wrapper over BsonValue),

    • (String, T) => CanBeBsonElement (CanBeBsonElement is a wrapper over BsonElement, which is a "tuple" of String and BsonValue),

    • Iterable[(String, T)] => CanBeBsonElements (CanBeBsonElements is a wrapper over Iterable[BsonElement])

    defined in BsonMagnets (CanBeBsonValue, CanBeBsonElement, CanBeBsonElements are magnets 1 2).

    Then Document can be created via factory methods

    def apply(elems: CanBeBsonElement*): Document
    
    def apply(elem: CanBeBsonElements): Document
    

    So try

    val doc = Document("a" -> CustomType("bb"))
    
    val testCollection: MongoCollection[Document] = ???
    
    testCollection.insertOne(doc)