Project import
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a9cb168
--- /dev/null
+++ b/README.md
@@ -0,0 +1,85 @@
+# StoreMad
+
+StoreMad is a collection of helpful categories and controllers that encourage a healthy relationship with Core Data. Your best bet it to sit down and learn Core Data, as you will be way better off in the long term. At the very least, there are some patterns that you may find helpful here.
+
+# Store Controller
+
+The SMStoreController is the main controller you use to add Core Data to your app. Instantiate a new SMStoreController which points to your sqlite store URL and momd URL.
+
+```objective-c
+#import <StoreMad/StoreMad.h>
+
+- (SMStoreController *)storeController
+{
+    if (_storeController) return _storeController;
+
+    // sqlite
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSURL *applicationDocDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
+    NSURL *storeURL = [applicationDocDirectory URLByAppendingPathComponent:@"StoreMadApp.sqlite"];
+
+    // momd
+    NSURL *modelURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"StoreMadApp" withExtension:@"momd"];
+    
+    // controller
+    SMStoreController *newStoreController = [SMStoreController storeControllerWithStoreURL:storeURL
+                                                                               andModelURL:modelURL];
+    
+    //
+    // Context saves when app changes state
+    //
+    [newStoreController shouldSaveOnAppStateChanges:YES];
+  
+    _storeController = newStoreController;
+  
+    return _storeController;
+}
+```
+
+## SMContextObserverController
+
+This controller is intended to be used much the same way that you interact with `NSNotificationCenter`. An `NSManagedObjectContext` emits various notifications when it performs inserts, updates, and deletes. Using `SMContextObserverController`,  you can run a block on an object or set of objects of interest. This is an essential Core Data pattern for updating your UI, and decoupling your data and UI later.
+
+## Observe an Object
+
+Here is a simple pattern for observing changes to an NSManagedObject. Note that whenever you add an observer, you must remove it when finished, just like NSNotificationCenter.
+
+```objective-c
+#import <StoreMad/StoreMad.h>
+
+@interface MyViewController ()
+
+@property (nonatomic, strong) id observer;
+
+@property (nonatomic, weak) IBOutlet UILabel *employmentStatusLabel;
+
+@end
+
+@implementation MyViewController
+
+- (void)dealloc
+{
+	[[SMContextObserverController defaultController] removeContextObserver:self.observer];
+}
+
+- (void)viewDidLoad
+{
+	[super viewDidLoad];
+	
+    Employee *employee = [Employee createInContext:context];
+    
+    __weak MyViewController *weakSelf = self;
+    self.observer = [[SMContextObserverController defaultController] addContextObserverForChangesToObject:employee
+                                                                                                workBlock:^(NSManagedObject *object) {
+                                                                                                    [weakSelf configureWithEmployee:object];
+                                                                                                }];
+}
+
+- (void)configureWithEmployee:(Employee *)employee
+{
+    _employmentStatusLabel.text = employee.isFired ? @"Fired" : "Hired";
+}
+
+@end
+```
+
diff --git a/StoreMad/NSManagedObject+StoreMad.h b/StoreMad/NSManagedObject+StoreMad.h
new file mode 100644
index 0000000..7987144
--- /dev/null
+++ b/StoreMad/NSManagedObject+StoreMad.h
@@ -0,0 +1,64 @@
+//
+//  NSManagedObject+StoreMad.h
+//  StoreMad
+//
+//  Created by Andrew Smith on 7/20/12.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy 
+// of this software and associated documentation files (the "Software"), to deal 
+// in the Software without restriction, including without limitation the rights 
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+// of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included 
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// FAD = "From Apple Docs"
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@interface NSManagedObject (StoreMad)
+
+/**
+ Shortcut to the objects URI.
+ 
+ FAD: "An NSURL object containing a URI that provides an archiveable reference to the
+ object which the receiver represents."
+ */
+@property (nonatomic, readonly) NSURL *stm_objectURI;
+
+/**
+ Sometimes CoreData will fault a particular instance, while there is still
+ the same object in the store.  This also checks to see if there is a clone.
+ 
+ FAD: "The method (isDeleted) returns YES if Core Data will ask the persistent store to delete
+ the object during the next save operation. It may return NO at other times,
+ particularly after the object has been deleted. The immediacy with which
+ it will stop returning YES depends on where the object is in the process of being deleted."
+ */
+@property (nonatomic, readonly) BOOL stm_hasBeenDeleted;
+
+/**
+ Checks the objectID to see if it is permanent.
+ 
+ FAD: "New objects inserted into a managed object context are assigned a temporary ID
+ which is replaced with a permanent one once the object gets saved to a persistent store."
+ */
+@property (nonatomic, readonly) BOOL stm_hasBeenSaved;
+
+/**
+ Inserts an object of the same type as the calling class name into the context.
+ */
++ (id)stm_createInContext:(NSManagedObjectContext *)context;
+
+@end
diff --git a/StoreMad/NSManagedObject+StoreMad.m b/StoreMad/NSManagedObject+StoreMad.m
new file mode 100644
index 0000000..ce07899
--- /dev/null
+++ b/StoreMad/NSManagedObject+StoreMad.m
@@ -0,0 +1,59 @@
+//
+//  NSManagedObject+StoreMad.m
+//  StoreMad
+//
+//  Created by Andrew Smith on 7/20/12.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy 
+// of this software and associated documentation files (the "Software"), to deal 
+// in the Software without restriction, including without limitation the rights 
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+// of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included 
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSManagedObject+StoreMad.h"
+#import "NSManagedObjectContext+StoreMad.h"
+
+@implementation NSManagedObject (StoreMad)
+
+- (NSURL *)stm_objectURI
+{
+    return self.objectID.URIRepresentation;
+}
+
+- (BOOL)stm_hasBeenDeleted
+{
+    NSManagedObjectID *objectID           = [self objectID];
+    NSManagedObject   *managedObjectClone = [[self managedObjectContext] existingObjectWithID:objectID
+                                                                                        error:NULL];
+    
+    if (!managedObjectClone || [self isDeleted]) {
+        return YES;
+    } else {
+        return NO;
+    }
+}
+
+- (BOOL)stm_hasBeenSaved
+{
+    return !self.objectID.isTemporaryID;
+}
+
++ (id)stm_createInContext:(NSManagedObjectContext *)context
+{
+    return [context stm_insertNewObjectForEntityNamed:[self description]];
+}
+
+@end
diff --git a/StoreMad/NSManagedObjectContext+StoreMad.h b/StoreMad/NSManagedObjectContext+StoreMad.h
new file mode 100644
index 0000000..ff82ad0
--- /dev/null
+++ b/StoreMad/NSManagedObjectContext+StoreMad.h
@@ -0,0 +1,161 @@
+//
+//  NSManagedObjectContext+StoreMad.h
+//  StoreMad
+//
+//  Created by Andrew Smith on 7/20/12.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy 
+// of this software and associated documentation files (the "Software"), to deal 
+// in the Software without restriction, including without limitation the rights 
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+// of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included 
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+/**
+ 
+ !!!
+ With any of these methods, you have to respect the NSManagedObjectContext internal queues. It's 
+ recommended that you wrap a bunch of context work in a performBlock, or performBlockAndWait. Methods
+ that provide their own blocks specifically say so in the method name. For example, `queueBlockSave`
+ will queue a block save on the context.
+ !!!
+ 
+ */
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@interface NSManagedObjectContext (StoreMad)
+
+/**
+ Creates a new managed object context based on the current contexts persistent store.
+ When you save with the thread safe copy, it will merge the changes on the main 
+ thread as long as the copy you created the thread safe copy from is still around.
+ 
+ Note: You have to allocate the threadSafeCopy on the thread you want to use it with.
+ */
+- (NSManagedObjectContext *)stm_threadSafeCopy;
+
+/**
+ Standard save, but handles any errors with some (hopefully) usefull logging for you
+ */
+- (void)stm_save;
+
+/**
+ Adds performBlock wrapped save. Use this when you are outside of a performBlock to save right from
+ the main thread.
+ */
+- (void)stm_queueBlockSave;
+
+/**
+ Adds a performBlock wrapped save and waits for it to finish. Use this to block the calling thread
+ until the save is finished.
+ */
+- (void)stm_queueBlockSaveAndWait;
+
+/**
+ If a parent context is present, queue a performBlock wrapped save on the parent context. This is
+ useful if you are calling save from a background thread onto the main context.
+ */
+- (void)stm_queueBlockSaveOnParentContext;
+
+/**
+ Returns the NSManagedObject for the corresponding URI. If the object cannot be fetched, or does 
+ not exist, or cannot be faulted, this will return nil.
+ */
+- (NSManagedObject *)stm_objectForURI:(NSURL *)objectURI;
+
+/**
+ Deletes the object at the given URI.
+ */
+- (void)stm_deleteObjectAtURI:(NSURL *)objectURI;
+
+/**
+ Deletes and array of NSManagedObjects
+ */
+- (void)stm_deleteObjects:(NSArray *)objects;
+
+/**
+ Handy wrapper around executeFetchRequest that handles errors nicely.
+ */
+- (NSArray *)stm_executeFetchRequest:(NSFetchRequest *)request;
+
+/**
+ Executes the fetch request and returns the first object of the result. Fetch limit is set to 1 for
+ faster fetching.
+ */
+- (NSManagedObject *)stm_executeFetchRequestAndReturnFirstObject:(NSFetchRequest *)request;
+
+/**
+ Handy wrapper around countForFetchRequest that handles errors nicely.
+ */
+- (NSUInteger)stm_countForFetchRequest:(NSFetchRequest *)request;
+
+/**
+ Handy wrapper around countForFetchRequest
+ */
+- (NSUInteger)stm_countForObjectClass:(Class)objectClass;
+
+/**
+ This will fetch a list of all properties of a given entity. For example, if you have a twitter app
+ and you wanted all tweetId (Integer 64) property values on your Tweet object, you could use this method.
+ 
+ NSFetchRequest *tweetFetch = [self findAllFetchRequestForObjectNamed:@"Tweet"];
+ NSArray *result = [context allValuesForProperty:@"tweetId" withRequest:tweetFetch];
+ 
+ <result = [1, 5, 25, 123, 82349, 29292, … ] >
+ 
+ NOTE: Fetched properties will only include properties of objects that have been saved.
+ */
+- (NSArray *)stm_allValuesForProperty:(NSString *)propertyName
+                          withRequest:(NSFetchRequest *)request;
+
+/**
+ Returns a fetch request for the object name. Returns nil if context has no object registered with
+ the given name.
+ */
+- (NSFetchRequest *)stm_fetchRequestForObjectNamed:(NSString *)objectName;
+
+/**
+ Returns a fetch request that will find all of the objects of the given name. Adds a predicate that
+ always returns YES for all objects. Fast way of fetching all objects for the given objectName.
+ 
+ NOTE: this includes a predicate with 1==1, which does nothing. However, NSFetchedResultsController
+ requires a predicate for it's fetch request, so use this instead of fetchRequestForObjectNamed with
+ no predicate.
+ */
+- (NSFetchRequest *)stm_findAllFetchRequestForObjectNamed:(NSString *)objectName;
+
+/**
+ Returns a fetch request for the object matching the provided class name.
+ */
+- (NSFetchRequest *)stm_fetchRequestForObjectClass:(Class)objectClass;
+
+/**
+ Returns a fetch request that will find all of the objects of the given name. Adds a predicate that
+ always returns YES for all objects. Fast way of fetching all objects for the given objectName.
+ 
+ NOTE: this includes a predicate with 1==1, which does nothing. However, NSFetchedResultsController
+ requires a predicate for it's fetch request, so use this instead of fetchRequestForObjectNamed with
+ no predicate.
+ */
+- (NSFetchRequest *)stm_findAllFetchRequestForObjectClass:(Class)objectClass;
+
+/**
+ Inserts a new object for the entity name in the given context, returns said object if successful.
+ */
+- (NSManagedObject *)stm_insertNewObjectForEntityNamed:(NSString *)entityName;
+
+@end
diff --git a/StoreMad/NSManagedObjectContext+StoreMad.m b/StoreMad/NSManagedObjectContext+StoreMad.m
new file mode 100644
index 0000000..24c585f
--- /dev/null
+++ b/StoreMad/NSManagedObjectContext+StoreMad.m
@@ -0,0 +1,244 @@
+//
+//  NSManagedObjectContext+StoreMad.m
+//  StoreMad
+//
+//  Created by Andrew Smith on 7/20/12.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy 
+// of this software and associated documentation files (the "Software"), to deal 
+// in the Software without restriction, including without limitation the rights 
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+// of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included 
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSManagedObjectContext+StoreMad.h"
+#import "StoreMad.h"
+
+@implementation NSManagedObjectContext (StoreMad)
+
+- (void)handleErrors:(NSError *)error
+{
+    // Forget where I snagged this from
+    if (error) {
+        NSDictionary *userInfo = [error userInfo];
+        for (NSArray *detailedError in [userInfo allValues]) {
+            if ([detailedError isKindOfClass:[NSArray class]]) {
+                for (NSError *e in detailedError) {
+                    if ([e respondsToSelector:@selector(userInfo)]) {
+                        NSLog(@"Error Details: %@", [e userInfo]);
+                    }
+                    else {
+                        NSLog(@"Error Details: %@", e);
+                    }
+                }
+            }
+            else {
+                NSLog(@"Error: %@", detailedError);
+            }
+        }
+        NSLog(@"Error Domain: %@", [error domain]);
+        NSLog(@"Recovery Suggestion: %@", [error localizedRecoverySuggestion]);
+    }
+}
+
+#pragma mark - Thread
+
+- (NSManagedObjectContext *)stm_threadSafeCopy
+{    
+    //
+    // Create new context with default concurrency type
+    //
+    NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
+    [newContext setParentContext:self];
+    
+    //
+    // Optimization.  No undos in background thread.
+    //
+    [newContext setUndoManager:nil];
+    
+    return newContext;
+}
+
+#pragma mark - Save
+
+- (void)stm_save
+{
+    NSError *error;
+    [self save:&error];
+    [self handleErrors:error];
+}
+
+- (void)stm_queueBlockSave
+{
+    [self performBlock:^{
+        [self stm_save];
+    }];
+}
+
+- (void)stm_queueBlockSaveAndWait
+{
+    [self performBlockAndWait:^{
+        [self stm_save];
+    }];
+}
+
+- (void)stm_queueBlockSaveOnParentContext
+{
+    [self.parentContext performBlock:^{
+        [self.parentContext stm_save];
+    }];
+}
+
+#pragma mark - URI Helpers
+
+- (NSManagedObject *)stm_objectForURI:(NSURL *)objectURI
+{
+    NSManagedObjectID *objectID = [[self persistentStoreCoordinator] managedObjectIDForURIRepresentation:objectURI];
+    if (!objectID) return nil;
+    
+    // If the object cannot be fetched, or does not exist, or cannot be faulted, existingObjectWithID returns nil
+    NSError *error;
+    NSManagedObject *object = [self existingObjectWithID:objectID
+                                                   error:&error];
+    
+    [self handleErrors:error];
+    
+    return object;
+}
+
+#pragma mark - Delete
+
+- (void)stm_deleteObjects:(NSArray *)objects
+{
+    for (NSManagedObject *object in objects) {
+        [self deleteObject:object];
+    }
+}
+
+- (void)stm_deleteObjectAtURI:(NSURL *)objectURI
+{
+    NSManagedObject *object = [self stm_objectForURI:objectURI];
+    if (!object) return;
+    [self deleteObject:object];
+}
+
+#pragma mark - Fetching
+     
+- (NSArray *)stm_executeFetchRequest:(NSFetchRequest *)request
+{    
+    NSError *error;
+    NSArray *results = [self executeFetchRequest:request error:&error];
+
+    [self handleErrors:error];
+
+    return results;
+}
+
+- (NSManagedObject *)stm_executeFetchRequestAndReturnFirstObject:(NSFetchRequest *)request
+{
+    [request setFetchLimit:1];
+    NSArray *results = [self stm_executeFetchRequest:request];
+    if (results.count < 1) return nil;
+    return [results objectAtIndex:0];
+}
+
+- (NSUInteger)stm_countForFetchRequest:(NSFetchRequest *)request
+{
+    // Optimization?  I'd imagine it doesn't include these when counting, but
+    // should test.
+    request.includesPropertyValues = NO;
+        
+    NSError *error;
+    NSUInteger count = [self countForFetchRequest:request error:&error];
+    
+    [self handleErrors:error];
+    
+    return count;
+}
+
+- (NSUInteger)stm_countForObjectClass:(Class)objectClass
+{
+    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass(objectClass)];
+    return [self stm_countForFetchRequest:request];
+}
+
+- (NSArray *)stm_allValuesForProperty:(NSString *)propertyName
+                          withRequest:(NSFetchRequest *)request
+{
+    // This could be really slow.  Use carefully.
+    NSDictionary *properties = request.entity.propertiesByName;
+    NSPropertyDescription *property = [properties objectForKey:propertyName];
+    if (!property) return nil;
+    
+    [request setPropertiesToFetch:@[property]];
+    [request setResultType:NSDictionaryResultType];
+    NSArray *results = [self stm_executeFetchRequest:request];
+    
+    NSMutableArray *propertyValuesList = [NSMutableArray arrayWithCapacity:results.count];
+    for (NSManagedObject *object in results) {
+        [propertyValuesList addObject:[object valueForKey:propertyName]];
+    }
+    
+    return propertyValuesList;
+}
+
+#pragma mark - Fetch Requests
+
+- (NSFetchRequest *)stm_fetchRequestForObjectNamed:(NSString *)objectName
+{
+    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:objectName 
+                                                         inManagedObjectContext:self];
+    
+    // Fetch request must have an entity
+    if (!entityDescription) return nil;
+    
+    NSFetchRequest *request = [[NSFetchRequest alloc] init];
+    [request setEntity:entityDescription];
+    
+    return request;
+}
+
+- (NSFetchRequest *)stm_findAllFetchRequestForObjectNamed:(NSString *)objectName
+{
+    NSFetchRequest *request = [self stm_fetchRequestForObjectNamed:objectName];
+    [request setPredicate:[NSPredicate predicateWithFormat:@"1 = 1"]];
+    return request;  
+}
+
+- (NSFetchRequest *)stm_fetchRequestForObjectClass:(Class)objectClass;
+{
+    return [self stm_fetchRequestForObjectNamed:NSStringFromClass(objectClass)];
+}
+
+- (NSFetchRequest *)stm_findAllFetchRequestForObjectClass:(Class)objectClass
+{
+    return [self stm_findAllFetchRequestForObjectNamed:NSStringFromClass(objectClass)];
+}
+
+#pragma mark - Create
+
+- (NSManagedObject *)stm_insertNewObjectForEntityNamed:(NSString *)entityName
+{
+    // If entity is nil, initWithEntity will cause a crash. So make sure to bail.
+    NSEntityDescription *entity = [NSEntityDescription entityForName:entityName
+                                              inManagedObjectContext:self];
+    if (!entity) return nil;
+    
+    NSManagedObject *object = [[NSManagedObject alloc] initWithEntity:entity
+                                       insertIntoManagedObjectContext:self];
+    return object;
+}
+
+@end
diff --git a/StoreMad/SMCollectionViewDataSource.h b/StoreMad/SMCollectionViewDataSource.h
new file mode 100644
index 0000000..359b86e
--- /dev/null
+++ b/StoreMad/SMCollectionViewDataSource.h
@@ -0,0 +1,38 @@
+//
+//  SMCollectionViewDataSource.h
+//  StoreMad
+//
+//  Created by Andrew Smith on 2/23/13.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "SMDataSource.h"
+
+@interface SMCollectionViewDataSource : SMDataSource
+
+- (void)setupWithCollectionView:(UICollectionView *)collectionView
+                   fetchRequest:(NSFetchRequest *)fetchRequest
+                        context:(NSManagedObjectContext *)context
+             sectionNameKeyPath:(NSString *)sectionNameKeyPath
+                      cacheName:(NSString *)cacheName;
+
+@end
diff --git a/StoreMad/SMCollectionViewDataSource.m b/StoreMad/SMCollectionViewDataSource.m
new file mode 100644
index 0000000..cecfe3b
--- /dev/null
+++ b/StoreMad/SMCollectionViewDataSource.m
@@ -0,0 +1,227 @@
+//
+//  SMCollectionViewDataSource.m
+//  StoreMad
+//
+//  Created by Andrew Smith on 2/23/13.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "SMCollectionViewDataSource.h"
+
+@interface SMCollectionViewDataSource ()
+
+@property (nonatomic, strong) NSMutableArray *objectChanges;
+
+@property (nonatomic, strong) NSMutableArray *sectionChanges;
+
+@property (nonatomic, weak) UICollectionView *collectionView;
+
+@end
+
+@implementation SMCollectionViewDataSource
+
+- (id)init
+{
+    self = [super init];
+    if (self) {
+        _objectChanges = [NSMutableArray array];
+        _sectionChanges = [NSMutableArray array];
+    }
+    return self;
+}
+
+- (void)setupWithCollectionView:(UICollectionView *)collectionView
+                   fetchRequest:(NSFetchRequest *)fetchRequest
+                        context:(NSManagedObjectContext *)context
+             sectionNameKeyPath:(NSString *)sectionNameKeyPath
+                      cacheName:(NSString *)cacheName
+{    
+    // Build controller
+    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
+                                                                        managedObjectContext:context
+                                                                          sectionNameKeyPath:sectionNameKeyPath
+                                                                                   cacheName:cacheName];
+    
+    // Respond to changes here
+    self.fetchedResultsController.delegate = self;
+    
+    // Hold onto this to work with when changes happen
+    self.collectionView = collectionView;
+}
+
+#pragma mark - Change
+
+- (void)controller:(NSFetchedResultsController *)controller
+  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
+           atIndex:(NSUInteger)sectionIndex
+     forChangeType:(NSFetchedResultsChangeType)type
+{
+    NSMutableDictionary *change = [NSMutableDictionary new];
+    
+    switch(type) {
+        case NSFetchedResultsChangeInsert:
+            change[@(type)] = @(sectionIndex);
+            break;
+        case NSFetchedResultsChangeDelete:
+            change[@(type)] = @(sectionIndex);
+            break;
+        case NSFetchedResultsChangeUpdate:
+            change[@(type)] = @(sectionIndex);
+            break;
+        case NSFetchedResultsChangeMove:
+            // Not handled
+            break;
+    }
+    
+    [self.sectionChanges addObject:change];
+}
+
+- (void)controller:(NSFetchedResultsController *)controller
+   didChangeObject:(id)anObject
+       atIndexPath:(NSIndexPath *)indexPath
+     forChangeType:(NSFetchedResultsChangeType)type
+      newIndexPath:(NSIndexPath *)newIndexPath
+{
+    NSMutableDictionary *change = [NSMutableDictionary new];
+    
+    switch(type) {
+        case NSFetchedResultsChangeInsert:
+            change[@(type)] = newIndexPath;
+            break;
+        case NSFetchedResultsChangeDelete:
+            change[@(type)] = indexPath;
+            break;
+        case NSFetchedResultsChangeUpdate:
+            change[@(type)] = indexPath;
+            break;
+        case NSFetchedResultsChangeMove:
+            change[@(type)] = @[indexPath, newIndexPath];
+            break;
+    }
+    
+    [self.objectChanges addObject:change];
+}
+
+- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
+{    
+    if ([self.sectionChanges count] > 0) {
+        [self.collectionView performBatchUpdates:^{
+            for (NSDictionary *change in self.sectionChanges) {
+                [change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id sectionIndex, BOOL *stop) {
+                    NSFetchedResultsChangeType type = [key unsignedIntegerValue];
+                    switch (type) {
+                        case NSFetchedResultsChangeInsert:
+                            [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:[sectionIndex unsignedIntegerValue]]];
+                            break;
+                        case NSFetchedResultsChangeDelete:
+                            [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:[sectionIndex unsignedIntegerValue]]];
+                            break;
+                        case NSFetchedResultsChangeUpdate:
+                            [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:[sectionIndex unsignedIntegerValue]]];
+                            break;
+                        case NSFetchedResultsChangeMove:
+                            [self.collectionView moveSection:[sectionIndex[0] unsignedIntegerValue]
+                                                   toSection:[sectionIndex[1] unsignedIntegerValue]];
+                            break;
+                    }
+                }];
+            }
+        } completion:nil];
+    }
+    
+    if ([self.objectChanges count] > 0 && [self.sectionChanges count] == 0) {
+        if ([self shouldReloadCollectionViewToPreventKnownIssue]) {
+            // This is to prevent a bug in UICollectionView from occurring.
+            // The bug presents itself when inserting the first object or deleting the last object in a collection view.
+            // http://stackoverflow.com/questions/12611292/uicollectionview-assertion-failure
+            // This code should be removed once the bug has been fixed, it is tracked in OpenRadar
+            // http://openradar.appspot.com/12954582
+            [self.collectionView reloadData];
+        } else {
+            [self.collectionView performBatchUpdates:^{
+                for (NSDictionary *change in _objectChanges) {
+                    [change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id indexPath, BOOL *stop) {
+                        NSFetchedResultsChangeType type = [key unsignedIntegerValue];
+                        switch (type) {
+                            case NSFetchedResultsChangeInsert:
+                                [self.collectionView insertItemsAtIndexPaths:@[indexPath]];
+                                break;
+                            case NSFetchedResultsChangeDelete:
+                                [self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
+                                break;
+                            case NSFetchedResultsChangeUpdate:
+                                // If you call reload here, you can run into a Core Data crash,
+                                // where you see [__NSArrayM insertObject:atIndex:]: object cannot be nil.
+                                // To be honest, I'm not sure what this is, could be a bug.
+                                // [self.collectionView     :@[indexPath]];
+
+                                // Instead, use reload here.
+                                [self.dataSourceDelegate configureCell:[self.collectionView cellForItemAtIndexPath:indexPath]
+                                                           atIndexPath:indexPath];
+                                break;
+                            case NSFetchedResultsChangeMove:
+                                [self.collectionView moveItemAtIndexPath:indexPath[0] toIndexPath:indexPath[1]];
+                                break;
+                        }
+                    }];
+                }
+            } completion:nil];
+        }
+        
+        [self.sectionChanges removeAllObjects];
+        [self.objectChanges removeAllObjects];
+    }
+}
+
+- (BOOL)shouldReloadCollectionViewToPreventKnownIssue
+{
+    __block BOOL shouldReload = NO;
+    for (NSDictionary *change in self.objectChanges) {
+        [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
+            NSFetchedResultsChangeType type = [key unsignedIntegerValue];
+            NSIndexPath *indexPath = obj;
+            switch (type) {
+                case NSFetchedResultsChangeInsert:
+                    if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) {
+                        shouldReload = YES;
+                    } else {
+                        shouldReload = NO;
+                    }
+                    break;
+                case NSFetchedResultsChangeDelete:
+                    if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) {
+                        shouldReload = YES;
+                    } else {
+                        shouldReload = NO;
+                    }
+                    break;
+                case NSFetchedResultsChangeUpdate:
+                    shouldReload = NO;
+                    break;
+                case NSFetchedResultsChangeMove:
+                    shouldReload = NO;
+                    break;
+            }
+        }];
+    }
+    return shouldReload;
+}
+
+@end
diff --git a/StoreMad/SMContextObserverController.h b/StoreMad/SMContextObserverController.h
new file mode 100644
index 0000000..2e60a19
--- /dev/null
+++ b/StoreMad/SMContextObserverController.h
@@ -0,0 +1,94 @@
+//
+//  SMContextObserverController.h
+//  StoreMad
+//
+//  Created by Andrew Smith on 3/13/14.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@interface SMContextObserverController : NSObject
+
++ (instancetype)defaultController;
+
+/**
+ Returns an opaque class of SMContextObserver. The workBlock will be executed
+ every time the context fires a notification of the notificationType. For instance, 
+ you could set a predicate to observe a specific object change in a given context
+ every time the context saves, firing a NSManagedObjectContextDidSaveNotification.
+ 
+ Optionally, you can pass in an NSOperationQueue to run the workBlock on. This is
+ useful if you have a long running task. If nil, the workBlock will be executed on
+ whatever thread the context fires the NSNotifiation. Most likely this is the main thread.
+ 
+ @param context The context to observe
+ @param predicate The predicate used to filter
+ @param notificationName The NSManagedObjectContext notifications. Valid values
+ include NSManagedObjectContextDidSaveNotification, NSManagedObjectContextWillSaveNotification,
+ and NSManagedObjectContextObjectsDidChangeNotification.
+ @param queue The NSOperationQueue to run the workBlock on. If nil, the workBlock will
+ be run on the main thread.
+ @param workBlock The work block to run on the updated, inserted, and deleted objects
+ involved in the notification.
+ */
+- (id)addContextObserverForContext:(NSManagedObjectContext *)context
+                         predicate:(NSPredicate *)predicate
+           contextNotificationType:(NSString *)notificationType
+                             queue:(NSOperationQueue *)queue
+                         workBlock:(void (^)(NSSet *updateObjects,
+                                             NSSet *insertedOjects,
+                                             NSSet *deletedObjects))workBlock;
+
+/**
+ Returns an opaque class of SMContextObserver. The workBlock will be executed
+ every time the context fires a notification of type NSManagedObjectContextObjectsDidChangeNotification.
+ 
+ @param context The context to observe
+ @param workBlock The work block to run on the updated, inserted, and deleted objects
+ involved in the notification.
+ */
+- (id)addContextObserverForChangesToObject:(NSManagedObject *)object
+                                 workBlock:(void (^)(NSManagedObject *object))workBlock;
+
+/**
+ Returns an opaque class of SMContextObserver. The workBlock will be executed
+ every time the context fires a notification of type NSManagedObjectContextObjectsDidChangeNotification.
+ 
+ Optionally, you can pass in an NSOperationQueue to run the workBlock on. This is
+ useful if you have a long running task. If nil, the workBlock will be executed on
+ whatever thread the context fires the NSNotifiation. Most likely this is the main thread.
+ 
+ @param context The context to observe
+ @param queue Optional. The NSOperationQueue to run the workBlock on.
+ @param workBlock The work block to run on the updated, inserted, and deleted objects
+ involved in the notification.
+ */
+- (id)addContextObserverForChangesToObject:(NSManagedObject *)object
+                                     queue:(NSOperationQueue *)queue
+                                 workBlock:(void (^)(NSManagedObject *object))workBlock;
+
+/**
+ Removes the observer from recieving any further notifications.
+ */
+- (void)removeContextObserver:(id)observer;
+
+@end
diff --git a/StoreMad/SMContextObserverController.m b/StoreMad/SMContextObserverController.m
new file mode 100644
index 0000000..604c580
--- /dev/null
+++ b/StoreMad/SMContextObserverController.m
@@ -0,0 +1,263 @@
+//
+//  SMContextObserverController.m
+//  StoreMad
+//
+//  Created by Andrew Smith on 3/13/14.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "SMContextObserverController.h"
+
+#import "NSManagedObject+StoreMad.h"
+
+#pragma mark - SMContextObserver
+
+typedef void (^SMContextObserverBlock)(NSSet *updateObjects, NSSet *insertedOjects, NSSet *deletedObjects);
+
+@interface SMContextObserver : NSObject
+
+@property (nonatomic, strong) NSManagedObjectContext *context;
+
+@property (nonatomic, strong) NSPredicate *predicate;
+
+@property (nonatomic, strong) NSString *notificationType;
+
+@property (nonatomic, strong) NSOperationQueue *queue;
+
+@property (nonatomic, copy) SMContextObserverBlock workBlock;
+
+@end
+
+@implementation SMContextObserver
+
+- (void)dealloc
+{
+    [self stopObservingNotifications];
+}
+
+#pragma mark - Notifications
+
+- (void)startObservingNotifications
+{
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(notificationDidFire:)
+                                                 name:self.notificationType
+                                               object:self.context];
+}
+
+- (void)stopObservingNotifications
+{
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:self.notificationType
+                                                  object:self.context];
+}
+
+- (void)notificationDidFire:(NSNotification *)notification
+{
+    if (_queue) {
+        __weak SMContextObserver *weakSelf = self;
+        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
+            __strong SMContextObserver *strongSelf = weakSelf;
+            [strongSelf processContextNotification:notification];
+        }];
+        [_queue addOperation:op];
+    } else {
+        [self processContextNotification:notification];
+    }
+}
+
+- (void)processContextNotification:(NSNotification *)notification
+{
+    // Bail if no work block
+    if (!self.workBlock) return;
+
+    //
+    // Grab objects from notification.  These are standard keys, as defined by docs
+    //
+    NSSet *updatedObjects  = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
+    NSSet *insertedObjects = [[notification userInfo] objectForKey:NSInsertedObjectsKey];
+    NSSet *deletedObjects  = [[notification userInfo] objectForKey:NSDeletedObjectsKey];
+    
+    //
+    // Filter sets using predicate
+    //
+    if (!self.predicate) self.predicate = [NSPredicate predicateWithFormat:@"1 = 1"];
+    NSSet *updatedObjectsFiltered  = [updatedObjects filteredSetUsingPredicate:self.predicate];
+    NSSet *insertedObjectsFiltered = [insertedObjects filteredSetUsingPredicate:self.predicate];
+    NSSet *deletedObjectsFiltered  = [deletedObjects filteredSetUsingPredicate:self.predicate];
+    
+    //
+    // Bail if no changes
+    //
+    if (updatedObjectsFiltered.count == 0
+        && insertedObjectsFiltered.count == 0
+        && deletedObjectsFiltered.count == 0) return;
+    
+    //
+    // Perform work
+    //
+    self.workBlock(updatedObjectsFiltered, insertedObjectsFiltered, deletedObjectsFiltered);
+}
+
+@end
+
+#pragma mark - SMContextObserverController
+
+@interface SMContextObserverController ()
+
+@property (nonatomic, strong) NSMutableArray *observers;
+
+@end
+
+@implementation SMContextObserverController
+
+- (id)init
+{
+    self = [super init];
+    if (self) {
+        _observers = [NSMutableArray array];
+    }
+    return self;
+}
+
+#pragma mark - Singleton
+
++ (instancetype)defaultController
+{
+    static SMContextObserverController *_defaultController = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        _defaultController = [[self alloc] init];
+    });
+    return _defaultController;
+}
+
+#pragma mark - API
+
+- (id)addContextObserverForContext:(NSManagedObjectContext *)context
+                         predicate:(NSPredicate *)predicate
+           contextNotificationType:(NSString *)notificationType
+                             queue:(NSOperationQueue *)queue
+                         workBlock:(void (^)(NSSet *updateObjects,
+                                             NSSet *insertedOjects,
+                                             NSSet *deletedObjects))workBlock
+{
+    //
+    // Validate context
+    //
+    NSAssert(context, @"Tried to add an observer to a nil NSManagedObjectContext!");
+    if (!context) return nil;
+    
+    //
+    // Validate notification type
+    //
+    NSArray *validNotifications = @[NSManagedObjectContextDidSaveNotification,
+                                    NSManagedObjectContextWillSaveNotification,
+                                    NSManagedObjectContextObjectsDidChangeNotification];
+    BOOL valid = [validNotifications containsObject:notificationType];
+    NSAssert(valid, @"Notification type must be one of the following: NSManagedObjectContextDidSaveNotification, NSManagedObjectContextWillSaveNotification, NSManagedObjectContextObjectsDidChangeNotification");
+    if (!valid) return nil;
+    
+    //
+    // Build observer
+    //
+    SMContextObserver *observer = [SMContextObserver new];
+    observer.context = context;
+    observer.predicate = predicate;
+    observer.notificationType = notificationType;
+    observer.queue = queue;
+    observer.workBlock = workBlock;
+    
+    //
+    // Turn on the observer
+    //
+    [observer startObservingNotifications];
+    
+    //
+    // Keep track of observer
+    //
+    @synchronized (_observers) {
+        [_observers addObject:observer];
+    }
+    
+    return observer;
+}
+
+- (id)addContextObserverForChangesToObject:(NSManagedObject *)object
+                                 workBlock:(void (^)(NSManagedObject *object))workBlock
+{
+    return [self addContextObserverForChangesToObject:object
+                                                queue:nil
+                                            workBlock:workBlock];
+}
+
+- (id)addContextObserverForChangesToObject:(NSManagedObject *)object
+                                     queue:(NSOperationQueue *)queue
+                                 workBlock:(void (^)(NSManagedObject *object))workBlock
+{
+    NSAssert(object.stm_hasBeenSaved, @"Object must be saved first to observe. Right now it only has a temporary objectID");
+    
+    //
+    // Search for the object we pushed in
+    //
+    NSPredicate *predicate  = [NSPredicate predicateWithFormat:@"objectID == %@", object.objectID];;
+    
+    //
+    // Wrap the work block.
+    //
+    SMContextObserverBlock newWorkBlock = ^(NSSet *updateObjects,
+                                            NSSet *insertedOjects,
+                                            NSSet *deletedObjects) {
+        NSManagedObject *fetchedObject;
+        
+        if (updateObjects.count > 0) {
+            fetchedObject = [updateObjects anyObject];
+        } else if (insertedOjects.count > 0) {
+            fetchedObject = [insertedOjects anyObject];
+        } else if (deletedObjects.count > 0) {
+            fetchedObject = [deletedObjects anyObject];
+        }
+        
+        if (workBlock) {
+            workBlock(fetchedObject);
+        }
+    };
+    
+    //
+    // Build the observer
+    //
+    id observer = [self addContextObserverForContext:object.managedObjectContext
+                                           predicate:predicate
+                             contextNotificationType:NSManagedObjectContextObjectsDidChangeNotification
+                                               queue:queue
+                                           workBlock:newWorkBlock];
+    
+    return observer;
+}
+
+- (void)removeContextObserver:(SMContextObserver *)observer
+{
+    [observer stopObservingNotifications];
+    @synchronized (_observers) {
+        [_observers removeObject:observer];
+    }
+}
+
+@end
diff --git a/StoreMad/SMDataSource.h b/StoreMad/SMDataSource.h
new file mode 100644
index 0000000..a0b2f33
--- /dev/null
+++ b/StoreMad/SMDataSource.h
@@ -0,0 +1,74 @@
+//
+//  SMDataSource.h
+//  StoreMad
+//
+//  Created by Andrew Smith on 7/1/13.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@protocol SMDataSourceDelegate;
+
+@interface SMDataSource : NSObject <NSFetchedResultsControllerDelegate>
+
+@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
+
+@property (nonatomic, weak) id <SMDataSourceDelegate> dataSourceDelegate;
+
+- (void)setupWithFetchRequest:(NSFetchRequest *)fetchRequest
+                      context:(NSManagedObjectContext *)context
+           sectionNameKeyPath:(NSString *)sectionNameKeyPath
+                    cacheName:(NSString *)cacheName;
+
+- (void)performFetch;
+
+- (void)performFetchWithNewFetchRequest:(NSFetchRequest *)fetchRequest;
+
+- (id)objectAtIndexPath:(NSIndexPath *)index;
+
+- (NSInteger)numberOfSections;
+
+- (NSInteger)numberOfItemsInSection:(NSInteger)section;
+
+- (NSString *)titleForHeaderInSection:(NSInteger)section;
+
+- (NSArray *)sectionIndexTitles;
+
+- (NSInteger)sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;
+
+- (BOOL)isEmpty;
+
+@end
+
+@protocol SMDataSourceDelegate <NSObject>
+
+- (void)dataSource:(SMDataSource *)dataSource
+           isEmpty:(BOOL)empty;
+
+- (void)configureCell:(id)cell
+          atIndexPath:(NSIndexPath *)indexPath;
+
+@optional
+
+- (void)fetchResultsDidChange;
+
+@end
diff --git a/StoreMad/SMDataSource.m b/StoreMad/SMDataSource.m
new file mode 100644
index 0000000..2ecc396
--- /dev/null
+++ b/StoreMad/SMDataSource.m
@@ -0,0 +1,146 @@
+//
+//  SMDataSource.m
+//  StoreMad
+//
+//  Created by Andrew Smith on 7/1/13.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "SMDataSource.h"
+#import "SMStoreController.h"
+
+@implementation SMDataSource
+
+- (void)dealloc
+{
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)setupWithFetchRequest:(NSFetchRequest *)fetchRequest
+                      context:(NSManagedObjectContext *)context
+           sectionNameKeyPath:(NSString *)sectionNameKeyPath
+                    cacheName:(NSString *)cacheName
+{
+    // Build controller
+    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
+                                                                        managedObjectContext:context
+                                                                          sectionNameKeyPath:sectionNameKeyPath
+                                                                                   cacheName:cacheName];
+    
+    // Respond to changes here
+    self.fetchedResultsController.delegate = self;
+
+    // Respond to a reset of the store controller
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(storeControllerDidReset:)
+                                                 name:kSMStoreControllerDidReset
+                                               object:nil];
+}
+
+#pragma mark - Reset
+
+- (void)storeControllerDidReset:(NSNotification *)notification
+{
+    SMStoreController *controller = notification.object;
+    if (![controller.managedObjectContext isEqual:self.fetchedResultsController.managedObjectContext]) {
+        return;
+    }
+    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:self.fetchedResultsController.fetchRequest
+                                                                        managedObjectContext:self.fetchedResultsController.managedObjectContext
+                                                                          sectionNameKeyPath:self.fetchedResultsController.sectionNameKeyPath
+                                                                                   cacheName:self.fetchedResultsController.cacheName];
+    self.fetchedResultsController.delegate = self;
+    [self.fetchedResultsController performFetch:nil];
+}
+
+#pragma mark - Fetching
+
+- (void)performFetch
+{
+    [self.fetchedResultsController performFetch:nil];
+}
+
+- (void)performFetchWithNewFetchRequest:(NSFetchRequest *)fetchRequest
+{
+    [NSFetchedResultsController deleteCacheWithName:self.fetchedResultsController.cacheName];
+    
+    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
+                                                                        managedObjectContext:self.fetchedResultsController.managedObjectContext
+                                                                          sectionNameKeyPath:self.fetchedResultsController.sectionNameKeyPath
+                                                                                   cacheName:self.fetchedResultsController.cacheName];
+    
+    // Respond to changes here
+    self.fetchedResultsController.delegate = self;
+
+    // Auto resend the fetch
+    [self performFetch];
+}
+
+#pragma mark - Helper
+
+- (id)objectAtIndexPath:(NSIndexPath *)index
+{
+    return [self.fetchedResultsController objectAtIndexPath:index];
+}
+
+#pragma mark - UICollectionView data source helpers
+
+- (NSInteger)numberOfSections
+{
+    //
+    // Report if empty
+    //
+    BOOL empty = (self.fetchedResultsController.fetchedObjects.count == 0);
+    [self.dataSourceDelegate dataSource:self isEmpty:empty];
+    
+    return self.fetchedResultsController.sections.count;
+}
+
+- (NSInteger)numberOfItemsInSection:(NSInteger)section
+{
+    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
+
+    [self.dataSourceDelegate dataSource:self isEmpty:self.isEmpty];
+
+    return [sectionInfo numberOfObjects];
+}
+
+- (NSString *)titleForHeaderInSection:(NSInteger)section
+{
+    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
+    return [sectionInfo name];
+}
+
+- (NSArray *)sectionIndexTitles
+{
+    return [self.fetchedResultsController sectionIndexTitles];
+}
+
+- (NSInteger)sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
+{
+    return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
+}
+
+- (BOOL)isEmpty
+{
+    return (self.fetchedResultsController.fetchedObjects.count < 1);
+}
+
+@end
diff --git a/StoreMad/SMStoreController.h b/StoreMad/SMStoreController.h
new file mode 100644
index 0000000..d95ab99
--- /dev/null
+++ b/StoreMad/SMStoreController.h
@@ -0,0 +1,82 @@
+//
+//  SMStoreController.h
+//  StoreMad
+//
+//  Created by Andrew Smith on 7/20/12.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy 
+// of this software and associated documentation files (the "Software"), to deal 
+// in the Software without restriction, including without limitation the rights 
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+// of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included 
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+extern NSString * const kSMStoreControllerDidReset;
+
+@interface SMStoreController : NSObject
+
+/**
+ The URL of the sqlite store, as set when initialized.
+ */
+@property (nonatomic, copy, readonly) NSURL *storeURL;
+
+/**
+ The URL of the model URL, as set when initialized.
+ */
+@property (nonatomic, copy, readonly) NSURL *modelURL;
+
+/**
+ The URL of a previous model for migration, as set when initialized.
+ */
+@property (nonatomic, copy, readonly) NSURL *oldModelURL;
+
+/**
+ Initialized with NSMainQueueConcurrencyType.  Meant as the main thread store.
+ */
+@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
+@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
+@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
+
+/**
+ Set this to YES if you want the internal managedObjectContext to save when the
+ app moves to the background. Set to NO, if otherwise.
+ */
+@property (nonatomic, assign) BOOL saveOnAppStateChange;
+
++ (SMStoreController *)storeControllerWithStoreURL:(NSURL *)storeURL
+                                       andModelURL:(NSURL *)modelURL
+                                       oldModelURL:(NSURL *)oldModelURL;
+
+/**
+ Delete's the local store and rebuilds it. This will nuke any data you have and
+ give you a fresh sqlite store. So yeah, be careful.
+ */
+- (void)reset DEPRECATED_MSG_ATTRIBUTE("Use resetStore: instead.");
+
+/**
+ Delete's the local store and rebuilds it. This will nuke any data you have and
+ give you a fresh sqlite store. So yeah, be careful.
+ */
+- (BOOL)resetStore:(NSError **)error;
+
+/**
+ This will delete the sqlite store on file. So yeah, be careful.
+ */
+- (void)deleteStore;
+
+@end
diff --git a/StoreMad/SMStoreController.m b/StoreMad/SMStoreController.m
new file mode 100644
index 0000000..c3f2ac7
--- /dev/null
+++ b/StoreMad/SMStoreController.m
@@ -0,0 +1,358 @@
+//
+//  SMStoreController.m
+//  StoreMad
+//
+//  Created by Andrew Smith on 7/20/12.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy 
+// of this software and associated documentation files (the "Software"), to deal 
+// in the Software without restriction, including without limitation the rights 
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+// of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included 
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "SMStoreController.h"
+
+#import <UIKit/UIApplication.h>
+#import "NSManagedObjectContext+StoreMad.h"
+
+NSString * const kSMStoreControllerDidReset = @"kSMStoreControllerDidReset";
+
+@interface SMStoreController ()
+
+@property (nonatomic, copy, readwrite) NSURL *storeURL;
+@property (nonatomic, copy, readwrite) NSURL *modelURL;
+@property (nonatomic, copy, readwrite) NSURL *oldModelURL;
+
+@property (nonatomic, strong, readwrite) NSManagedObjectContext *managedObjectContext;
+@property (nonatomic, strong, readwrite) NSManagedObjectModel *managedObjectModel;
+@property (nonatomic, strong, readwrite) NSPersistentStoreCoordinator *persistentStoreCoordinator;
+
+@end
+
+@implementation SMStoreController
+
+- (void)dealloc
+{
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
++ (SMStoreController *)storeControllerWithStoreURL:(NSURL *)storeURL 
+                                       andModelURL:(NSURL *)modelURL
+                                       oldModelURL:(NSURL *)oldModelURL
+{
+    NSParameterAssert(storeURL);
+    NSParameterAssert(modelURL);
+    
+    SMStoreController *controller = [self new];
+    
+    controller.oldModelURL = oldModelURL;
+    controller.storeURL = storeURL;
+    controller.modelURL = modelURL;
+    
+    // Force lazy load
+    [controller managedObjectContext];
+    
+    return controller;
+}
+
+
+#pragma mark
+
+- (void)reset
+{
+    @synchronized(self) {
+        // Nil local variables
+        _managedObjectContext = nil;
+        _managedObjectModel = nil;
+        _persistentStoreCoordinator = nil;
+        
+        // Delete SQlite
+        [self deleteStore];
+        
+        // Rebuild
+        [self managedObjectContext];
+    }
+}
+
+- (void)deleteStore 
+{	
+    @synchronized(self) {
+        // Ensure we are on the main thread
+        NSAssert([[NSThread currentThread] isEqual:[NSThread mainThread]], @"Delete operation must occur on the main thread");
+        
+        NSFileManager *fileManager = [NSFileManager defaultManager];
+        if (self.storeURL) {
+            [fileManager removeItemAtURL:self.storeURL error:NULL];
+        }
+    }
+}
+
+- (BOOL)resetStore:(NSError **)error
+{
+    // Ensure we are on the main thread
+    NSAssert([[NSThread currentThread] isEqual:[NSThread mainThread]],
+             @"Resetting the store must occur on the main thread");
+    
+    NSError *internalError;
+    if (error != NULL) {
+        *error = internalError;
+    }
+    
+    //
+    // Reset the managed object context
+    //
+    [_managedObjectContext reset];
+    
+	//
+	// Remove all persistent stores
+	//
+	for (NSPersistentStore *store in _persistentStoreCoordinator.persistentStores) {
+		[_persistentStoreCoordinator removePersistentStore:store
+		                                             error:&internalError];
+		if (internalError != nil) {
+			return NO;
+		}
+	}
+
+	//
+	// Delete the underlying SQLite store
+	//
+    if ([[NSFileManager defaultManager] fileExistsAtPath:self.storeURL.path]) {
+        [[NSFileManager defaultManager] removeItemAtURL:self.storeURL
+                                                  error:&internalError];
+    }
+	if (internalError != nil) {
+		return NO;
+	}
+    
+    //
+    // Rebuild store
+    //
+    [self addPersistentStoreToCoordinator:&internalError
+             attemptMigrationWithOldStore:NO
+                              oldModelURL:nil];
+    
+    if (internalError != nil) {
+        return NO;
+    }
+    
+    //
+    // Send update that the store was just reset, and listeners should purge any NSManagedObjects
+    // they are holding onto.
+    //
+    [[NSNotificationCenter defaultCenter] postNotificationName:kSMStoreControllerDidReset
+                                                        object:self];
+
+	return YES;
+}
+
+#pragma mark -
+
+- (void)setSaveOnAppStateChange:(BOOL)saveOnAppStateChange
+{
+    _saveOnAppStateChange = saveOnAppStateChange;
+    
+    if (!saveOnAppStateChange) {
+        [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                        name:UIApplicationDidEnterBackgroundNotification
+                                                      object:nil];
+        
+        [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                        name:UIApplicationWillTerminateNotification
+                                                      object:nil];
+    } else {
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(saveContext)
+                                                     name:UIApplicationDidEnterBackgroundNotification
+                                                   object:nil];
+        
+        [[NSNotificationCenter defaultCenter] addObserver:self
+                                                 selector:@selector(saveContext)
+                                                     name:UIApplicationWillTerminateNotification
+                                                   object:nil];
+    }
+}
+
+#pragma mark - CoreData Stack
+
+- (void)saveContext
+{
+    [self.managedObjectContext stm_queueBlockSaveAndWait];
+}
+
+- (NSManagedObjectContext *)managedObjectContext 
+{
+    if (_managedObjectContext) {
+        return _managedObjectContext;
+    }
+    
+    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
+    if (coordinator) {
+        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
+        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
+    }
+    
+    return _managedObjectContext;
+}
+
+- (NSManagedObjectModel *)managedObjectModel
+{
+    if (_managedObjectModel) {
+        return _managedObjectModel;
+    }
+    
+    NSAssert(_modelURL, @"ModelURL was nil!  Could not find resource");
+    
+    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL];
+    return _managedObjectModel;
+}
+
+- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 
+{
+    if (_persistentStoreCoordinator != nil) {
+        return _persistentStoreCoordinator;
+    }
+    
+    NSError *error = nil;
+    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
+    
+    [self addPersistentStoreToCoordinator:&error
+             attemptMigrationWithOldStore:YES
+                              oldModelURL:self.oldModelURL];
+    
+    if (error) {
+        return nil;
+    }
+    
+    return _persistentStoreCoordinator;
+}
+
+
+
+
+- (BOOL)addPersistentStoreToCoordinator:(NSError **)error
+           attemptMigrationWithOldStore:(BOOL)attemptMigrationWithOldStore
+                            oldModelURL:(NSURL *)oldModelURL
+{
+    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
+                                                   configuration:nil
+                                                             URL:self.storeURL
+                                                         options:nil
+                                                           error:error])
+    {
+        /*
+         Replace this implementation with code to handle the error appropriately.
+         
+         
+         Typical reasons for an error here include:
+         * The persistent store is not accessible;
+         * The schema for the persistent store is incompatible with current managed object model.
+         Check the error message to determine what the actual problem was.
+         
+         
+         If the persistent store is not accessible, there is typically something wrong with the file path. 
+         Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
+         
+         If you encounter schema incompatibility errors during development, you can reduce their frequency by:
+         * Simply deleting the existing store:
+         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
+         
+         * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
+         [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
+         
+         Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
+         
+         */
+        
+        NSLog(@"The model used to open the store is incompatable with the one used to create the store! Performing lightweight migration.");
+        
+        NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption : @(YES),
+                                  NSInferMappingModelAutomaticallyOption       : @(YES)};
+        NSError *migrationError;
+        if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
+                                                       configuration:nil
+                                                                 URL:self.storeURL
+                                                             options:options
+                                                               error:&migrationError]) {
+            
+            NSError *mappingError;
+            NSError *moveError = nil;
+            BOOL migrationSuccess = NO;
+            NSURL *newStoreURL = nil;
+            
+            if (attemptMigrationWithOldStore) {
+                
+                // generate a managed object model for the old schema, we will use it to attempt a migration
+                // between the old and new schemas
+                NSManagedObjectModel *oldModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:oldModelURL];
+                
+                NSMappingModel *mappingModel = [NSMappingModel inferredMappingModelForSourceModel:oldModel
+                                                                                 destinationModel:self.managedObjectModel
+                                                                                            error:&mappingError];
+                // attempt a migration if the mapping model exists
+                if (mappingModel != nil) {
+                    // Create a migration manager to perform the migration.
+                    NSMigrationManager *manager = [[NSMigrationManager alloc]
+                                                   initWithSourceModel:oldModel
+                                                   destinationModel:self.managedObjectModel];
+                    
+                    NSError *migrationError = nil;
+                    
+                    newStoreURL = [self.storeURL URLByAppendingPathExtension:@"foo"];
+                    [[NSFileManager defaultManager] removeItemAtURL:newStoreURL error:nil]; //clear out any existing temporary file
+                    
+                    migrationSuccess = [manager migrateStoreFromURL:self.storeURL
+                                                               type:NSSQLiteStoreType
+                                                            options:nil
+                                                   withMappingModel:mappingModel
+                                                   toDestinationURL:newStoreURL
+                                                    destinationType:NSSQLiteStoreType
+                                                 destinationOptions:nil
+                                                              error:&migrationError];
+                    
+                    if (migrationSuccess) {
+                        
+                        // if the migration succeeds, remove the old sql store and move the new sql store to the old stores location
+                        // on disk
+                        [[NSFileManager defaultManager] removeItemAtURL:self.storeURL error:nil];
+                        [[NSFileManager defaultManager] moveItemAtURL:newStoreURL toURL:self.storeURL error:&moveError];
+                    }
+                }
+            }
+            
+            // if any step in the migration has failed, delete both of the stores
+            if (moveError != nil || !migrationSuccess) {
+                
+                [[NSFileManager defaultManager] removeItemAtURL:self.storeURL error:nil];
+                [[NSFileManager defaultManager] removeItemAtURL:newStoreURL error:nil];
+            }
+            
+            //
+            // If you still can't add a persistent store after all this, throw an error.
+            //
+            if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
+                                                           configuration:nil
+                                                                     URL:self.storeURL
+                                                                 options:options
+                                                                   error:error]) {
+                return NO;
+            }
+        }
+    }
+    return YES;
+}
+
+@end
diff --git a/StoreMad/SMTableViewDataSource.h b/StoreMad/SMTableViewDataSource.h
new file mode 100644
index 0000000..a221b02
--- /dev/null
+++ b/StoreMad/SMTableViewDataSource.h
@@ -0,0 +1,37 @@
+//
+//  SMTableViewDataSource.h
+//  StoreMad
+//
+//  Created by Andrew Smith on 10/4/12.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <UIKit/UIKit.h>
+#import "SMDataSource.h"
+
+@interface SMTableViewDataSource : SMDataSource
+
+- (void)setupWithTableView:(UITableView *)tableView
+              fetchRequest:(NSFetchRequest *)fetchRequest
+                   context:(NSManagedObjectContext *)context
+        sectionNameKeyPath:(NSString *)sectionNameKeyPath
+                 cacheName:(NSString *)cacheName;
+
+@end
diff --git a/StoreMad/SMTableViewDataSource.m b/StoreMad/SMTableViewDataSource.m
new file mode 100644
index 0000000..bd61cfa
--- /dev/null
+++ b/StoreMad/SMTableViewDataSource.m
@@ -0,0 +1,129 @@
+//
+//  SMTableViewDataSource.m
+//  StoreMad
+//
+//  Created by Andrew Smith on 10/4/12.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "SMTableViewDataSource.h"
+
+@interface  SMTableViewDataSource ()
+
+@property (nonatomic, weak) UITableView *tableView;
+
+@end
+
+@implementation SMTableViewDataSource
+
+- (void)setupWithTableView:(UITableView *)tableView
+              fetchRequest:(NSFetchRequest *)fetchRequest
+                   context:(NSManagedObjectContext *)context
+        sectionNameKeyPath:(NSString *)sectionNameKeyPath
+                 cacheName:(NSString *)cacheName
+{
+    self.tableView = tableView;
+    
+    // Normal setup
+    [self setupWithFetchRequest:fetchRequest
+                        context:context
+             sectionNameKeyPath:sectionNameKeyPath
+                      cacheName:cacheName];
+}
+
+#pragma mark - NSFetchedResultsControllerDelegate
+
+- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
+{
+    [self.tableView beginUpdates];
+    
+    // Update sections
+    for (NSInteger i = self.tableView.numberOfSections; i < controller.sections.count; i++) {
+        [self.tableView insertSections:[NSIndexSet indexSetWithIndex:i]
+                      withRowAnimation:UITableViewRowAnimationAutomatic];
+    }
+}
+
+- (void)controller:(NSFetchedResultsController *)controller
+   didChangeObject:(id)anObject
+       atIndexPath:(NSIndexPath *)indexPath
+     forChangeType:(NSFetchedResultsChangeType)type
+      newIndexPath:(NSIndexPath *)newIndexPath
+{
+    switch(type) {
+        case NSFetchedResultsChangeInsert:
+            [self.tableView insertRowsAtIndexPaths:@[newIndexPath]
+                                  withRowAnimation:UITableViewRowAnimationAutomatic];
+            break;
+        case NSFetchedResultsChangeDelete:
+            [self.tableView deleteRowsAtIndexPaths:@[indexPath]
+                                  withRowAnimation:UITableViewRowAnimationAutomatic];
+            break;
+        case NSFetchedResultsChangeUpdate:
+            // If you call reload here, you can run into a Core Data crash,
+            // where you see [__NSArrayM insertObject:atIndex:]: object cannot be nil.
+            // To be honest, I'm not sure what this is, could be a bug.
+//            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
+            
+            // Instead, you can just re-configure the cell at the index path, which
+            // does the same thing.
+            [self.dataSourceDelegate configureCell:[self.tableView cellForRowAtIndexPath:indexPath]
+                                       atIndexPath:indexPath];
+            break;
+        case NSFetchedResultsChangeMove:
+            [self.tableView moveRowAtIndexPath:indexPath
+                                   toIndexPath:newIndexPath];
+            break;
+    }
+}
+
+- (void)controller:(NSFetchedResultsController *)controller
+  didChangeSection:(id)sectionInfo
+           atIndex:(NSUInteger)sectionIndex
+     forChangeType:(NSFetchedResultsChangeType)type
+{
+    switch(type) {
+        case NSFetchedResultsChangeInsert:
+            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
+                          withRowAnimation:UITableViewRowAnimationAutomatic];
+            break;
+        case NSFetchedResultsChangeDelete:
+            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
+                          withRowAnimation:UITableViewRowAnimationAutomatic];
+            break;
+        case NSFetchedResultsChangeMove:
+            break;
+        case NSFetchedResultsChangeUpdate:
+            break;
+        default:
+            break;
+    }
+}
+
+- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
+{
+    [self.tableView endUpdates];
+    
+    if ([self.dataSourceDelegate respondsToSelector:@selector(fetchResultsDidChange)]) {
+        [self.dataSourceDelegate performSelector:@selector(fetchResultsDidChange)];
+    }
+}
+
+@end
diff --git a/StoreMad/StoreMad.h b/StoreMad/StoreMad.h
new file mode 100644
index 0000000..4e5cb8e
--- /dev/null
+++ b/StoreMad/StoreMad.h
@@ -0,0 +1,37 @@
+//
+//  StoreMad.h
+//  StoreMad
+//
+//  Created by Andrew Smith on 3/16/13.
+//  Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#ifndef StoreMad_StoreMad_h
+#define StoreMad_StoreMad_h
+
+#import "SMStoreController.h"
+#import "SMContextObserverController.h"
+#import "SMDataSource.h"
+#import "SMTableViewDataSource.h"
+#import "SMCollectionViewDataSource.h"
+#import "NSManagedObject+StoreMad.h"
+#import "NSManagedObjectContext+StoreMad.h"
+
+#endif