Alexander Kolarov
Alexander Kolarov

Reputation: 855

mongoDB - add custom field by another document field, with pagination

I have user schema and invitation schema. When user search for users I want to return array with object that have property invited.

const InvitationSchema = new Schema<IInvitation>(
    {
        sender: {
            type: Schema.Types.ObjectId,
            ref: 'user'
        },
        receiver: {
            type: Schema.Types.ObjectId,
            ref: 'user'
        }
    }
)
const IUserSchema = new Schema<IUser>(
    {
        email: {
            type: String,
        },
        invitedUsers: [
            {
                type: Schema.Types.ObjectId,
                ref: 'user'
            }
        ]
    }
)

Scenario of my problem:

  1. There are 3 users with ids - userId1, userId2, userId3
  2. User1 sends invitation to User2. So current db becomes:
user1:{
  id:userId1,
  invitedUsers:[userId2]
}

invitation1:{
  id:someId,
  sender:userId1,
  receiver:userId2
}
  1. Here User1 make a user search request to the server and receive:
users:[
  {
     id:userId2,
     invited:true
  },
  {
     id:userId3,
     invited:false
  },
]

I also want to paginated users but its not target of the current problem.

I tried some variants with aggregation $addFields but nothing works. The other way to do 10 queries more as the users per page are 10. This is bad idea of course.

Upvotes: 1

Views: 291

Answers (2)

nimrod serok
nimrod serok

Reputation: 16033

This is a nice question. One option is to do it with $lookup:

  1. The first 4 steps here are regarding your pagination, returning only the number of documents you need, after sorting, skipping, limiting and remove the user who request the search.
  2. Then we use the $lookup` to add the details of the requestingUserId, including the list of invited user ids on each one of our selected documents.
  3. Now all is left is to check for each document if its own id is included at the invitedUsers we got from the requesting user.
db.users.aggregate([
  {$match: {id: {$ne: requestingUserId}}},
  {$sort: {id: 1}},
  {$skip: 1},
  {$limit: 2},
  {$addFields: {invitedBy: requestingUserId }},
  {
    $lookup: {
      from: "users",
      localField: "invitedBy",
      foreignField: "id",
      as: "invited"
    }
  },
  {$set: {invited: {$first: "$invited"}}},
  {
    $project: {
      invited: {$cond: [{$in: ["$id", "$invited.invitedUsers"]}, true, false] },
      id: 1, _id: 0
    }
  }
])

See how it works on the playground example

You can also improve the performance by using the $lookup pipeline to bring only the part you need:

db.users.aggregate([
  {$match: {id: {$ne: requestingUserId}}},
  {$sort: {id: 1}},
  {$skip: 1},
  {$limit: 2},
  {
    $lookup: {
      from: "users",
      let: {myId: "$id"},
      pipeline: [
        {$match: {id: "userId1"}},
        {$project: {res: {$cond: [{$in: ["$$myId", "$invitedUsers"]}, true, false]}}}
      ],
      as: "invited"
    }
  },
  {$project: {invited: {$first: "$invited.res"}, id: 1, _id: 0}}
])

See how it works on the playground example - lookup-pipeline

Upvotes: 1

Usama Masood
Usama Masood

Reputation: 141

If you want to check if the invitedUsers array has some length, then return true, otherwise false, then this could be done with aggregate

User.aggregate([
  {
    $project: {
      _id: 1,
      invitedUsers: {
        $cond: [
          { $gt: [{ $size:"$invitedUsers" }, 0] },
          true,
          false,
        ],
      }
    }
  }
])

explore aggregate here

Upvotes: 0

Related Questions