Reputation: 2645
Using Core Data, I'm looking to setup a tagging system with the following to-many relationship:
article.tags <<------->> tag.articles
In this scenario an article
can have numerous tags
, and those same tags
can be assigned to multiple articles
. Whenever an article
's tags are edited, I remove all previous tags
associated with the article
and re-add those that are still relevant, like so:
// Remove any existing tags from this article
if(article.tags.count)
{
NSSet *existingEventTags = [article.tags copy];
for (NSManagedObject *tag in existingEventTags)
{
[[article mutableSetValueForKey:@"tags"] removeObject:tag];
[moc deleteObject:tag];
}
}
// Now re-assign any applicable tags to this article
for(NSString *tag in tags)
{
Tag *existingTag = [self existingTagWithString:tag];
if(existingTag)
{
[article addTagsObject:existingTag];
}
else
{
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Tag" inManagedObjectContext:moc];
Tag *newTag = (Tag *)[[NSManagedObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:moc];
newTag.name = tag;
[article addTagsObject:newTag];
}
}
My question is how best to handle the delete rules associated with the two relationships. Essentially the behaviour I'm looking for is that when I remove a tag
from an article
, I only want to delete it entirely if no other articles
have been tagged with it.
To accomplish this I've set my tag.articles
relationship to Nullify, and my articles.tags
relationship to Cascade. However when deleting an article any tags associated with it (regardless of whether they're associated with other articles) are removed. To test this I've written a simple debug function which I'll include below:
- (void)debugTags
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Tag" inManagedObjectContext:moc];
[request setEntity:entity];
// Execute the fetch.
NSError *error = nil;
NSArray *objects = [moc executeFetchRequest:request error:&error];
if (objects != nil && [objects count] > 0)
{
NSLog(@"Found %d tags", [objects count]);
for(Tag *tag in objects)
{
NSLog(@"\t%@ (%d events)", tag.name, tag.articles.count);
}
}
else
{
NSLog(@"No tags found!");
}
}
For completeness, here's an example output (showing the creation of two articles
with two shared tags
: food and fastfood). Following the deletion of one article
, I'd expect to still find food and fastfood in the store, but with a relationship count of 1.
Before deletion
---------------
Found 4 tags
breakfast (1 events)
food (2 events)
fastfood (2 events)
lunch (1 events)
After deletion
--------------
Found 1 tags
lunch (1 events)
Upvotes: 1
Views: 666
Reputation: 2645
The best solution I found to this problem was to set both delete rules to Nullify, and to handle the deletion in my article
's NSManagedObject
prepareForDeletion
method:
- (void)prepareForDeletion
{
[super prepareForDeletion];
for (Tag *tag in self.tags)
{
if(tag.events.count == 1)
{
[self.managedObjectContext deleteObject:tag];
}
}
}
Upvotes: 5
Reputation: 7241
Everything is correct except delete rules. With this configuration CoreData do not wont save context with error:
NSValidationErrorKey=events, NSLocalizedDescription=The operation couldn’t be completed. (Cocoa error 1600.), NSValidationErrorValue=Relationship 'articles' on managed object
To accomplish this you should set tag.articles relationship to Nullify, and my articles.tags relationship to Cascade. This will work as expected.
And second. Your code crashed with exception: Collection <_NSFaultingMutableSet: 0x841de10> was mutated while being enumerated. Make a copy of relationships to safely enumerate through it:
NSSet* tags = [[object valueForKey:@"tags"] copy];
for (NSManagedObject *tag in tags) {
[[object mutableSetValueForKey:@"tags"] removeObject:tag];
[context deleteObject:tag];
}
Upvotes: 0