3

I have a Cart schema in Mongoose including CartItems as an array of objects.

{
  _id: string;
  items: [
    {
      product: string;
      options: {
        size: string;
        color: string;
      }
      quantity: number;
    }
  ]
}

In this case, is it possible to push an item when product and options doesn't match or add quantity when it does?

Here is the code that I have tried which does not work.

Cart.findByIdAndUpdate(_id, {
    items: {
      $cond: [
        {
          // Does not work
          $elemMatch: {
            product,
            "options.color": options.color,
            "options.size": options.size,
          },
        },
        {
          // Should add quantity where it is matched.
        },
        {
          $push: {
            product,
            productName,
            options,
            quantity,
          },
        },
      ],
    },
  });
1
  • What version of MongoDB are you using? Commented Dec 31, 2021 at 7:08

1 Answer 1

1

Query

  • pipeline update requires MongoDB >= 4.2
  • newProduct is the product you want to add (a js variable)
  • check if product already exists => add not-exist? field
  • if not-exists add it in the end
  • else map to find it and update the quantity
  • unset the 2 fields newProduct and not-exists

*it does 2 array reads, alternative could be to use $reduce but if you have many products $concatArrays is slow to be inside a reduce, so this is faster solutions (even if reduce would read the array 1 time only)

*you need a method to do update with pipeline, i don't know if mongoose is updated to support it, we are MongoDB 5 so i guess it will be(java is), in worst case you can use updateCommand and call it with runCommand(...)

Test code here

update({"_id" : "1"},
[{"$set":
  {"newProduct":
   {"product":"p1",
    "options":{"size":"s1", "color":"c1"},
    "quantity":1}}},
 {"$set":
  {"not-exists?":
   {"$eq":
    [{"$filter":
      {"input":"$items",
       "cond":
       {"$and":
        [{"$eq":["$$this.product", "$newProduct.product"]},
         {"$eq":["$$this.options.size", "$newProduct.options.size"]},
         {"$eq":["$$this.options.color", "$newProduct.options.color"]}]}}},
     []]}}},
 {"$set":
  {"items":
   {"$cond":
    ["$not-exists?", {"$concatArrays":["$items", ["$newProduct"]]},
     {"$map":
      {"input":"$items",
       "in":
       {"$cond":
        [{"$and":
          [{"$eq":["$$this.product", "$newProduct.product"]},
           {"$eq":["$$this.options.size", "$newProduct.options.size"]},
           {"$eq":["$$this.options.color", "$newProduct.options.color"]}]},
         {"$mergeObjects":
          ["$$this", {"quantity":{"$add":["$$this.quantity", 1]}}]},
         "$$this"]}}}]}}},
 {"$unset":["not-exists?", "newProduct"]}])

Query2

  • if you don't want to use update pipeline you can do it with more queries

Check if exists

db.collection.find({
  "_id" : "1",
  "items": {
    "$elemMatch": {
      "product": "p1",
      "options": {
        "size": "s1",
        "color": "c1"
      }
    }
  }
})

If not exists

db.collection.update({
  "_id": "1"
},
{
  "$push": {
    "items": "NEWITEM"   //put here the new object
  }
})

else If exists

db.collection.update({"_id" : "1"},
{
  "$inc": {
    "items.$[i].quantity": 1
  }
},
{
  arrayFilters: [
    {
      "i.product": "p1",
      "i.options.size": "s1",
      "i.options.color": "c1"
    }
  ]
})
Sign up to request clarification or add additional context in comments.

3 Comments

This is impressive. However, I feel this sort of query is too complicated to maintain.
you can do it also with more queries and not pipeline update, i added alternative way, but aggregation framework is important part of MongoDB, the first query looks bigger because of JSON syntax it looks complicated but its not so much.
My intention is to run atomically safe query which prevents manipulation between find and update. So the first one you suggested will fit my needs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.