‫So, this is part two
‫of calculating the review statistics.
‫This time, for when a review is updated or deleted.
‫And this part is actually a bit harder
‫because, keep in mind that a review
‫is updated or deleted using
‫findByIdAndUpdate
‫or also findByIdAndDelete, right?
‫So for these, we actually do not have document middleware,
‫but only query middleware, okay.
‫And so in the query, we actually don't have direct access
‫to the document in order to then do something similar
‫to this, okay.
‫Because, remember, we need access to the current review,
‫so that from there, we can extract the tour ID,
‫and then calculate the statistics from there, right,
‫but again, for these hooks here,
‫we only have query middleware, okay.
‫But let me now show you a nice trick
‫to actually go around this limitation.
‫So, we're going to implement a pre-middleware
‫for these hooks, for these events basically.
‫So pre, and then again I'm going to use
‫a regular expression for a string starting
‫with findOneAnd and that's it.
‫And so this one is then going to work
‫for findOneAndUpdate, and findOneAndDelete
‫because, remember that behind the scenes,
‫findByIdAndUpdate is only just a shorthand
‫for findOneAndUpdate with the current ID, right.
‫So here, we actually need to use the findOneAndDelete
‫and findOneAndUpdate middleware hooks, all right.
‫So, function, and it gets the next keyword
‫because it's pre-middleware.
‫So, remember that the goal is to get access
‫to the current review document, okay,
‫but here the, this keyword is the current query.
‫Now, how are we going to go around this?
‫Well, we can basically execute a query,
‫and then that will give us the document
‫that's currently being processed.
‫So in order to do that, we can use findOne.
‫And that's it.
‫So then all we need to do is await this query
‫and then save it somewhere.
‫So let's call it r, which is gonna stand for review, okay.
‫Then save it as async.
‫And, that's it.
‫And just to make sure that this works here,
‫let's for now just log this to the console, okay.
‫So without doing any calculations,
‫all we're really interested in
‫is to see if this nice trick here works.
‫So basically, the trick of going around
‫that in a query midddleware,
‫we only have access to the query.
‫So again, we need to get access to the document,
‫and so we basically execute this query
‫by using findOne.
‫All right, so let's update a review,
‫and so let's actually update the last one that we did,
‫all right, and the rating,
‫now let's actually set it to four.
‫So it was five before
‫and so now we are changing it to four.
‫So it sent that,
‫and here is the review.
‫Now of course, the rating is still set to five
‫at this point, because this findOne here
‫really gets the document from the database,
‫and so at this point of time, in pre,
‫it still didn't persist any changes to the database,
‫and so it was five before,
‫and so now it's still gonna be five.
‫But that doesn't really matter here
‫because all we are interested in is this ID.
‫Actually, this tour ID, right,
‫because that is what we're gonna need
‫in order to calculate the average ratings.
‫Okay, and so now, let's actually use that function.
‫Okay, now, let's think about this
‫because if we were to use this calcAverageRatings function
‫at this point in time,
‫then we would calculate the statistics
‫using the non-updated data, okay.
‫And so that's the exact same reason why up here,
‫we also needed to use post and not pre,
‫okay, because only after the document is already saved
‫to the database it makes sense
‫to then calculate the ratings.
‫And so here, it's the exact same thing,
‫with the big difference that we cannot simply change
‫this pre to post.
‫So we cannot do that because at this point in time
‫we no longer have access to the query
‫because the query has already executed, right,
‫and so without the query,
‫we cannot save the review document,
‫and we can then not run this function.
‫So, this is really confusing, I understand,
‫but I really decided to create this lecture in this way
‫because, well, it's really the only solution
‫around this problem, and it's a really great exercise
‫for you to basically understand this Hello Experience.
‫So, the solution for this is to now use post,
‫so, reviewSchema, then basically the same,
‫so let's just, actually let's just copy all of this,
‫make our lives a bit easier,
‫get rid of this, and get rid of the next.
‫Here, we also need to call next, okay,
‫but then we do it post, okay.
‫And so now this point in time,
‫so after the query has already finished,
‫and so therefore the review has been updated,
‫this is a perfect point in time
‫where we can then call this function.
‫So, calculate average ratings.
‫But where do we now get the tour ID from?
‫Well, we're gonna have to use a trick
‫which is basically to pass data from the pre-middleware
‫to the post middleware.
‫And so instead of saving this document to a simple variable,
‫we're gonna save it to this.r.
‫So basically, we create a property on this variable.
‫Okay, and so now here, we still have access to that.
‫And so now, we can say this.r, which remember is the review,
‫and then .tour.
‫So, this is again quite confusing,
‫but again also quite fun once you understand
‫how this really works, okay.
‫Now, again, we need something like this here
‫in order to actually call this function here
‫because remember that this in fact
‫is a static method, and so we need to call it on the model.
‫Now where is this model in this case?
‫Well, it's at this.r,
‫which is in this case, equivalent
‫to this this here in this middleware,
‫.constructor.calcAverageRatings.
‫Woo, that looks quite overwhelming, doesn't it?
‫Of course, we have to await it,
‫so that's why we declared this as async, okay.
‫So, again, we basically used this way here
‫of passing the data from the pre-middleware
‫to the post middleware, and so then here
‫we retrieved the review document from this variable.
‫Okay, and again, we did have to do it in this way
‫because at this point in time here,
‫the query was already executed,
‫and so we could not do this here.
‫And let's actually write that down; does NOT work here,
‫query has already executed.
‫Great, and with this,
‫we should actually now be ready to test this.
‫So let's go ahead and update this one here once more,
‫and set it to a rating of one, all right.
‫Let's now let's take a look here,
‫until you see that the number of ratings is still five,
‫but the average is now only three,
‫and now the question is if this really updated
‫also on the tour,
‫and, yep, indeed, it did.
‫So, five ratings with an average of three.
‫Great, let's now update another one.
‫So, in order to that, let's get all our reviews,
‫and actually we do not want all of them,
‫but only the last one.
‫And here I still I have this filter,
‫so I should get rid of that,
‫so we get all 65 results, so all of these reviews,
‫but we only want the last ones,
‫so the ones that we just created.
‫So let's update...
‫I'm not sure, because actually they're not in order here.
‫Let's, okay, so here we have the most recent ones.
‫So let me now update this one here, to let's say, four,
‫just as a final test, and so that should
‫bring the average up a little bit.
‫And indeed it did.
‫So, 3.6 now, and of course, the number is still five
‫because you didn't add a new review.
‫So it works great on update,
‫let's now actually also test it with deleting.
‫So let's go ahead and delete the review that we just updated
‫and so now we should be down to only four,
‫and indeed, here we are.
‫So now, only four.
‫And let's actually delete all of them.
‫So, this one is next.
‫Ah, here we are.
‫Then also this one.
‫So, now we should be down to three only
‫or, actually, we only just have two left
‫with an average of four.
‫So let's see which one we have still left.
‫So it's this one with three and this one with five.
‫And so that's why we have this average of four.
‫So let's get rid of this one
‫and so now our average should be five, right?
‫Or actually three
‫because the only one that's left has three.
‫And now the final test, deleting the last one,
‫just to see what's gonna happen once we do that.
‫And, we get an error here.
‫So, cannot read property nRating of undefined.
‫So that's calcAverageRatings at line number 69.
‫So, don't know what this is.
‫So, line 69, and so, it's this problem here.
‫So we're trying to read nRating of undefined.
‫So stats zero is basically undefined.
‫And that's because if there are no document
‫matching this query here basically,
‫well then we simply get back an empty array.
‫And so that's actually exactly what we have down here.
‫So this is the stats array
‫and so right now it doesn't have any results, okay,
‫so we should only execute this piece of code here
‫whenever we actually do have something in the stats array.
‫So let's do that.
‫So if stats.length is greater than zero,
‫well, then do this.
‫And if not, well, basically that means
‫that all our reviews are gone,
‫well then we basically want to go back to the default.
‫So we're gonna set it then to the quantity of zero
‫and the average of 4.5, which remember,
‫is the default when there are no reviews at all.
‫So let's quickly create a new review,
‫and it can be this,
‫all right, so we're back to having one rating,
‫and now delete it right away,
‫just to test that piece of code we just wrote,
‫just to see it here as well,
‫so one rating, average five.
‫And now when we delete it,
‫we no longer get an error.
‫And on our tour, we are back to zero
‫and a default of 4.5.
‫All right, all right, all right, perfect.
‫So let's again take just a quick second here
‫to recap what we just did.
‫So in order to be able to run this function here
‫also on update and on delete,
‫we actually need to use the query middleware
‫that Mongoose gives us for these situations.
‫Okay, so, we do not have a handy document middleware,
‫which works, for these functions,
‫but instead we need to use the query middleware,
‫and in that one, we do not directly have access
‫to the current document.
‫And so we need to go around that
‫by using this findOne here, and so basically retrieving
‫the current document from the database.
‫We then store it on the current query variable,
‫and so that's this, and by doing that,
‫we then get access to it in the post middleware.
‫And it's then only in the post middleware
‫where we actually calculate the statistics for reviews.
‫And remember that we do it this way
‫because if we did it right in this middleware function,
‫then the underlying data would not have been updated
‫at that point and so the calculated statistics
‫would not really be up to date.
‫And so that's why we used
‫this two-step process here basically.
‫Now, let's get rid of this console.log here,
‫since we're finished now, and also of these statistics.
‫So, I hope that this was fun to you
‫and not all too overwhelming.
‫I designed this exercise specifically
‫so that we could solve a real world business problem
‫by using all the tools that Mongoose gives us.
‫And so, with that, I hope that you learned a bit better
‫how to work with all these different middlewares
‫in different situations whenever needed
‫in a real world situation.
‫So, great job for finishing this quite challenging lecture
‫and for still being with me at this point of the course.
‫It's really good for me to see,
‫so I'm really happy about that.
‫But anyway, there's still some more great content coming up
‫in this section actually,
‫and so let's now move on together, right to the next video.