Mongoose: Ensuring array is never larger than a certain number of items

Viewed 73

Trying to figure out the exact syntax for a set operation in Mongoose. I have a schema that looks like this:

const userSchema = mongoose.Schema({
  instagram: {
    images: [{
      id: { type: String },
      media_type: { type: String },
      media_url: { type: String },
      timestamp: { type: Date }
    }]
  }
});

I will periodically update this array with the user's latest Instagram photos like so:

User.findOneAndUpdate({ _id }, { $addToSet: { "instagram.images": { $each: arr } } });

How can I make sure the array is updated with the latest images, but never exceeds 30 images? The functionality should be that if the array has 20 images, and there are 11 new images to add, then all 11 new images should be added to the array and the last item that currently exists in the array should be removed.

2 Answers

You can't achieve this with $addToSet operator. You have 2 options:

  1. Make sure you're only fetching unique images, you can then achieve this using $push combined with the $slice operator.
User.findOneAndUpdate({ _id }, 
    { $push: { "instagram.images": { $each: arr, $slice: -30 } } });
  1. If you can't ensure unique images you'll have to fetch the user and filter the array in code before proceeding to update:
let user = User.findOne({_id});
// whatever unique indentifier makes an image "unqiue"
let imagesIds = user.instagram.images.map(v => v.id);
let fileterdImages = arr.filter((elem => !imagesIds.includes(elem.id)));

And finally using the syntax from option 1 with fileterdImages:

User.findOneAndUpdate({ _id }, 
    { $push: { "instagram.images": { $each: fileterdImages, $slice: -30 } } });

Note that mongoose tends to auto-generate an _id field for nested objects making the $addToSet not so relevant to begin with.

If I understood your logic right, try $size operator, like this:

const T = async () => {
  try {
     //The result document won't be found, if array_field have more then 30 elements.
     let result = User.findOneAndUpdate({$expr: {$lte: [{$size: "$array_field"}, 30]}});
     if (!result) {
        //No such documents
     } else {
       //you could add as much elements as you want via for loop for example
       result.array_field.addToSet(element)
       //also you could check your array_field.length after it, before saving
       await result.save()
     }
  } catch (e) {
    console.error(e)
  }
}

So if you array_field is less then 30 elements, it would be found, and so will be updated. You could also combine $size with comparison operators: $gte / $lte

$addToSet guarantees you, that you updated array with the unique values. So you could check it once more (if you want it to) before saving to the collection.

Related