views:

338

answers:

2

My model is setup so Business has many clients, Client has one business. Inverse relationship is setup in the mom file.

I have a unit test like this:

- (void)testNewClientFromBusiness
{
    PTBusiness *business = [modelController newBusiness];
    STAssertTrue([[business clients] count] == 0, @"is actually %d", [[business clients] count]);
    PTClient *client = [business newClient];
    STAssertTrue([business isEqual:[client business]], nil);
    STAssertTrue([[business clients] count] == 1, @"is actually %d", [[business clients] count]);
}

I implement -newClient inside of PTBusiness like this:

- (PTClient *)newClient
{
    PTClient *client = [NSEntityDescription insertNewObjectForEntityForName:@"Client" inManagedObjectContext:[self managedObjectContext]];
    [client setBusiness:self];
    [client updateLocalDefaultsBasedOnBusiness];
    return client;
}

The test fails because [[business clients] count] is still 0 after -newClient is called.

If I impliment it like this:

- (PTClient *)newClient
{
    PTClient *client = [NSEntityDescription insertNewObjectForEntityForName:@"Client" inManagedObjectContext:[self managedObjectContext]];
    NSMutableSet *group = [self mutableSetValueForKey:@"clients"];
    [group addObject:client];
    [client updateLocalDefaultsBasedOnBusiness];
    return client;
}

The tests passes.

My question(s): So am I right in thinking the inverse relationship is only updated when I interact with the mutable set? That seems to go against some other Core Data docs I've read. Is the fact that this is running in a unit test without a run loop have anything to do with it?

Any other troubleshooting recommendations? I'd really like to figure out why I can't set up the relationship at the client end.

Update: Some people have suggested I use -processPendingChanges to for the relationships be updated before the end of the run loop where it typically happens. Doing this is not helping me. Another sample test that fails:

- (void)testAssigningRelationship
{
    // BUG: for some unknow reason in this project i have to assign both ends of the relationsip manually.
    NSURL *modelUrl = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath], @"PTDataModel.momd/schemaVersion1.mom"]];
    NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelUrl]; 

    NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
    [persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:nil];

    NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] init];
    [managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];

    PTBusiness *business = [NSEntityDescription insertNewObjectForEntityForName:@"Business" inManagedObjectContext:managedObjectContext];
    STAssertTrue([[business clients] count] == 0, @"is actually %d", [[business clients] count]);
    PTClient *client = [NSEntityDescription insertNewObjectForEntityForName:@"Client" inManagedObjectContext:managedObjectContext];
    //NSMutableSet *clients = [business valueForKey:@"clients"];
    //[clients addObject:client];
    [client setBusiness:business];
    [managedObjectContext processPendingChanges];
    STAssertTrue([business isEqual:[client business]], @"[client business] is %@", [client business]);
    // fails count is 0
            STAssertTrue([[business clients] count] == 1, @"is actually %d", [[business clients] count]);
    NSSet *clientSet = [business valueForKey:@"clients"];
    // fails count is 0
            STAssertTrue([clientSet count] == 1, @"is actually %d", [clientSet count]);
}

I've tried to recreate the bug in a fresh project that mirrors my own project's core data stack, but the fresh projects seem to work correctly. I'm really at a lose where to troubleshoot next. Sure I can work around this is my code (always use the mutableSet) but I worry about this being the tip of a bigger problem.

+1  A: 

Inverse relationships are only handled at the end of the runloop typically. You can force it to happen earlier though by calling -processPendingChanges on your context.

Also, in your second example using the mutableSetValueForKey: you should have the inverse problem because there your PTClient object's business relationship would be nil.

Ashley Clark
When I use the `mutableSetValueForKey:` the inverse seems to happen immediately as the unit tests do pass.
zorn
Right. Because in your second example you're modifying the set directly (through mutableSetValueForKey:) while in the first example you're relying on CoreData's processing of inverse relationships to update the relationship. In your second example you've just switched which inverse relationship needs to be maintained (Business clients vs. Client business).
Ashley Clark
I'm not understanding you. You said I should have the inverse problem, that my PTClient object's business would be nil. This is not the behavior I'm seeing.More over I've made like 3 new Xcode projects trying to mirror the setup I have in my main project. In all the fresh projects I see good inverse relationship being set both ways. In my main project, even when called processPendingChanges the inverse is only setup when I manipulate the mutableSets.I've spent like 6 hours on this now and have no clue where to look next.
zorn
+2  A: 

Thanks for all your help people but the lack of updated inverse relationships seems to have been caused by a very subtle KCV bug in that inside of PTClient I had a relationship called business and a method (for compatibility with a previous implementation) called -isBusiness.

More info and a sample project demo-ing the bug is on my blog:

http://blog.clickablebliss.com/2010/04/09/walkthrough-of-a-recent-core-data-bug/

Thanks again.

zorn