• No results found

Progressive Data Migration (An Academic Exercise)

In document Core Data 2nd Edition (Page 67-73)

Your First Data Model Version

3.6 Progressive Data Migration (An Academic Exercise)

We’re going to wrap up this chapter with a final section that’s primarily a mental and academic exercise, one that demonstrates the flexibility available when working with Core Data migrations. Since it is possible to insert custom code at nearly every point in a migration, we can do some very interesting things. Let’s take a look at an example.

What happens when our application is at version 5 of its data model and a user at version 1 decides to upgrade? Normally, we would need to provide a mapping model for every combination of source and destination object models.

For the first couple of versions, this is not an issue. However, as we get further away from version 1, it becomes increasingly difficult. Fortunately, it is pos-sible to figure out a migration path and do a progressive migration.

To accomplish a progressive migration, we need to handle the migration manually. The workflow is as follows:

1. If the store’s model is the current model, do nothing.

2. Find a mapping model with the current store’s model as its source.

3. Migrate the data to that mapping model’s destination model.

4. Repeat starting at step 1.

Creating the Migration Method

To begin this monumental task, we will be creating a new method in the AppDelegate. The method requires several pieces of information: the source path, the source type (XML, SQL, and so on), and the final model. In addition, we will pass in an error to be able to report any failures.

RecipesV3/PPRecipes/PPRAppDelegate.m

- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL ofType:(NSString*)type

toModel:(NSManagedObjectModel*)finalModel error:(NSError**)error

{

It’s a rather unwieldy method name, to be sure, but it contains all the infor-mation we need to figure out our migration path. Since this is going to be a recursive method, the first thing we need to do is check to see whether we are at our goal.

RecipesV3/PPRecipes/PPRAppDelegate.m NSDictionary *sourceMetadata =

[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type

URL:sourceStoreURL error:error];

if (!sourceMetadata) return NO;

if ([finalModel isConfiguration:nil

compatibleWithStoreMetadata:sourceMetadata]) {

*error = nil;

return YES;

}

Progressive Data Migration (An Academic Exercise)

55

In this code segment, we first retrieve the metadata from the source URL. If that metadata is not nil, we ask the final model whether the metadata is compatible with it. If it is, we are happy and done. We then set the error pointer to nil and return YES. If it isn’t compatible, we need to try to figure out the mapping model and potentially the interim data model to migrate to.

Finding All the Managed Object Models

To proceed to the next step in the migration, we need to find all the managed object models in the bundle and loop through them. The goal at this point is to get all the models and figure out which one we can migrate to. Since these models will probably be in their own bundles, we have to first look for the bundles and then look inside each of them.

//Find the source model

//Find all of the mom and momd files in the Resources directory NSMutableArray *modelPaths = [NSMutableArray array];

NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd"

inDirectory:nil];

for (NSString *momdPath in momdArray) {

NSString *resourceSubpath = [momdPath lastPathComponent];

NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom"

inDirectory:nil];

[modelPaths addObjectsFromArray:otherModels];

if (!modelPaths || ![modelPaths count]) { //Throw an error if there are no models

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict setValue:@"No models found in bundle"

forKey:NSLocalizedDescriptionKey];

//Populate the error

*error = [NSError errorWithDomain:@"Zarra" code:8001 userInfo:dict];

return NO;

}

In this code block, we first grab all the resource paths from the mainBundle that are of type momd. This gives us a list of all the model bundles. We then loop through the list and look for mom resources inside each and add them to an

overall array. Once that’s done, we look inside the mainBundle again for any freestanding mom resources. Finally, we do a failure check to make sure we have some models to look through. If we can’t find any, we populate the NSError and return NO.

Finding the Mapping Model

Now the complicated part comes in. Since it is not currently possible to get an NSMappingModel with just the source model and then determine the destina-tion model, we have to instead loop through every model we find, instantiate it, plug it in as a possible destination, and see whether there is a mapping model in existence. If there isn’t, we continue to the next one.

RecipesV3/PPRecipes/PPRAppDelegate.m

//If we found a mapping model then proceed if (mappingModel) break;

//Release the target model and keep looking [targetModel release], targetModel = nil;

}

//We have tested every model, if nil here we failed if (!mappingModel) {

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict setValue:@"No models found in bundle"

forKey:NSLocalizedDescriptionKey]; migration routine. In this section, we’re looping through all the models that were previously discovered. For each of those models, we’re instantiating the model and then asking NSMappingModel for an instance that will map between our known source model and the current model. If we find a mapping model, we break from our loop and continue. Otherwise, we release the instantiated model and continue the loop. After the loop, if the mapping model is still nil, we generate an error stating that we cannot discover the progression between Progressive Data Migration (An Academic Exercise)

57

the source model and the target and return NO. At this point, we should have all the components we need for one migration. The source model, target model, and mapping model are all known quantities. Now it’s time to migrate!

Performing the Migration

In this block, we are instantiating an NSMigrationManager (if we needed something special, we would build our own manager) with the source model and the desti-nation model. We are also building up a unique path to migrate to. In this example, we are using the destination model’s filename as the unique change to the source store’s path. Once the destination path is built, we then tell the migration manager to perform the migration and check to see whether it was successful. If it wasn’t, we simply return NO because the NSError will be populated by the NSMigrationManager. If it’s successful, there are only three things left to do:

move the source out of the way, replace it with the new destination store, and finally recurse.

NSString *storeExtension = [[sourceStoreURL path] pathExtension];

NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension];

//Build a path to write the new store

storePath = [NSString stringWithFormat:@"%@.%@.%@", storePath,

In this final code block, we first create a permanent location for the original store to be moved to. In this case, we will use a globally unique string gener-ated from the NSProcessInfo class and attach the destination model’s filename and the store’s extension to it. Once that path is built, we move the source to it and then replace the source with the destination. At this point, we are at the same spot we were when we began except that we are now one version closer to the current model version.

Now we need to loop back to step 1 again in our workflow. Therefore, we will recursively call ourselves, returning the result of that recurse. As you can recall from the beginning of this method, if we are now at the current version, we will simply return YES, which will end the recursion.

RecipesV3/PPRecipes/PPRAppDelegate.m

NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];

guid = [guid stringByAppendingPathExtension:modelName];

//Move the destination to the source path if (![fileManager moveItemAtPath:storePath

toPath:[sourceStoreURL path]

error:error]) {

//Try to back out the source move first, no point in checking it for errors [fileManager moveItemAtPath:backupPath

toPath:[sourceStoreURL path]

error:nil];

return NO;

}

//We may not be at the "current" model yet, so recurse return [self progressivelyMigrateURL:sourceStoreURL

ofType:type toModel:finalModel

error:error];

This progressive migration can be tested by first running version 1 of our Grokking Recipes application, entering some data, and then running the third version. You will then see the data model migrate seamlessly from version 1 to version 3 with no intervention required.

3.7 Wrapping Up

We explored how to deal with changes and additions to our application and discussed data migration. In the next chapter, we’re going to take a look at how our applications perform and ways to tune them.

Wrapping Up

59

In document Core Data 2nd Edition (Page 67-73)