The data model you currently have associates movies with a single family member. This means that the app potentially stores the same movie over and over again. When we were only storing data, this wasn't that big of a deal. This wasn't a big deal when you first implemented your database. However, now that the app will query the movie database in a background fetch task, it would be a waste of resources to ask the movie database for the same ratings multiple times. Also, you most certainly don't want to use the movie database search API in the same way you did before; you should refer to the movie you want as precisely as possible.
To facilitate this, the relationship between movies and family members must be changed to a many-to-many relationship. You'll also add a new field to the movie entity: remoteId. remoteId will hold the identifier the movie database uses for a particular movie so it can be used directly in later API calls.
Open the model editor in Xcode and add the new remoteId property to Movie. Make sure that it's a 64-bit integer and that it's optional. Also, select the familyMember relationship and change it to a To Many relationship in the sidebar. It's also a good idea to rename the relationship to familyMembers, since it's now related to more than one family member. Your model should resemble the model in the following screenshot:
Great, the model has been updated. There's still a lot of work to be done though. Because the name and nature of the family member relationship were changed, the project won't compile. Make the following modifications to the managedObjectContextDidChange(_:) method in MoviesViewController.swift:
if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<Movie> { for object in updatedObjects { if let familyMember = self.familyMember, let familyMembers = object.familyMembers, familyMembers.contains(familyMember) { tableView.reloadData() break } } }
There is just one more model-related change that you should incorporate. To efficiently search for an existing movie or create a new one, an extension to the Movie model should be created. Create a new group called Models in your project and add a new Swift file named Movie.swift to it. Finally, add the following implementation to the file:
import CoreData extension Movie { static func find(byName name: String, orCreateIn moc: NSManagedObjectContext) -> Movie { let predicate = NSPredicate(format: "title ==[dc] %@", name) let request: NSFetchRequest<Movie> = Movie.fetchRequest() request.predicate = predicate guard let result = try? moc.fetch(request) else { return Movie(context: moc) } return result.first ?? Movie(context: moc) } }
The preceding code queries Core Data for an existing movie with the provided name. The movies are matched case-insensitive by passing [dc] to the == operator in NSPredicate. It's important that this lookup is case-insensitive because people might write the same movie name with different capitalizations. If no movies could be found, or if the results come back empty, a new movie is created. Otherwise, the first, and presumably the only, result that Core Data has is returned. This wraps up the changes that need to be made to the app's data layer.