Spotted a bug? Have a great idea? Help us make google.dev great!

Goals

In this codelab, you'll enhance a restaurant recommendation web app powered by Cloud Firestore.

What you'll learn

  • Write data using transactions.

What you'll need

This codelab builds on the previous codelabs in this playlist. If you have not completed the previous codelabs, please do so before continuing here.

In this section, you'll add the ability for users to submit reviews of restaurants. So far, all of the write operations in this codelab series have been atomic and relatively simple. If any of the write operations returned an error, the application could just prompt the user to retry or your app could retry the write operation automatically.

Because your app might have many users who want to add a rating for a restaurant, you'll need to coordinate multiple reads and writes. First, the review itself has to be submitted; then the restaurant's rating count and average rating need to be updated. If one of these write operations fails but the other succeeds, then the system could be left in an inconsistent state in which the data in one part of the database doesn't match the data in another.

Fortunately, Cloud Firestore provides transaction functionality that allows multiple reads and writes in a single atomic operation, ensuring that your data remains consistent.

  1. Go back to your file scripts/FriendlyEats.Data.js.
  2. Find the function FriendlyEats.prototype.addRating.
  3. Replace the entire function with the following code:

FriendlyEats.Data.js

FriendlyEats.prototype.addRating = function(restaurantID, rating) {
  var collection = firebase.firestore().collection('restaurants');
  var document = collection.doc(restaurantID);
  var newRatingDocument = document.collection('ratings').doc();

  return firebase.firestore().runTransaction(function(transaction) {
    return transaction.get(document).then(function(doc) {
      var data = doc.data();

      var newAverage =
          (data.numRatings * data.avgRating + rating.rating) /
          (data.numRatings + 1);

      transaction.update(document, {
        numRatings: data.numRatings + 1,
        avgRating: newAverage
      });
      return transaction.set(newRatingDocument, rating);
    });
  });
};

In the block above, a transaction is triggered to update the numeric values of averageRating and ratingCount in the restaurant document. At the same time, the new rating is added to the ratings subcollection.