4

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.

edelaney05
  • 6,822
  • 6
  • 41
  • 65
  • That method is useful if you need to use custom validation before removing an object. So, you can override it to add your custom delete logic there. If you do it, you should call the superclass implementation. – Lorenzo B Dec 21 '13 at 22:15
  • About your model, I cannot understand two things. First, if you have a to-many rel, how can an employee belong to more departments? Second, you should model the employees rel as cascade while the department would be nullify. Take a look at http://stackoverflow.com/questions/15232092/setting-up-a-parent-child-relationship-in-core-data/15233763#15233763. – Lorenzo B Dec 21 '13 at 22:19
  • `Department<<--->>Employee` I went with employee/department because it's the most often used example. You're right, as a problem domain, it doesn't make logical sense - it's just an example for the sake of clarity. :-) – edelaney05 Dec 21 '13 at 22:25
  • Just see the following answers. http://stackoverflow.com/questions/3031790/when-does-the-deny-delete-rule-in-core-data-actually-deny-deletion-of-an-object and http://stackoverflow.com/questions/15545179/core-data-deny-rule-is-not-deleting-destination-objects-related-to-the-source – Lorenzo B Dec 21 '13 at 22:53
  • So, employees would be deny, while departments woul be nullify. If you want to delete a department but it has employees you cannot do it. The method above will return NO, otherwise YES. – Lorenzo B Dec 21 '13 at 23:12
  • 1
    This is very unsatisfying, especially as the documentation states: "The validation constraints are applied by Core Data only during a save operation or upon request (you can invoke the validation methods directly at any time it makes sense for your application flow). " https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/index.html – Morty Apr 11 '16 at 12:55

1 Answers1

0

The way that I read the documentation is that you should subclass it but your subclass should also call super first so that if there is a validation in the framework itself you will capture that as well.

From my experience, -validateForDelete: only ever returns NO on a deny rule.

My recommendation would be to set the relationship rule to "No Action" and then implement the logic in your subclasses.

In your subclass you can check the relationship to department/classroom and if the count is zero then you return YES. Very simple delete validation rule that cannot be implemented in the model.

Marcus S. Zarra
  • 46,571
  • 9
  • 101
  • 182