Search code examples
mongodbmongooseaggregation-framework

Mongoose aggregate - $filter not removing matching elements


I have the following query that should update the privacyStatus of a user to some value, and also remove all notifications (an array of ObjectIds) which have a type property equal to the value: follow_request.

 User.aggregate([
            {
                $match: { _id: new mongoose.Types.ObjectId(req.user._id) },
            },
            {
                $lookup: {
                    from: "Notification",
                    localField: "notifications",
                    foreignField: "_id",
                    as: "notifications",
                },
            },
            {
                $set: {
                    privacyStatus: value,
                    notifications: {
                        $filter: {
                            input: "$notifications",
                            cond: {
                                $ne: ["$$this.type", "follow_request"],
                            },
                        },
                    },
                },
            },
    
            {
                $merge: {
                    into: "users",
                },
            },
        ]).then((result) => {
            res.send("Done")
        })

The privacyStatus is successfully updated in the database, therefore I know the $match stage is working correctly, however the notifications array doesn't update at all even if there are notifications that match the filter.

My User model:

{
  privacyStatus: {type: Boolean},
  notifications: [type: mongoose.Schema.Types.ObjectId,ref: "Notification",]
}

Notification Model:

{
   type: {type: String, default: "follow_request"}
}

Why aren't my notifications that have a type of follow_request being removed from the users notification array? Thanks

Sample documents: user document:

{
   _id: ObjectId('66324110da2f0f7175ca1949'),
   username: "sam",
   notifications: [
      ObjectId('663c1c0bd0sa68fae7cf6ad2')
   ]
}

Notification document:

{
   _id: ObjectId('663c1c0bd0sa68fae7cf6ad2'),
   type: "follow_request"
}

Solution

  • To get your aggregate to merge the results back intot he users collection you need to do two things:

    1. Change the collection used in the $lookup from Notification to notifications.
    2. Recognise that your User.notifications field is an array of ObjectIds. When your $filter returns it's results, you won't be able $merge them back into your User.notifications array because they will be full Notification documents. That can be fixed with a $map stage to step over each filtered Notification document and only return their _id. Then that will match your schema.

    The finished aggregation might look like this:

    User.aggregate([
      {
        $match: {
           _id: new mongoose.Types.ObjectId(req.user._id)
        }
      },
      {
        $lookup: {
          from: "notifications", //< change to this
          localField: "notifications",
          foreignField: "_id",
          as: "notifications"
        }
      },
      {
        $set: {
          privacyStatus: value,
          notifications: {
            $map: {
              input: {
                $filter: {
                  input: "$notifications",
                  cond: {
                    $ne: [
                      "$$this.type",
                      "follow_request"
                    ]
                  }
                }
              },
              as: "n",
              in: "$$n._id"
            }
          }
        }
      },
      {
        $merge: {
          into: "users"
        }
      }
    ]).then((result) => {
       res.send("Done")
    })
    

    See HERE for a working example.