losik123
losik123

Reputation: 590

Mongoose filter nested array in pre find query

I have the following issue. I have some comments that are soft-deletable. So they have a flag is_deleted and it is set to true when a record is deleted.

My comments aren't an independent model, but they are a nested array in another model (simplified model):

let CommentSchema = new Schema({
    text: {
        type: String,
        required: true
    },
    modified_at: {
        type: Date,
        default: null
    },
    created_at: {
        type: Date,
        default: Date.now
    },
    is_deleted: {
        type: Boolean,
        default: false
    },
});

let BookSchema = new Schema({
    ...
    comments: [CommentSchema],
    ...
});

Now when I get all my Books with Books.find({}, (err, books) => {}) I wanted to filter out the deleted comments in:

BookSchema.pre('find', function() {
    this.aggregate(
    {},
    { $unwind: '$comments'},
    { $match: {'comments.is_deleted': false}})
}); 

But it does not work. Any idea how to write the query, so that it only return the non-deleted nested comments without creating an independent Comment collection?

EDIT: I didn't mention it's an endpoint where I access only one book object. The resource url is like this: /books/{bookId}/comments. But also nice to have if it would work when getting all book objects.

Upvotes: 0

Views: 1507

Answers (1)

Orelsanpls
Orelsanpls

Reputation: 23545

You can use the positional $ operator and filter directly from find. As greatly explaned by @Blakes Seven Here

BookSchema.find({
 'comments.is_deleted': false,
}, {
 'comments.$': 1,
});

EDIT:

As you said the $ operator only return one element, here is what the document says :

The positional $ operator limits the contents of an from the query results to contain only the first element matching the query document

There is two solution to your problem:

  1. Make an aggregate, which is usually slow to get executed by database
  2. Get the books and filter the comments using loops

BookSchema.find({
   'comments.is_deleted': false,
}).map(x => Object.assign(x, {
   comments: x.comments.filter(y => !y.is_deleted),
}));

The find get all books that have a non-deleted comment.

The map loop on each book.

We then remove the comments marked as deleted

Upvotes: 1

Related Questions