Julius Guevarra
Julius Guevarra

Reputation: 888

MongoDB Aggregate is not matching specific field

I'm new to Aggregation in MongoDB and I'm trying to understand the concepts of it by making examples.

I'm trying to paginate my subdocuments using aggregation but the returned document is always the overall values of all document's specific field.

I want to paginate my following field which contains an array of Object IDs.

I have this User Schema:

const UserSchema = new mongoose.Schema({
    username: {
        type: String,
        unique: true,
        required: true
    },
    firstname: String,
    lastname: String,
    following: [{
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    }],
    ...
}, { timestamps: true, toJSON: { virtuals: true }, toObject: { getters: true, virtuals: true } });

Without aggregation, I am able to paginate following, I have this route which gets the user's post by their username

router.get(
    '/v1/:username/following',
    isAuthenticated,
    async (req, res, next) => {
        try {
            const { username } = req.params;
            const { offset: off } = req.query;

            let offset = 0;
            if (typeof off !== undefined && !isNaN(off)) offset = parseInt(off);

            const limit = 2;
            const skip = offset * limit;

            const user = await User
                .findOne({ username })
                .populate({
                    path: 'following',
                    select: 'profilePicture username fullname',
                    options: {
                        skip,
                        limit,
                    }
                })

          
            res.status(200).send(user.following);
        } catch (e) {
            console.log(e);
            res.status(500).send(e)
        }
    }
);

And my pagination version using aggregate:

const following = await User.aggregate([
                {
                    $match: { username }
                },
                {
                    $lookup: {
                        'from': User.collection.name,
                        'let': { 'following': '$following' },
                        'pipeline': [
                            {
                                $project: {
                                    'fullname': 1,
                                    'username': 1,
                                    'profilePicture': 1
                                }
                            }
                        ],
                        'as': 'following'
                    },
                }, {
                    $project: {
                        '_id': 0,
                        'following': {
                            $slice: ['$following', skip, limit]
                        }
                    }
                }
            ]);

Suppose I have this documents:

[
                {
                    _id: '5fdgffdgfdgdsfsdfsf',
                    username: 'gagi',
                    following: []
                },
                {
                    _id: '5fgjhkljvlkdsjfsldkf',
                    username: 'kuku',
                    following: []
                },
                {
                    _id: '76jghkdfhasjhfsdkf',
                    username: 'john',
                    following: ['5fdgffdgfdgdsfsdfsf', '5fgjhkljvlkdsjfsldkf']
                },
            ]

And when I test my route for user john: /john/following, everything is fine but when I test for different user which doesn't have any following: /gagi/following, the returned result is the same as john's following which aggregate doesn't seem to match user by username.

/john/following | following: 2

/kuku/following | following: 0

Aggregate result:

[
  {
    _id: '5fdgffdgfdgdsfsdfsf',
    username: 'kuku',
    ...
  }, 
  {
    _id: '5fgjhkljvlkdsjfsldkf',
    username: 'gagi',
    ...
  }
]

I expect /kuku/following to return an empty array [] but the result is same as john's. Actually, all username I test return the same result.

I'm thinking that there must be wrong with my implementation since I've only started exploring aggregation.

Upvotes: 0

Views: 510

Answers (1)

Joe
Joe

Reputation: 28316

Mongoose uses a DBRef to be able to populate the field after it has been retrieved.
DBRefs are only handled on the client side, MongoDB aggregation does not have any operators for handling those.

The reason that aggregation pipeline is returning all of the users is the lookup's pipeline does not have a match stage, so all of the documents in the collection are selected and included in the lookup.

The sample document there is showing an array of strings instead of DBRefs, which wouldn't work with populate.

Essentially, you must decide whether you want to use aggregation or populate to handle the join.

For populate, use the ref as shown in that sample schema.

For aggregate, store an array of ObjectId so you can use lookup to link with the _id field.

Upvotes: 1

Related Questions