Overhauled the object seeder API. It's much cleaner. Added example code to the RKTwitterCoreDataExample

This commit is contained in:
Blake Watters
2011-03-05 16:11:00 -05:00
parent 6c351c1ef9
commit 1405c0b404
12 changed files with 415 additions and 149 deletions

View File

@@ -10,5 +10,5 @@
#import "../ObjectMapping/ObjectMapping.h"
#import "RKManagedObject.h"
#import "RKManagedObjectStore.h"
#import "RKObjectSeeder.h"
#import "RKManagedObjectSeeder.h"
#import "RKManagedObjectCache.h"

View File

@@ -0,0 +1,65 @@
//
// RKManagedObjectSeeder.h
// RestKit
//
// Created by Blake Watters on 3/4/10.
// Copyright 2010 Two Toasters. All rights reserved.
//
#import "../ObjectMapping/ObjectMapping.h"
@protocol RKManagedObjectSeederDelegate
@required
// Invoked when the seeder creates a new object
- (void)didSeedObject:(NSObject<RKObjectMappable>*)object fromFile:(NSString*)fileName;
@end
/**
* Provides an interface for generating a seed database suitable for initializing
* a Core Data backed RestKit application. The object seeder loads files from the
* application's main bundle and processes them with the Object Mapper to produce
* a database on disk. This file can then be copied into the main bundle of an application
* and provided to RKManagedObjectStore at initialization to start the app with a set of
* data immediately available for use within Core Data.
*/
@interface RKManagedObjectSeeder : NSObject {
RKObjectManager* _manager;
NSObject<RKManagedObjectSeederDelegate>* _delegate;
}
// Delegate for seeding operations
@property (nonatomic, assign) NSObject<RKManagedObjectSeederDelegate>* delegate;
// Path to the generated seed database on disk
@property (nonatomic, readonly) NSString* pathToSeedDatabase;
/**
* Generates a seed database using an object manager and a null terminated list of files. Exits
* the seeding process and outputs an informational message
*/
+ (void)generateSeedDatabaseWithObjectManager:(RKObjectManager*)objectManager fromFiles:(NSString*)fileName, ...;
/**
* Returns an object seeder ready to begin seeding. Requires a fully configured instance of an object manager.
*/
+ (RKManagedObjectSeeder*)objectSeederWithObjectManager:(RKObjectManager*)objectManager;
/**
* Seed the database with objects from the specified file(s). The list must be terminated by nil
*/
- (void)seedObjectsFromFiles:(NSString*)fileName, ...;
/**
* Seed the database with objects from the specified file. Optionally use the specified mappable class and
* keyPath to traverse the object graph before seeding
*/
- (void)seedObjectsFromFile:(NSString*)fileName toClass:(Class<RKObjectMappable>)nilOrMppableClass keyPath:(NSString*)nilOrKeyPath;
/**
* Completes a seeding session by persisting the store, outputing an informational message
* and exiting the process
*/
- (void)finalizeSeedingAndExit;
@end

View File

@@ -0,0 +1,132 @@
//
// RKObjectSeeder.m
// RestKit
//
// Created by Blake Watters on 3/4/10.
// Copyright 2010 Two Toasters. All rights reserved.
//
#import "RKManagedObjectSeeder.h"
#import "RKManagedObjectStore.h"
@interface RKManagedObjectSeeder (Private)
- (id)initWithObjectManager:(RKObjectManager*)manager;
- (void)seedObjectsFromFileNames:(NSArray*)fileNames;
@end
@implementation RKManagedObjectSeeder
@synthesize delegate = _delegate;
+ (void)generateSeedDatabaseWithObjectManager:(RKObjectManager*)objectManager fromFiles:(NSString*)firstFileName, ... {
RKManagedObjectSeeder* seeder = [RKManagedObjectSeeder objectSeederWithObjectManager:objectManager];
va_list args;
va_start(args, firstFileName);
NSMutableArray* fileNames = [NSMutableArray array];
for (NSString* fileName = firstFileName; fileName != nil; fileName = va_arg(args, id)) {
[fileNames addObject:fileName];
}
va_end(args);
// Seed the files
for (NSString* fileName in fileNames) {
[seeder seedObjectsFromFile:fileName toClass:nil keyPath:nil];
}
[seeder finalizeSeedingAndExit];
}
+ (RKManagedObjectSeeder*)objectSeederWithObjectManager:(RKObjectManager*)objectManager {
return [[[RKManagedObjectSeeder alloc] initWithObjectManager:objectManager] autorelease];
}
- (id)initWithObjectManager:(RKObjectManager*)manager {
self = [self init];
if (self) {
_manager = [manager retain];
// Delete any existing persistent store
[_manager.objectStore deletePersistantStore];
}
return self;
}
- (void)dealloc {
[_manager release];
[super dealloc];
}
- (NSString*)pathToSeedDatabase {
return _manager.objectStore.pathToStoreFile;
}
- (void)seedObjectsFromFiles:(NSString*)firstFileName, ... {
va_list args;
va_start(args, firstFileName);
NSMutableArray* fileNames = [NSMutableArray array];
for (NSString* fileName = firstFileName; fileName != nil; fileName = va_arg(args, id)) {
[fileNames addObject:fileName];
}
va_end(args);
for (NSString* fileName in fileNames) {
[self seedObjectsFromFile:fileName toClass:nil keyPath:nil];
}
}
- (void)seedObjectsFromFile:(NSString*)fileName toClass:(Class<RKObjectMappable>)nilOrMppableClass keyPath:(NSString*)nilOrKeyPath {
NSError* error = nil;
NSArray* mappedObjects;
NSString* filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
NSString* payload = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
if (nil == error) {
// TODO: When we support multiple parsers, we should auto-detect the MIME Type from the file extension
// and pass it through to the mapper
id objects = [_manager.mapper parseString:payload];
NSAssert1(objects != nil, @"Unable to parse data from file %@", filePath);
if (nilOrKeyPath) {
objects = [objects valueForKeyPath:nilOrKeyPath];
}
NSAssert1([objects isKindOfClass:[NSArray class]], @"Expected an NSArray of objects, got %@", objects);
NSAssert1([[objects objectAtIndex:0] isKindOfClass:[NSDictionary class]], @"Expected an array of NSDictionaries, got %@", [objects objectAtIndex:0]);
if (nilOrMppableClass) {
mappedObjects = [_manager.mapper mapObjectsFromArrayOfDictionaries:objects toClass:nilOrMppableClass];
} else {
mappedObjects = [_manager.mapper mapObjectsFromArrayOfDictionaries:objects];
}
// Inform the delegate
if (self.delegate) {
for (NSObject<RKObjectMappable>* object in mappedObjects) {
[self.delegate didSeedObject:object fromFile:fileName];
}
}
NSLog(@"[RestKit] RKManagedObjectSeeder: Seeded %d objects from %@...", [mappedObjects count], [NSString stringWithFormat:@"%@", fileName]);
} else {
NSLog(@"Unable to read file %@: %@", fileName, [error localizedDescription]);
}
}
- (void)finalizeSeedingAndExit {
NSError* error = [[_manager objectStore] save];
if (error != nil) {
NSLog(@"[RestKit] RKManagedObjectSeeder: Error saving object context: %@", [error localizedDescription]);
}
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString* storeFileName = [[_manager objectStore] storeFilename];
NSString* destinationPath = [basePath stringByAppendingPathComponent:storeFileName];
NSLog(@"[RestKit] RKManagedObjectSeeder: A seeded database has been generated at '%@'. "
@"Please execute `open %@` in your Terminal and copy %@ to your app. Be sure to add the seed database to your \"Copy Resources\" build phase.",
destinationPath, basePath, storeFileName);
exit(1);
}
@end

View File

@@ -152,7 +152,9 @@ static NSString* const kRKManagedObjectContextKey = @"RKManagedObjectContext";
// Clear the current managed object context. Will be re-created next time it is accessed.
NSMutableDictionary* threadDictionary = [[NSThread currentThread] threadDictionary];
[threadDictionary setObject:nil forKey:kRKManagedObjectContextKey];
if ([threadDictionary objectForKey:kRKManagedObjectContextKey]) {
[threadDictionary setNilValueForKey:kRKManagedObjectContextKey];
}
[self createPersistentStoreCoordinator];
}

View File

@@ -1,45 +0,0 @@
//
// RKObjectSeeder.h
// RestKit
//
// Created by Blake Watters on 3/4/10.
// Copyright 2010 Two Toasters. All rights reserved.
//
#import "../ObjectMapping/ObjectMapping.h"
// TODO: This class needs an API scrubbing
// TODO: Should be updated with ability to auto-detect MIME type
// from the file extension. Does this need a delegate property?
@interface RKObjectSeeder : NSObject {
RKObjectManager* _manager;
}
/**
* Initialize a new object seeder
*/
- (id)initWithObjectManager:(RKObjectManager*)manager;
/**
* Read a file from the main bundle and seed the database with its contents.
* Returns the array of model objects built from the file.
*/
- (NSArray*)seedDatabaseWithBundledFile:(NSString*)fileName ofType:(NSString*)type;
/**
* Seeds the database with an array of files of the specified type
*/
- (void)seedDatabaseWithBundledFiles:(NSArray*)fileNames ofType:(NSString*)type;
/**
* Seed a specific object class with data from a file
*/
- (void)seedObjectsFromFile:(NSString*)fileName ofType:(NSString*)type toClass:(Class)theClass keyPath:(NSString*)keyPath;
/**
* Completes a seeding session by persisting the store, outputing an informational message
* and exiting the process
*/
- (void)finalizeSeedingAndExit;
@end

View File

@@ -1,82 +0,0 @@
//
// RKObjectSeeder.m
// RestKit
//
// Created by Blake Watters on 3/4/10.
// Copyright 2010 Two Toasters. All rights reserved.
//
#import "RKObjectSeeder.h"
#import "RKManagedObjectStore.h"
@implementation RKObjectSeeder
- (id)initWithObjectManager:(RKObjectManager*)manager {
self = [self init];
if (self) {
_manager = [manager retain];
}
return self;
}
- (void)dealloc {
[_manager release];
[super dealloc];
}
- (NSArray*)seedDatabaseWithBundledFile:(NSString*)fileName ofType:(NSString*)type {
NSError* error = nil;
NSString* filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:type];
NSString* payload = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
if (nil == error) {
return [[_manager mapper] mapFromString:payload];
}
return nil;
}
- (void)seedDatabaseWithBundledFiles:(NSArray*)fileNames ofType:(NSString*)type {
NSLog(@"[RestKit] RKModelSeeder: Seeding database with contents of %d %@ files...", [fileNames count], [type uppercaseString]);
for (NSString* fileName in fileNames) {
NSArray* objects = [self seedDatabaseWithBundledFile:fileName ofType:type];
NSLog(@"[RestKit] RKModelSeeder: Seeded %d objects from %@...", [objects count], [NSString stringWithFormat:@"%@.%@", fileName, type]);
}
[self finalizeSeedingAndExit];
}
- (void)seedObjectsFromFile:(NSString*)fileName ofType:(NSString*)type toClass:(Class)theClass keyPath:(NSString*)keyPath {
NSError* error = nil;
NSString* filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:type];
NSString* payload = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
if (nil == error) {
id objects = [_manager.mapper parseString:payload];
NSAssert1(objects != nil, @"Unable to parse data from file %@", filePath);
id parseableObjects = [objects valueForKeyPath:keyPath];
NSAssert1([parseableObjects isKindOfClass:[NSArray class]], @"Expected an NSArray of objects, got %@", objects);
NSAssert1([[parseableObjects objectAtIndex:0] isKindOfClass:[NSDictionary class]], @"Expected an array of NSDictionaries, got %@", [objects objectAtIndex:0]);
NSArray* mappedObjects = [_manager.mapper mapObjectsFromArrayOfDictionaries:parseableObjects toClass:theClass];
NSLog(@"[RestKit] RKModelSeeder: Seeded %d objects from %@...", [mappedObjects count], [NSString stringWithFormat:@"%@.%@", fileName, type]);
} else {
NSLog(@"Unable to read file %@ with type %@: %@", fileName, type, [error localizedDescription]);
}
}
- (void)finalizeSeedingAndExit {
NSError* error = [[_manager objectStore] save];
if (error != nil) {
NSLog(@"[RestKit] RKModelSeeder: Error saving object context: %@", [error localizedDescription]);
}
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString* storeFileName = [[_manager objectStore] storeFilename];
NSString* destinationPath = [basePath stringByAppendingPathComponent:storeFileName];
NSLog(@"[RestKit] RKModelSeeder: A Pre-loaded database has been generated at %@. Please copy into Resources/", destinationPath);
exit(1);
}
@end