How to Validate CoreData attribute value for uniqueness
How to Validate CoreData attribute value for uniqueness
- Subject: How to Validate CoreData attribute value for uniqueness
- From: Motti Shneor <email@hidden>
- Date: Sat, 30 Aug 2014 13:58:34 +0300
Hello everyone. Many discussions address this in many development forums. I read them all, and was frustrated NOTHING of the suggested solutions actually worked. Apple's documentation adds insult to injury, completely ignoring such a common need for any real-world Core-Data modeled application.
I have an entity "Species" with a numerical attribute "catalogID" which has "human" meaning (every digit will tell something about the species, plus some free digits). Different species entities must have unique catalog IDs much like social security number for people, or ISBN for books. I have no more than few hundred "Species" entities, and they have many attributes and relations.
My wish is simple. To emit an error dialog when a user tries to use an existing ID when creating a new species in the UI (source/detail window), or when trying to edit the ID of an existing species, changing it to a value taken for another species.
After much reading, I implemented KVC validation (validate<key>:error:) in my NSManagedObject subclass "PMSpecies" like thus:
-(BOOL)validateCatalogID:(id *)ioValue error:(NSError * __autoreleasing *)outError {
// Prepare optimized static request to only fetch all species catalogID's for reuse every time we validate the edited species catalogID.
static NSFetchRequest *allSpeciesIDsFetchRequest = nil;
if (allSpeciesIDsFetchRequest == nil) {
allSpeciesIDsFetchRequest = [NSFetchRequest fetchRequestWithEntityName:[[self entity] name]];
[allSpeciesIDsFetchRequest setPropertiesToFetch:@[ @"catalogID" ]];
[allSpeciesIDsFetchRequest setResultType: NSDictionaryResultType];
}
NSArray *allSpecies = [self.managedObjectContext executeFetchRequest:allSpeciesIDsFetchRequest error:nil];
NSArray *allCatalogIDs = [allSpecies valueForKeyPath:@"catalogID"];
if ([allCatalogIDs containsObject:*ioValue]) {
if (outError != NULL) {
NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"The ID %@ is already taken by another species. Please use a unique ID.", NULL), *ioValue] };
*outError = [[NSError alloc] initWithDomain:@"Model Validation" code:eExistingCatalogID userInfo:userInfoDict];
}
return NO;
}
return YES; // valid ID
}
I was happy enough to see that the bound NSTextField in the UI now behaved correctly, and emitted error upon adding/updating a species with a non-unique ID. However - the moment I try to save my context to the persistent store, I receive hundreds of "uniqueness" errors for every species.
I saw that the above validation method was called for every species on every save, and in those calls, my "fetch" indeed finds one occurrence of the ID --- the actual NSManagedObject's! which is OK.
So this kind of validation can't work because it is used in 2 different situations - BEFORE the value was set to the attribute, and AFTER is was set (upon saving the context). But in this method, I don't have a "context" meaning, I don't know what kind of validation is this.
I tried to implement instead the validateForInsert: and validateForUpate: methods, but then the UI remains stupid, and only when the user actually saves the document (long after closing the "Species" window) he receives strange validation errors. In these implementations, I detect violations by finding 2 or more of the same ID - not just one as the above.
Apple CoreData Programming Guide tells you the following:
NSManagedObject provides consistent hooks for validating property and inter-property values. You typically should not override validateValue:forKey:error:, instead you should implement methods of the form validate<Key>:error:, as defined by the NSKeyValueCoding protocol. If you want to validate inter-property values, you can override validateForUpdate: and/or related validation methods.
But there is absolutely nothing about inter-entity validation, such as value uniqueness, value ordering dependency upon previous entities like serial IDs or any cross-entity validation.
Can anyone shed light on the subject?
At my current situation, I would gladly settle on a UI-only validation (meaning, something that would validate a text field upon end-edit-session, but I don't know how to hook this on a bound-to-model field either.
Please help...
Motti Shneor
---
It is impossible to make anything foolproof, because fools are so ingenious.
- Mark Twain
Make something fool-proof, and only fools will use it.
- My own addition
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden