Just like a battle plan, no code base ever survives contact with users. As soon as users start to use an application, they want to change it. Even if the code is just for ourselves, we, also as users, will want to change things. For example, we may need to add an attribute or a new object and then restructure things to accommodate those changes. Additions and changes can be quite involved and invariably require a change in how the data is stored. Although a data migration works even when there is no data stored, it is more useful to have some data to work with. Therefore, if you have not added any recipes yet, I recommend doing so before we proceed.
Starting with Mac OS X 10.5 Leopard and iOS 3.0, Apple has made data migration nearly trivial for users of Core Data. Taking the project outlined in Appendix 1, Building a Foundation, on page 209, we will add some additional features to it in succeeding versions. In version 2, we will add the ability to tag an author to a recipe as well as tag a “last used” date. That way, we know who created the delicious dish as well as the last time we made it. We certainly wouldn’t want to accidentally make the same dish two days in a row!
In version 3, we will normalize the repository a bit by extracting the ingredients and forming a many-to-many relationship back to the recipes. In addition, we will add the concept of a shopping list to make it easier to ensure we pick up all the ingredients on our next trip to the store. Next, we will extract the unitOfMeasure attribute from the RecipeIngredient entity into its own entity and allow that new entity to be linked to the new ingredient entity. This step gives us one lookup list for the various units of measure and reduces the risk of human error. Lastly, we will remove the Meat and Fish entries from the Type attribute of the Recipe entity. Any recipe entries that are flagged with Meat or Fish will be updated to Entrée instead.
3.1 Some Maintenance Before We Migrate
Before we actually release a new version of our application that migrates the data, we need to first complete a minor “maintenance” update for our users.
Normally, we would add this code to the very first version of our application, but just in case we wrote that first release before versioning was a considera-tion, we need to go back to our old version and add a very small amount of code to help our users.
Some users will download the new version of an application to just “try it out”
and see whether it is worth the upgrade price or worth the hassle. Normally this is not an issue—until we upgrade the data underneath our users. Then things go sideways. What we do not want to happen is the error message shown in Figure 6, Default model issue dialog box, on page 38.
Figure 6—Default model issue dialog box
Note that this is the error message we would see on OS X. On iOS, our application would simply crash on launch. This is a terrible user experience and something we want to avoid. Fortunately, the way to avoid it is very easy, and we can add it to a point release of our application before we do any migration. That way, when the users open the first version of our application after “testing” the second version, they get a friendly error message; or we can take it a step further and restore/access the older version of their data.
Baseline/PPRecipes/PPRAppDelegateAlt2.m dispatch_async(queue, ^{
NSError *error = nil;
NSPersistentStoreCoordinator *coordinator = nil;
coordinator = [moc persistentStoreCoordinator];
NSPersistentStore *store = nil;
store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil
URL:storeURL options:nil
error:&error];
if (!store) {
ALog(@"Error adding persistent store to coordinator %@\n%@", [error localizedDescription], [error userInfo]);
NSString *msg = nil;
msg = [NSString stringWithFormat:@"The recipes database %@%@%@\n%@\n%@",
@"is either corrupt or was created by a newer ",
@"version of Grokking Recipes. Please contact ",
@"support to assist with this error.",
[error localizedDescription], [error userInfo]];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error"
message:msg
This is the new creation of the persistent store inside the -initializeCoreDataStack method. Once we have initialized our persistent store coordinator, we kick off a background process to add the NSPersistentStore to the NSPersistentStoreCoordi-nator. If there is an error here, we need to present it to the user and halt the application. We do this with a UIAlertView showing the error to the user, and then the delegate of the UIAlertView forces the application to quit. The resulting error message is shown in Figure 7, Version error on iOS, on page 40. In a production application, we would perhaps offer the user the option to reset the data as opposed to exiting. Note that we also have an ALog before the UIAlertView. When we are developing this application, we want to make very sure that a developer-level error fires here to ideally avoid the risk of the user ever encountering it.
3.2 A Simple Migration
To demonstrate a simple migration, let’s add the ability to attribute recipes to authors. To begin the versioning, the first thing we need to do is create a new managed object model (MOM) based on the first one. To do that, we need to select the existing model in Xcode and then choose Design > Data Model
> Add Model Version.
Creating a Versioned Data Model
This is the first time we have added a model version, so Xcode is going to create a new bundle for us called PPRecipes.xcdatamodeld and put the original MOM inside the bundle along with a new copy of the original MOM. To make things clearer in the example project, I renamed these MOM objects to A Simple Migration
•
39Figure 7—Version error on iOS