Movie Lists and Pagination

In this challenge, you will implement the remaining methods in the MovieService for retrieving a list of movies:

  • getByGenre - should return a paginated list of movies that are listed in a particular Genre

  • getForActor - should return a paginated list of movies that a particular Person has acted in

  • getForDirector - should return a paginated list of movies that a particular Person has directed

These methods are very similar to the all() method which returns a full list, but with a different pattern in the MATCH clause.

Because we also want to support sorting and pagination, we need to pass the values we get from the http-request in the route as $skip and $limit from the request as parameters and string-replace the field to order for sort and order direction (as those cannot be parameterized).
java
neoflix/services/MovieService.java
public List<Map<String,Object>> all(Params params, String userId) {
    // Open a new session
    try (var session = this.driver.session()) {
        // Execute a query in a new Read Transaction
        var movies = session.executeRead(tx -> {
            // Get an array of IDs for the User's favorite movies
            var favorites =  getUserFavorites(tx, userId);

            // Retrieve a list of movies with the
            // favorite flag appened to the movie's properties
            Params.Sort sort = params.sort(Params.Sort.title);
            String query = String.format("""
                MATCH (m:Movie)
                WHERE m.`%s` IS NOT NULL
                RETURN m {
                  .*,
                  favorite: m.tmdbId IN $favorites
                } AS movie
                ORDER BY m.`%s` %s
                SKIP $skip
                LIMIT $limit
                """, sort, sort, params.order());
            var res= tx.run(query, Values.parameters( "skip", params.skip(), "limit", params.limit(), "favorites",favorites));
            // Get a list of Movies from the Result
            return res.list(row -> row.get("movie").asMap());
        });

        return movies;
    }
}

For each subtask, we will provide you with the pattern required to run in the Cypher statement, plus any additional parameters that are required for the query to run.

All you need to do is take the code from the all() method and modify the pattern in the first line of the Cypher statement in the first argument of the tx.run() call, then if necessary add the additional parameters to the second argument.

If you get stuck at any point, you can click to reveal the completed solution or skip to the bottom for instructions on how to checkout the branch with the working solution.

getByGenre

The getByGenre() method returns a paginated list of movies that are listed within a certain genre.

java
neoflix/services/MovieService.java
public List<Map<String,Object>> byGenre(String name, Params params, String userId) {
    // TODO: Get Movies in a Genre
    // MATCH (m:Movie)-[:IN_GENRE]->(:Genre {name: $name})

    return AppUtils.process(comedyMovies, params);
}

Update the getByGenre() method to use the following Cypher Pattern:

cypher
MATCH (m:Movie)-[:IN_GENRE]->(:Genre {name: $name})

The tx.run() call will need to include the name parameter, which represents the name of the genre.

Click to reveal the completed getByGenre() method

Find the getByGenre() method in src/main/java/neoflix/services/MovieService.java and modify the function to find a list of movies by genre.

java
neoflix/services/MovieService.java
public List<Map<String,Object>> byGenre(String name, Params params, String userId) {
    // Get Movies in a Genre
    // MATCH (m:Movie)-[:IN_GENRE]->(:Genre {name: $name})

    // Open a new session and close at the end
    try (var session = driver.session()) {
        // Execute a query in a new Read Transaction
        return session.executeRead((tx) -> {
            // Get an array of IDs for the User's favorite movies
            var favorites = getUserFavorites(tx, userId);

            // Retrieve a list of movies with the
            // favorite flag append to the movie's properties
            var result = tx.run(
                    String.format("""
                              MATCH (m:Movie)-[:IN_GENRE]->(:Genre {name: $name})
                              WHERE m.`%s` IS NOT NULL
                              RETURN m {
                                .*,
                                  favorite: m.tmdbId IN $favorites
                              } AS movie
                              ORDER BY m.`%s` %s
                              SKIP $skip
                              LIMIT $limit
                            """, params.sort(), params.sort(), params.order()),
                    Values.parameters("skip", params.skip(), "limit", params.limit(),
                            "favorites", favorites, "name", name));
            var movies = result.list(row -> row.get("movie").asMap());
            return movies;
        });
    }
}

getForActor

The getForActor() method returns a paginated list of movies that a person has acted in.

java
neoflix/services/MovieService.java
public List<Map<String,Object>> getForActor(String actorId, Params params,String userId) {
    // TODO: Get Movies acted in by a Person
    // MATCH (:Person {tmdbId: $id})-[:ACTED_IN]->(m:Movie)

    return AppUtils.process(actedInTomHanks, params);
}

Find the getForActor() method in src/main/java/neoflix/services/MovieService.java and modify the function to find a list of movies by actor.

The pattern required in the MATCH clause will be:

cypher
MATCH (:Person {tmdbId: $id})-[:ACTED_IN]->(m:Movie)

You will have to include the additional parameter id - The tmdbId property relating to the actor.

Click to reveal the completed getForActor() method
java
neoflix/services/MovieService.java
public List<Map<String,Object>> getForActor(String actorId, Params params,String userId) {
    // Get Movies acted in by a Person
    // MATCH (:Person {tmdbId: $id})-[:ACTED_IN]->(m:Movie)

    // Open a new session
    try (var session = this.driver.session()) {

        // Execute a query in a new Read Transaction
        var movies = session.executeRead(tx -> {
            // Get an array of IDs for the User's favorite movies
            var favorites = getUserFavorites(tx, userId);
            var sort = params.sort(Params.Sort.title);

            // Retrieve a list of movies with the
            // favorite flag appended to the movie's properties
            String query = String.format("""
                      MATCH (:Person {tmdbId: $id})-[:ACTED_IN]->(m:Movie)
                      WHERE m.`%s` IS NOT NULL
                      RETURN m {
                        .*,
                          favorite: m.tmdbId IN $favorites
                      } AS movie
                      ORDER BY m.`%s` %s
                      SKIP $skip
                      LIMIT $limit
                    """, sort, sort, params.order());
            var res = tx.run(query, Values.parameters("skip", params.skip(), "limit", params.limit(), "favorites", favorites, "id", actorId));
            // Get a list of Movies from the Result
            return res.list(row -> row.get("movie").asMap());
        });
        return movies;
    }
}

getForDirector

The getForDirector() method returns a paginated list of movies that a person has directed.

java
neoflix/services/MovieService.java
public List<Map<String,Object>> getForDirector(String directorId, Params params,String userId) {
    // TODO: Get Movies directed by a Person
    // MATCH (:Person {tmdbId: $id})-[:DIRECTED]->(m:Movie)

    return AppUtils.process(directedByCoppola, params);
}

Find the getForDirector() method in src/main/java/neoflix/services/MovieService.java and modify the function to find a list of movies by director.

The pattern required in the MATCH clause will be:

cypher
MATCH (:Person {tmdbId: $id})-[:DIRECTED]->(m:Movie)

You will have to include the additional parameter id - The tmdbId property relating to the director.

Click to reveal the completed getForDirector() method
java
neoflix/services/MovieService.java
public List<Map<String,Object>> getForDirector(String directorId, Params params,String userId) {
    // Get Movies acted in by a Person
    // MATCH (:Person {tmdbId: $id})-[:DIRECTED]->(m:Movie)

    // Open a new session
    try (var session = this.driver.session()) {

        // Execute a query in a new Read Transaction
        var movies = session.executeRead(tx -> {
            // Get an array of IDs for the User's favorite movies
            var favorites = getUserFavorites(tx, userId);
            var sort = params.sort(Params.Sort.title);

            // Retrieve a list of movies with the
            // favorite flag appended to the movie's properties
            String query = String.format("""
                      MATCH (:Person {tmdbId: $id})-[:DIRECTED]->(m:Movie)
                      WHERE m.`%s` IS NOT NULL
                      RETURN m {
                        .*,
                          favorite: m.tmdbId IN $favorites
                      } AS movie
                      ORDER BY m.`%s` %s
                      SKIP $skip
                      LIMIT $limit
                    """, sort, sort, params.order());
            var res = tx.run(query, Values.parameters("skip", params.skip(), "limit", params.limit(), "favorites", favorites, "id", directorId));
            // Get a list of Movies from the Result
            return res.list(row -> row.get("movie").asMap());
        });
        return movies;
    }
}

Testing

To test that this functionality has been correctly implemented, run the following code in a new terminal session:

sh
Running the test
mvn test -Dtest=neoflix._11_MovieListTest

The test file is located at src/test/java/neoflix/_11_MovieListTest.java.

Are you stuck? Click here for help

If you get stuck, you can see a working solution by checking out the 11-movie-lists branch by running:

sh
Check out the 11-movie-lists branch
git checkout 11-movie-lists

You may have to commit or stash your changes before checking out this branch. You can also click here to expand the Support pane.

Verifying the Test

1. How many films has Francis Ford Copolla directed?

As part of the test suite, the test titled should find films directed by Francis Ford Coppola logs out the number of results returned from the API for films directed by Francis Ford Copolla.

Enter the number in the box below and click Check Answer.

  • ✓ 16

Hint

You can also find the answer by running the following Cypher statement:

cypher
MATCH (p:Person {tmdbId: "1776"})
RETURN count { (p)-[:DIRECTED]->() }

Copy the answer without any quotes or whitespace.

Lesson Summary

In this Challenge, you modified the functions in the MovieService to return lists of movies.

In the next Challenge, we will implement the findById() method.