Home > Back-end >  Node.js: Mongoose filter data from array of ObjectIDs stored in collection
Node.js: Mongoose filter data from array of ObjectIDs stored in collection

Time:11-11

I am trying to search using node.js, ejs and mongoose. All the filter parameters are working perfectly but only categoryIds is not (stored as a collection of ObjectIDs in the mongodb document, referring to the respective document in categories collection), always giving me the empty record set.

For example:
If I need to find the a movie called Cosmos (see the attached screenshot) then I can easily find it with all or any filter except categories. Once I select any category, the record-set will go blank even if the I have selected the one which it belongs to.

model.js

const Model = mongoose.model('Movie', new Schema({
   ...    

   categoryIds: [{
      type: Schema.Types.ObjectId,
      trim: true,
      default: null,
      ref: 'Category',
   }],
   copyrightId: {
      type: Schema.Types.ObjectId,
      trim: true,
      default: null,
      ref: 'Copyright',
   },
   
   ...
}, {
   timestamps: true
});

Controller.js

Router.get('/', (req, res) => {
   const search = req.query;
   const conditions = (() => {
      let object = {};

      ['releaseYear', 'languageId', 'copyrightId'].forEach(filter => {
         if (search[filter] != '') {
            object[filter] = search[filter];
         }
      });

      if (typeof search.categoryIds !== 'undefined') {
         object.categoryIds = [];

         search.categoryIds.forEach(item => object.categoryIds.push(item));
      }

      if (search.keywords != '') {
         object.title = {
            $regex: search.keywords,
            $options: 'i'
         };
      }

      return object;
   })();
   const count = await Model.count(conditions);
   const items = await Model.find(conditions, {
      __v: false,
      imdb: false,
      trailer: false,
      createdAt: false,
      updatedAt: false,
   }).sort({
      status: -1,
      releaseYear: -1,
      title: 1
   })
   .populate('languageId', ['title'])
   .populate('copyrightId', ['title'])
   .populate('categoryIds', ['title'])
   .skip(serialNumber)
   .limit(perPage);

   ...
});

All the fields in the search form

{
  categoryIds: [
    '6332a8a2a336e8dd78e3fe30',
    '6332a899a336e8dd78e3fe2e',
    '6332a87ba336e8dd78e3fe2c',
    '634574ab339b1a6b09c1e144'
  ],
  languageId: '',
  copyrightId: '',
  releaseYear: '',
  rating: '',
  seen: '',
  status: '',
  keywords: '',
  submit: 'search' // button
}

filtered search parameters

{
  categoryIds: [
    '6332a8a2a336e8dd78e3fe30',
    '6332a899a336e8dd78e3fe2e',
    '6332a87ba336e8dd78e3fe2c',
    '634574ab339b1a6b09c1e144'
  ]
}

Here is the screenshot of mongodb document. enter image description here

CodePudding user response:

The filter should contain all categoryIds and in the same order to match the document. It's not quite clear from the question if it is the intended functionality. If not, most popular usecases are documented at https://www.mongodb.com/docs/manual/tutorial/query-arrays/

I don't recall how mongoose handles types when you query with array function like $all, so you may need to convert string IDs to ObjectIDs manually, e.g.:

search.categoryIds.forEach(item => object.categoryIds.push(mongoose.Types.ObjectId(item)));

CodePudding user response:

...

if (typeof search.categoryIds !== 'undefined') {
   object.categoryIds = {
      $in: []
   };

   search.categoryIds.forEach(item => object.categoryIds.$in.push(
      mongoose.Types.ObjectId(item))
   );
}

console.log(object);

return object;

The is the final filter object

{
   categoryIds: {
      '$in': [
          new ObjectId("6332a87ba336e8dd78e3fe2c"),
          new ObjectId("634669f4a2725131e80d99f1")
      ]
   }
}

Now, all the filters are working perfectly.

Thank you everyone.

  • Related