Lesson Wednesday

Sweet. Now we have learned the basics of sorting, let's get down to business and sort some Reviews. We'll also write some tests to make sure we are doing this properly.

Implementing Comparable

Skip over to your Review class and implement the Comparable interface.

src/main/java/models/Review.java
public class Review implements Comparable<Review> {
}

That was easy. Now we need to add the required methods for this interface. Again, we can let the IDE do some of the work for us. Right click below the constructor and choose Implement Methods from the menu.

This will generate some boilerplate code for us:

@Override
public int compareTo(Review o) {
    return 0;
}

Remember, because we are implementing an interface, we cannot change a.) the method signature b.) the return type c.) the name of the method.

But we can and have to change the internal logic. Here's what I changed:

src/main/java/models/Review.java
@Override
public int compareTo(Review reviewObject) {
    if (this.createdat < reviewObject.createdat)
    {
        return -1; //this object was made earlier than the second object.
    }
    else if (this.createdat > reviewObject.createdat){ //this object was made later than the second object
        return 1;
    }
    else {
        return 0; //they were made at the same time, which is very unlikely, but mathematically not impossible.
    }
}

Make sure you understand what is happening here before you proceed.

Sorting the data

Great. We wrote some code that should tell us whether or not a Review is older or newer than a second one. That's helpful. But this isn't enough: We actually need to sort our list of reviews by date. We'll take care of this in our Sql2oReviewDao, which means we have to mention our special sorting method in our Interface.

Pop over to your ReviewDao Interface and add the following:

src/main/java/dao/ReviewDao.java
    List<Review> getAllReviewsByRestaurantSortedNewestToOldest(int restaurantId);

Let's write some basic implemtation code, then write some tests, then make the code pass.

src/main/java/dao/Sql2oReviewDao.java
@Override
public List<Review> getAllReviewsByRestaurantSortedNewestToOldest(int restaurantId) {
    List<Review> unsortedReviews = getAllReviewsByRestaurant(restaurantId); //calling other method!
    List<Review> sortedReviews = unsortedReviews;

    return sortedReviews;
}

OK, there is no way this should return the list in the correct order. Let's verify we don't have a false pass by writing a test.

Testing with time

While, strictly speaking, it is enough for our createdat fields to be just a millisecond different, this context lends itself to learning some extra testing tricks. Let's create some reviews that are staggered a short time after each other, so we can be sure that our objects are getting sorted correctly.

This is how we would go about this.

src/test/java/dao/Sql2oReviewTest.java
@Test
public void reviewsAreReturnedInCorrectOrder() throws Exception {
    Restaurant testRestaurant = setupRestaurant();
    restaurantDao.add(testRestaurant);
    Review testReview = new Review("Captain Kirk", 3, "foodcoma!", testRestaurant.getId());
    reviewDao.add(testReview);
    try {
        Thread.sleep(2000);
    }
    catch (InterruptedException ex){
        ex.printStackTrace();
    }

    Review testSecondReview = new Review("Mr Spock", 1, "passable", testRestaurant.getId());
    reviewDao.add(testSecondReview);

    assertEquals("passable", reviewDao.getAllReviewsByRestaurantSortedNewestToOldest(testRestaurant.getId()).get(0).getContent());
}

What on earth are we getting up to here? Is this a sewing class?

A very short introduction to threads

Every action our applications perform involve calculations. Whether we are looping through an array, sending a request, or instantiating an object, we rely on the machine's CPU to accurately perform calculations - writing something into memory, adding numbers correctly, executing a function call, etc etc.

In Java (and many other languages) operations run on so called threads. A thread is a single sequence of operations that run once-at-a-time.

It is possible to run code on separate threads (meaning that operations are carried out simultaneously) but in many cases, only one thread is used by default. When an operation is performed, the thread is occupied or "blocked". How long the thread is occupied for can depend on the processing power of the CPU, as calculations generally run as fast as the CPU is able to go. If we need to pause our application for some reason, an easy way to do this is to sleep the current thread - artificially "blocking" it for a preset amount of time.

When a thread is sleeping, the app will be unresponsive. Be careful when putting threads to sleep. Remember that 1000 milliseconds = 1 second. It can be easy to freeze the application if not used carefully. Because messing with threads can go wrong, we need to wrap our code in a try/catch.

Pausing our test by sleeping our threads

As mentioned above, we want to stagger the flow of our tests in order to be sure that our reviews have distinctly different times. We can achieve this by faking a short delay in between our reviews by sleeping the thread for a few seconds.

Go ahead and re-run your test. It should still fail, but you should see your test delay between saving reviews.

Creating a Sorted List

Cool - let's get some traction on sorting our Reviews, then we can make our test pass.

Let's plan our approach a little bit before we get stuck in. In order to write this method, we will have to:

  1. retrieve the unsorted reviews. We can use our existing method to do that if we want.
  2. loop through the reviews, and
  3. use our compareTo method to check and see which Review was older or newer.
  4. switch the order based on the results of comparing them. If the Review is older (was created earlier) it should move behind the current item.

Try and see if you can write this method on your own. If you can't get your method to work, check the cheat sheet for a possible solution. It has some hidden trickyness to it, so be sure to test thoroughly. Once it works for two Reviews, add in a few more test Reviews to make sure your method works with a larger dataset.

Here's a longer test you can use if you like:

src/test/java/dao/Sql2oReviewTest.java

@Test
    public void reviewsAreReturnedInCorrectOrder() throws Exception {
        Restaurant testRestaurant = setupRestaurant();
        restaurantDao.add(testRestaurant);
        Review testReview = new Review("Captain Kirk", 3, "foodcoma!", testRestaurant.getId());
        reviewDao.add(testReview);
        try {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex){
            ex.printStackTrace();
        }

        Review testSecondReview = new Review("Mr. Spock", 1, "passable", testRestaurant.getId());
        reviewDao.add(testSecondReview);

        try {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex){
            ex.printStackTrace();
        }

        Review testThirdReview = new Review("Scotty", 4, "bloody good grub!", testRestaurant.getId());
        reviewDao.add(testThirdReview);

        try {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex){
            ex.printStackTrace();
        }

        Review testFourthReview = new Review("Mr. Sulu", 2, "I prefer home cooking", testRestaurant.getId());
        reviewDao.add(testFourthReview);

        assertEquals(4, reviewDao.getAllReviewsByRestaurant(testRestaurant.getId()).size()); //it is important we verify that the list is the same size.
        assertEquals("I prefer home cooking", reviewDao.getAllReviewsByRestaurantSortedNewestToOldest(testRestaurant.getId()).get(0).getContent());
    }

Creating sorted reviews with our frontend

After our tests pass, we need to add add another route to retrieve sorted Reviews, but we also need to make a small change to our route for creating new Reviews because of some special circumstances with our createdat property.

src/main/java/App.java
post("/restaurants/:restaurantId/reviews/new", "application/json", (req, res) -> {
    int restaurantId = Integer.parseInt(req.params("restaurantId"));
    Review review = gson.fromJson(req.body(), Review.class);
    review.setCreatedat(); //I am new!
    review.setFormattedCreatedAt();
    review.setRestaurantId(restaurantId);
    reviewDao.add(review);
    res.status(201);
    return gson.toJson(review);
});

As you can see above, we have to use the explicit setter we created in an earlier lesson. You may have recalled that I mentioned a process called reflection, which allows us to manipulate objects in really specific ways. (We will not be discussing reflection further. For some perspective, give this thread a read through). Treehouse also has some nice videos on this topic) In short, reflection allows code to inspect an object's fields and methods. We can also use it to build new objects.

Gson uses reflection to create new object instances instead of calling a constructor. As a result, the code this.createdat = System.currentTimeMillis(); in our Review constructor doesn't run when we ask Gson to make a new object from JSON data. Therefore, a simple fix for us is to call our designated setter instead so that the data is written into the object correctly after the fact.

We can test that we Reviews added via our frontend are getting added in correctly by adding a route to retrieve them:

get("/restaurants/:id/sortedReviews", "application/json", (req, res) -> { //// TODO: 1/18/18 generalize this route so that it can be used to return either sorted reviews or unsorted ones.
    int restaurantId = Integer.parseInt(req.params("id"));
    Restaurant restaurantToFind = restaurantDao.findById(restaurantId);
    List<Review> allReviews;
    if (restaurantToFind == null){
        throw new ApiException(404, String.format("No restaurant with the id: \"%s\" exists", req.params("id")));
    }
    allReviews = reviewDao.getAllReviewsByRestaurantSortedNewestToOldest(restaurantId);
    return gson.toJson(allReviews);
});

Go ahead and test this out with Postman to verify that both of our route work. If you need to, check the cheat sheet or the example repo, but try and do as much of this as you can on your own.

Example GitHub Repo for Jadle Code at this stage

Here is one way you can solve the sorting problem. How could you refactor this method?

You might also explore ternary operators if you have not yet made their acquaintance.

src/main/java/dao/Sql2oDaoReview.java
    @Override
    public List<Review> getAllReviewsByRestaurantSortedNewestToOldest(int restaurantId) {
      List<Review> unsortedReviews = getAllReviewsByRestaurant(restaurantId);
      List<Review> sortedReviews = new ArrayList<>();
        int i = 1;
        for (Review review : unsortedReviews){
            int comparisonResult;
            if (i == unsortedReviews.size()) { //we need to do some funky business here to avoid an arrayindex exception and handle the last element properly
                if (review.compareTo(unsortedReviews.get(i-1)) == -1){
                sortedReviews.add(0, unsortedReviews.get(i-1));
                }
                break;
            }

            else {
                if (review.compareTo(unsortedReviews.get(i)) == -1) { //first object was made earlier than second object
                    sortedReviews.add(0, unsortedReviews.get(i));
                    i++;
                } else if (review.compareTo(unsortedReviews.get(i)) == 0) {//probably should have a tie breaker here as they are the same.
                    sortedReviews.add(0, unsortedReviews.get(i));
                    i++;
                } else {
                    sortedReviews.add(0, unsortedReviews.get(i)); //push the first object to the list as it is newer than the second object.
                    i++;
                }
            }
        }
    return sortedReviews;
    }