Instructor: 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.