Skip to content
Advertisement

Self-Referencing Data Object in CoreData

I’m making the mental switch from a lifetime of SQL to CoreData and it’s not without a few hiccups. The thing that’s kicking me at the moment is creating a self-referencing object. Allow me to explain.

Using SQL terminology, I have a table that contains the steps in a process. To keep it simple, I’ll say the table holds two pieces of information – the name of the step and the step that comes after it (which may or may not be the next record in the table). All steps in the process are stored in a single table. Each step will ALWAYS have one step that comes after it. But not all steps have a step that comes before it.

In other words, it should look something like this:

desired outcome
(source: justinwhitney.com)

In the SQL world, I would make a single table with an identity field, the name, and a foreign key referencing its own identity field (I guess that would be a domestic key?), thusly:

SQL version
(source: justinwhitney.com)

However, with relationships, there is no such thing as an identity field. Nor is there a select query I can create to pull the info I need.

So what is the best approach for creating an Entity that does the same thing? I tried creating a relationship that inverted to itself and that turned out to be a hard to debug disaster. What are the other options?

Thank you!

Advertisement

Answer

Ah! You provided the vital clue, Martin.

I tried the code sample, but that didn’t quite work. It ended up creating duplicates of everything because both thingA and thingB got inserted into the table. However, the diagram actually gave me what I think might be the key. Previously I had tried assigning nextStep as its own inverse relationship, which was just bonkers. But just adding previousSteps and setting that to Many while nextStep was set to One seems to have led to the solution.

Here’s what I ended up creating for the relationships:

Steps entity Attributes and Relationships
(source: justinwhitney.com)

Here is the plist I used to populate the Steps entity (intended to be run on first-time use or when the database is reset):

DefaultSteps.plist
(source: justinwhitney.com)

And now here is the entity population routine. This is what was tripping me up yesterday:

- (IBAction)populateSteps:(UIButton *)sender {
    
    NSString *responseString;
    
    BOOL isStepsPopulated = [[UserPrefs loadUserData:@"didPopulateSteps"] boolValue];
    
    if (!isStepsPopulated) {
        
        NSDictionary *stepDict = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"DefaultSteps" ofType:@"plist"]];
        NSArray *stepArray = [stepDict objectForKey:@"Steps"];
        
        //1
        __block NSMutableDictionary *stepObjectDict = [[NSMutableDictionary alloc] initWithCapacity:[stepArray count]];
        
        //2
        [stepArray enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
            
            //3
            Steps *thisStep = [NSEntityDescription insertNewObjectForEntityForName:@"Steps" inManagedObjectContext:self.managedContext];
            thisStep.stepName = [dict objectForKey:@"StepName"];
            
            //4
            [stepObjectDict setObject:thisStep forKey:thisStep.stepName];
            
        }];
        
        //5
        [stepArray enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
            Steps *thisStep = [stepObjectDict objectForKey:[dict objectForKey:@"StepName"]];
            Steps *nextStep = [stepObjectDict objectForKey:[dict objectForKey:@"NextStep"]];
            thisStep.nextStep = nextStep;
        }];
        
        NSError *error = nil;
        [self.managedContext save:&error];
        if (error) {
            responseString = [NSString stringWithFormat:@"Error populating Steps: %@", error.description];
        } else {
            responseString = @"Steps have been populated.";
            [UserPrefs saveUserData:[NSNumber numberWithBool:TRUE] forKey:@"didPopulateSteps"];
        }

    } else {
        responseString = @"Steps already populated.";
    }
    
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Populate Steps" message:responseString delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alert show];
}

Here’s the key. At (1) create an NSMutableDictionary to hold the results of the iteration, using the stepName as the Key so it can be referred to later on.

While (2) enumerating through the plist contents, (3) create the first NSEntityDescription just as you did. But instead of creating the second one, (4) add it to the dictionary.

Once the initial enumeration is done, (5) go back through a second time. But this time, create the relationships by referring to the original objects themselves. Make sense?

After that, save the context as normal. The result, in this case, is 5 records, each referencing another record in the same entity, with no conflicts or duplicates.

Final Result
(source: justinwhitney.com)

Now the important question: how do I mark this as answered? Who gets the checkmark?

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement