How can I ignore a block of middlware in my model?

So I have a model in my DB called “Review.” I’m including the code for the model below. As you can see, there’s a field in each document called “reviewLikes.” My code is structured so that one someone clicks on a “like” button for a review, a PATCH request is sent to a controller, which triggers an update the “reviewLikes” field using findByIdAndUpdate. When this happens, the new number for “reviewLikes” is the only thing being passed in (along with the ID for the review, which is used to make sure the right document gets the new “like” number).

The problem: When this all happens, there is aggregation middleware in the model that runs. The part that is causing me a headache is “reviewSchema.statics.calcAverageRatings” that is included below. As a result, the “reviewLikes” is correctly updated, however, the averages for the ship and the review count all get reset back to 3 and 0 (respectively).

So, my question is, how can I prevent this block of code from executing if I am ONLY updating the “reviewLikes” field in a document? I have tried, unsuccessfully, to put a simple “if” statement outside of the block of code. It was something like if (this.rating) { the middleware } … but that did not work. Then I was going to try passing the entire review into the function instead of just reviewLikes, but then I realized that this wouldn’t stop the middleware from running… it would probably just generate funky changes to the counts each time the “like” button is clicked.

Anyway, if you’re still with me, I appreciate you reading this. I’ve been stuck on this all weekend. Any help would be amazing. Thanks.

const mongoose = require('mongoose');

const Ship = require('./shipModel');

const dateFormat = require('dateformat');

const reviewSchema = new mongoose.Schema(

  {

    review: {

      type: String,

      required: [true, 'Review cannot be empty!'],

    },

    rating: {

      type: Number,

      min: 1,

      max: 5,

    },

    ratingDining: {

      type: Number,

      min: 1,

      max: 5,

    },

    ratingCabin: {

      type: Number,

      min: 1,

      max: 5,

    },

    ratingKids: {

      type: Number,

      min: 1,

      max: 5,

    },

    ratingValue: {

      type: Number,

      min: 1,

      max: 5,

    },

    ratingEntertainment: {

      type: Number,

      min: 1,

      max: 5,

    },

    ratingValue: {

      type: Number,

      min: 1,

      max: 5,

    },

    cabinType: {

      type: String,

    },

    cabinNumber: {

      type: String,

    },

    sailDate: {

      type: String,

    },

    createdAt: {

      type: Date,

      default: Date.now,

    },

    displayDate: {

      type: String,

    },

    reviewLikes: {

      type: Number,

      default: 0,

    },

    ship: {

      type: mongoose.Schema.ObjectId,

      ref: 'Ship',

      required: [true, 'Review must belong to a ship.'],

    },

    user: {

      type: mongoose.Schema.ObjectId,

      ref: 'User',

      required: [true, 'Review must belong to a user.'],

    },

  },

  {

    toJSON: { virtuals: true },

    toObject: { virtuals: true },

  }

);

reviewSchema.index({ ship: 1, user: 1 }, { unique: true });

//MIDDLEWARE

reviewSchema.pre(/^find/, function (next) {

  // this.populate({

  //   path: 'ship',

  //   select: 'name',

  // }).populate({

  //   path: 'user',

  //   select: 'name photo',

  // });

  this.populate({

    path: 'user',

    select: 'photo name',

  });

  next();

});

reviewSchema.pre(/^find/, function (next) {

  this.populate({

    path: 'ship',

    select: 'shipName',

  });

  next();

});

reviewSchema.pre('save', async function () {

  const newDate = dateFormat(this.createdAt, 'mmmm dS, yyyy');

  this.displayDate = newDate;

});

reviewSchema.pre('save', async function () {

  const newDate = dateFormat(this.sailDate, 'mmmm, yyyy');

  this.sailDate = newDate;

});

reviewSchema.statics.calcAverageRatings = async function (shipId) {

  const stats = await this.aggregate([

    {

      $match: { ship: shipId },

    },

    {

      $group: {

        _id: '$ship',

        nRating: { $sum: 1 },

        avgRating: { $avg: '$rating' },

        avgRatingDining: { $avg: '$ratingDining' },

        avgRatingCabin: { $avg: '$ratingCabin' },

        avgRatingKids: { $avg: '$ratingKids' },

        avgRatingValue: { $avg: '$ratingValue' },

        avgRatingEnt: { $avg: '$ratingEntertainment' },

      },

    },

  ]);

  if (stats.length > 0) {

    await Ship.findByIdAndUpdate(shipId, {

      ratingsQuantity: stats[0].nRating,

      ratingsAverage: stats[0].avgRating.toFixed(1),

      ratingsAverageDining: stats[0].avgRatingCabin.toFixed(1),

      ratingsAverageCabin: stats[0].avgRatingCabin.toFixed(1),

      ratingsAverageKids: stats[0].avgRatingKids.toFixed(1),

      ratingsAverageValue: stats[0].avgRatingValue.toFixed(1),

      ratingsAverageEnt: stats[0].avgRatingEnt.toFixed(1),

    });

  } else {

    await Ship.findByIdAndUpdate(shipId, {

      ratingsQuantity: 0,

      ratingsAverage: 3,

      ratingsAverageDining: 3,

      ratingsAverageCabin: 3,

      ratingsAverageKids: 3,

      ratingsAverageValue: 3,

      ratingsAverageEnt: 3,

    });

  }

};

reviewSchema.post('save', function () {

  //"this" points to current review

  this.constructor.calcAverageRatings(this.ship);

});

// findByIdAndUpdate

// findByIdAndDelete

reviewSchema.pre(/^findOneAnd/, async function (next) {

  this.r = await this.findOne();

  //console.log(this.r);

  next();

});

reviewSchema.post(/^findOneAnd/, async function (next) {

  await this.r.constructor.calcAverageRatings(this.r.ship);

});

const Review = mongoose.model('Review', reviewSchema);

module.exports = Review;
1 Like

Howdy Chirs! Thanks for pinging me about this question! :smiley: Hmmm - sounds like you’ve got an interesting problem on your hands! It sounds like you are trying to update your revieLikes but the middleware is acting unexpectedly so your result is being reset. Programmatically, I don’t see any issues with using a if/else statement to control what code gets executed by the middleware. Can you elaborate on why it’s not working, so I can help troubleshoot? My guess would be that the aggregation is not returning an expected result, so the if/else statement is running the expected code.

I GOT IT! Thank you, this was helpful. I was putting the if/else in the wrong spot. I forgot that “this” inside of “statics” references the actual model. however, “this” inside of pre and post middleware references the actual object. When I console.log’d “this”, I was able to pinpoint where the info to be updated was. The solution, for me, looked like this:

reviewSchema.post(/^findOneAnd/, async function (next) {

  if (this._update.$set.reviewLikes) {
    console.log('All finished');
  } else {
    await this.r.constructor.calcAverageRatings(this.r.ship);
  }
});