Reputation: 8001
I have a Core Data model with a Container and Item entities. A Container can have have zero or more Items in it. An Item must belong to at least one Container (but it can be in more than one.)
The relationships look like this:
Container:
Relationship: items, Destination: Item, Inverse: itemContainers
Optional, To-Many Relationship
Delete Rule: Nullify
Item:
Relationship: itemContainers, Destination: Container, Inverse: items
Not-Optional, To-Many Relationship
Delete Rule: Cascade
Problems arise when a Container is deleted. The Item objects in that container are updated, but if the item existed in only one container, the itemContainers property is a set with no objects. Saving the object graph fails because that empty set violates the Item's not-optional setting for itemContainers.
Of course, it's easy enough find the Item objects with empty itemContainers using an NSPredicate like "itemContainers.@count == 0", but it seems like there ought to be a way to configure the model to do this automatically.
So is there an easier/better way?
Upvotes: 2
Views: 870
Reputation: 2870
I like doing it this way:
- (void)didChangeValueForKey:(NSString *)inKey withSetMutation:(NSKeyValueSetMutationKind)inMutationKind usingObjects:(NSSet *)inObjects
{
[super didChangeValueForKey:inKey withSetMutation:inMutationKind usingObjects:inObjects];
if ([inKey isEqualToString:@"YOURRELATIONSHIP"] && self.YOURRELATIONSHIP.count == 0) {
[self.managedObjectContext deleteObject:self];
}
}
Upvotes: 0
Reputation: 41
I tried Tony Arnold's answer above for a similar problem, but found issues when deleting several "Containers" at once (this is on OS X 10.8.2). Containers aren't removed from [item itemContainers]
until the managed object context is saved, so count
remains above 1 and item
never gets deleted.
I came up with the following solution using -[NSManagedObject isDeleted]
and category methods on NSManagedObject
.
File NSManagedObject+RJSNondeletedObjects.h
#import <CoreData/CoreData.h>
@interface NSManagedObject (RJSNondeletedObjects)
- (NSSet *)RJS_nondeletedObjectsForToManyKeyPath:(NSString *)keyPath;
- (BOOL)RJS_hasOtherNondeletedObjectsForToManyKeyPath:(NSString *)keyPath;
@end
File NSManagedObject+RJSNondeletedObjects.m
#import "NSManagedObject+RJSNondeletedObjects.h"
@implementation NSManagedObject (RJSNondeletedObjects)
- (NSSet *)RJS_nondeletedObjectsForToManyKeyPath:(NSString *)keyPath
{
NSSet * result = nil;
id allObjectsForKeyPath = [self valueForKeyPath:keyPath];
if ( ![allObjectsForKeyPath isKindOfClass:[NSSet class]] ) return result;
result = [(NSSet *)allObjectsForKeyPath objectsPassingTest:^BOOL(id obj, BOOL *stop)
{
BOOL testResult = ![obj isDeleted];
return testResult;
}];
return result;
}
- (BOOL)RJS_hasOtherNondeletedObjectsForToManyKeyPath:(NSString *)keyPath
{
BOOL result = NO;
// self will be in the set of nondeleted objects, assuming it's not deleted. So we need to adjust the test threshold accordingly.
NSUInteger threshold = [self isDeleted] ? 0 : 1;
NSSet * nondeletedObjects = [self RJS_nondeletedObjectsForToManyKeyPath:keyPath];
result = ( [nondeletedObjects count] > threshold );
return result;
}
@end
Container
class
...
#import "NSManagedObject+RJSNondeletedObjects.h"
...
- (void)prepareForDeletion
{
NSSet *childItems = [self items];
for (Item *item in childItems) {
if ([item RJS_hasOtherNondeletedObjectsForToManyKeyPath:@"containers"]) {
continue;
}
[managedObjectContext deleteObject:item];
}
}
Upvotes: 4
Reputation: 5966
I don't think you can specify this behavior in your model, butI instead of making that fetch, you could validate the count of itemContainers
in your Container's
- (void)removeItemObject:(Item *)value
{...
if(![[value itemContainers]count])
[context deleteObject:value];
...
}
Upvotes: 0
Reputation: 3592
In my app, I make the item's containers relationship optional, and give access to those containerless items via a 'smart container'.
If you don't want that, I suspect you will just have to handle the save failure, and delete the violating objects.
More and more I am changing my approach to core data to a defensive one: assuming validation will fail, and being prepared to handle it. Becomes even more important when you integrate iCloud sync.
Upvotes: 0
Reputation: 2939
I know it's not as clean as a configuration option offered by Core Data, but I've deployed a few projects where the Container
object cycles through it's child Item
entities when it is deleted, checking if they have 0 itemContainers
(inside 'Container.m'):
- (void)prepareForDeletion
{
NSSet *childItems = [self items];
for (Item *item in childItems) {
if ([[item itemContainers] count] > 1) {
continue;
}
[managedObjectContext deleteObject:item];
}
}
Upvotes: 1