So far, the website had only been a plain old movie database. We now wanted to add a touch of social to it.
So we started out by taking the User class that we'd already coded and made it a full-fledged Spring Data Neo4j entity. We added the ability to create friends and to rate movies. With that we also added a simple UserRepository that was able to look up users by ID.
The relationships of the user are his friends and the movie-ratings which is implemented
with a Rating
Relationship-Entity. This time we used a different approach
(for educational and curiosity purposes) to create the Rating
relationships.
The createRelationshipBetween
operation of the Neo4jTemplate was our matchmaker of
choice.
Example 12.1. Social entities
@NodeEntity class User { @Indexed(unique=true) String login; String name; String password; @RelatedToVia(type = RATED) @Fetch Set<Rating> ratings; @RelatedTo(type = "FRIEND", direction=Direction.BOTH) @Fetch Set<User> friends; public Rating rate(Neo4jOperations template, Movie movie, int stars, String comment) { final Rating rating = template.createRelationshipBetween(this, movie, Rating.class, RATED, false); rating.rate(stars, comment); return template.save(rating); } public void addFriend(User user) { this.friends.add(user); } } @RelationshipEntity class Rating { @StartNode User user; @EndNode Movie movie; int stars; String comment; public Rating rate(int stars, String comment) { this.stars = stars; this.comment = comment; return this; } }
We extended the DatabasePopulator to add some users and ratings to the initial setup.
Example 12.2. Populate users and ratings
@Transactional public List<Movie> populateDatabase() { Actor tomHanks = new Actor("1", "Tom Hanks"); Movie forestGump = new Movie("1", "Forrest Gump"); tomHanks.playedIn(forestGump, "Forrest"); template.save(tomHanks); User me = template.save(new User("micha", "Micha", "password")); Rating awesome = me.rate(template, forestGump, 5, "Awesome"); User ollie = template.save(new User("ollie", "Oliver", "password")); ollie.rate(template,forestGump, 2, "ok"); me.addFriend(ollie); template.save(me); return asList(forestGump); }
We also put a ratings field into the Movie class to be able to get a movie's ratings, and also a method to average its star rating.
Example 12.3. Getting the rating of a movie
class Movie { ... @RelatedToVia(type="RATED", direction = Direction.INCOMING) @Fetch Iterable<Rating> ratings; public int getStars() { int stars = 0, count = 0; for (Rating rating : ratings) { stars += rating.getStars(); count++; } return count == 0 ? 0 : stars / count; } }
Fortunately our tests highlighted the division by zero error when calculating the stars for a movie without ratings. The next steps were to add this information to the movie presentation in the UI, and creating a user profile page. But for that to happen, users must first be able to log in.