Search code examples
mongodbscalagraphqlcaliban

MongoDB Scala driver. How to update only changed document's fields from the case class object


I have the following case class:

  @GQLDirective(federation.Key("User Config"))
case class UserConfig(
                        userId: Int,
                        personalGroup: Option[PersonalGroup] = None,
                        accountGroup: Option[AccountGroup] = None
                       ) extends Bson {
    override def toBsonDocument[TDocument](documentClass: Class[TDocument], codecRegistry: CodecRegistry): BsonDocument = {
      new BsonDocumentWrapper[UserConfig](this, codecRegistry.get(classOf[UserConfig]))
    }
  }

And trying to update an already stored document this way:

def update(userConfig: UserConfig) = {
    collection.updateOne(equal("userId", 1), userConfig).headOption()
  }

But when I am trying to do so I have the following error: Invalid BSON field name userId

I have also tried to use replaceOne method, but it will replace the whole object and erase the fields that I don't want to.

What I am trying to achieve: I want to save only changed fields in mongodb document. These fields are given by graphql request


Solution

  • For now, I found a way to convert and recursively merge new object with old ones like this. If somebody has a better code or opinion - I will be happy to chat

    val firstJson =  parse(userConfigFromRequest.toBsonDocument(classOf[UserConfig], codecRegistry).toJson).extract[Map[String, Any]]
    val secondJson = parse(config.toBsonDocument(classOf[UserConfig], codecRegistry).toJson).extract[Map[String, Any]]
    val merged = deepMerge(firstJson, secondJson)
    
      def deepMerge(
                     map1: Map[String, Any],
                     map2: Map[String, Any],
                     accum: MutableMap[String, Any] = MutableMap[String, Any]()
                   ): MutableMap[String, Any] = {
        map1 foreach {
          case (key, value: Map[String, Any]) => {
            map2.get(key) match {
              case Some(oldValue: Map[String, Any]) => {
                accum.put(key, deepMerge(value, map2, MutableMap(oldValue.toSeq: _*) ))
              }
              case None => accum.put(key, value)
            }
          }
          case (key, value) => {
            accum.put(key, value)
          }
        }
        accum
      }
    }