CoreData - How to use validateForDelete: to determine if a managed object should be deleted

Viewed 982

Goal: I want to check a managed object to make sure that it is okay for deletion

Expectation: -[NSManagedObject validateForDelete:] should return a BOOL based on the delete rules setup in the managed object model


NSManagedObject Class Reference

validateForDelete:

Determines whether the receiver can be deleted in its current state.

- (BOOL)validateForDelete:(NSError **)error

Parameters

error

If the receiver cannot be deleted in its current state, upon return contains an instance of NSError that describes the problem.

Return Value

YES if the receiver can be deleted in its current state, otherwise NO.

Discussion

An object cannot be deleted if it has a relationship has a “deny” delete rule and that relationship has a destination object.

NSManagedObject’s implementation sends the receiver’s entity description a message which performs basic checking based on the presence or absence of values.

Reading this I assume that I should be able to do something like this:

BOOL canDelete = [myManagedObject validateForDelete:&error];

if (canDelete) {
    [managedObjectContext deleteObject:myManagedObject];
}
else {
    [self requestUserFeedback];
}

However, this method almost always returns NO.


Example Code

Managed Object Model

Department
 - Attribute: name  
     - Value: String
 - Relationship: employees
     - To Many
     - Destination: Employee
     - Delete Rule: Nullify
     - Inverse: department

Employee
 - Attribute: name
     - Value: String
 - Relationship: department
     - To One
     - Destination: Department
     - Delete Rule: Nullify
     - Inverse: employees

Example Code:

NSManagedObject *department = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:self.managedObjectContext];

for (int i = 0; i < 10; i++) {
    NSManagedObject *employee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:self.managedObjectContext];
    [employee setValue:department forKey:@"department"];
    if ([employee validateForDelete:NULL]) {
        NSLog(@"Can delete employee");
    }
    else {
        NSLog(@"WARNING: Cannot delete employee");
    }
}

NSError *error;
BOOL canDelete = [department validateForDelete:&error];
if (canDelete) {
    NSLog(@"Can delete department");
}
else {
    NSLog(@"WARNING: Cannot delete department");
}

Output:

Can delete employee
Can delete employee
Can delete employee
Can delete employee
Can delete employee
Can delete employee
Can delete employee
Can delete employee
Can delete employee
Can delete employee
WARNING: Cannot delete department

To my surprise, canDelete is NO. Why would the department object be invalid for deletion when the employee objects are not invalid? The error returned contains the following (abbreviated)

Error Domain=NSCocoaErrorDomain
Code=1600
UserInfo= <>{
    NSValidationErrorObject=< department object >
    NSValidationErrorKey=employees
    NSValidationErrorValue=Relationship 'employees' on managed object
}

So, validateForDelete: is not returning what I expect for to-many relationships, but does return the expected value for to-one relationships. But, go back to the documentation for a second:

An object cannot be deleted if it has a relationship has [sic] a “deny” delete rule and that relationship has a destination object.

NSManagedObject’s implementation sends the receiver’s entity description a message which performs basic checking based on the presence or absence of values.

This calls out how the default implementation of validateForDelete: works and makes a concerted effort to draw attention to the "deny" delete rule. There is no mention of nullify or cascade delete rules or differences between to-one or to-many relationships.


Further Research

A Google search turns up this hit on the Apple Mailing List archives from 2009, which basically says: -[NSManagedObject validateForDelete:] is a hook for subclassers, you need to implement your own logic in a separate method to determine if an object is valid for deletion.

What? Is this true? That's not what the documentation for this method would lead one to believe:

Determines whether the receiver can be deleted in its current state.


How are we suppose to check if a managed object is in a state that is okay for deletion?

I've set up a small sample project on GitHub that illustrates my findings with both production code and unit tests.

1 Answers
Related