Using $push + $each in Swift Driver

Hey all

I have an optional array field within a document where values can be added to it over time and for some reason I can’t seem to get it to work. As an example, I’ve expanded on code from the ComplexVaporExample so hopefully this makes some sense and isn’t complete gibberish. Please let me know otherwise.

I’ve added a friends property to Kitten , with a route where I want to be able to append single and / or multiple values at a time from an array.

I am struggling to work how to create the required BSON to make it work for the specific line below. I have tried using the BSON array modifier but that creates an array within an array.

 "$each": .document(try BSONEncoder().encode(kittenFriends))

Full code below. Unable to get correct syntax for the updateDocument

struct Kitten: Content {     
    var _id: BSONObjectID?     
    let name: String        
    let color: String         
    let favoriteFood: CatFood          
    var friends: [Kitten]? 
}

app.patch("kittens", ":name", "friends") { req -> EventLoopFuture<Response> in
    
      let nameFilter = try getNameFilter(from: req)

      let kittenFriends = try req.content.decode([Kitten].self)
        
      let updateDocument: BSONDocument = [
            "$push": [
                "friends": [
                    "$each": .document(try BSONEncoder().encode(kittenFriends))
                ]
            ]
      ]

      return collection.updateOne(filter: nameFilter, update: updateDocument)
          .hop(to: req.eventLoop)
          .flatMapErrorThrowing { error in
              throw Abort(.internalServerError, reason: "Failed to update kitten: \(error)")
          }
          .unwrap(or: Abort(.internalServerError, reason: "Unexpectedly nil response from database"))
          .flatMapThrowing { result in
              guard result.matchedCount == 1 else {
                  throw Abort(.notFound, reason: "No kitten with matching name")
              }
              return Response(status: .ok)
          }
  }
1 Like

Hi @Piers_Ebdon and welcome to the forums,

The syntax of $each with $push operator requires it to be an array and not a document. For example:

let kittenFriends : BSON = [.string("Garfield")]
let updateDocument: BSONDocument = [
            "$push": [
                "friends": [
                   "$each":kittenFriends
                ]
            ]
      ]

let updateResult = try collection.updateOne(filter: ["Name":"Felix"], update: updateDocument)

I have a limited knowledge on Vapor, but please see MongoSwift BSON Library documentation to find out more about how to work with BsonDocument.

Regards,
Wan.

3 Likes

To handle encoding the kittens correctly into the document, I think what you need is something like:

let encodedKittens: [BSON] = try BSONEncoder().encode(kittenFriends).map { .document($0) }

let updateDocument: BSONDocument = [
            "$push": [
                "friends": [
                   "$each": .array(encodedKittens)
                ]
            ]
      ]
3 Likes

Thanks @kmahar ! that worked :+1: