-
Notifications
You must be signed in to change notification settings - Fork 0
Create and Inject Custom Objects For Complex Core Data Relations
Purpose: This is recipe is used to create a glue object (with attributes) that completes a many-to-many
relationship in Core Data.
For example, a person may belong to a variety of social organizations, which in turn have many members. So you have a Core Data model for Person
and another model for Organization
with a many-to-many relationship between them. But what happens when you want to stick some attributes in there to describe each of those relationships. An example might be an attribute for a person's title or role within an organization (i.e. "President", "Social Coordinator"). These attributes don't really belong in the Organization
model, nor on the Person model
. In such cases, I'll use a third entity, Position
. Position
will have one Person
and one Organization
relationship defined. Modeling that in Core Data is easy enough, but how do you handle this in RestKit's mapping system?
In the following recipe, we will show how to map a SLFCommitteePosition
glue object that connects a SLFLegislator
to a SLFCommittee
. A legislator will sit on many committees, which in turn have many other members who have differing roles and responsibilities.
The JSON for the committee looks like this (truncated for legibility):
{
"id": "MDC000065",
"committee": "HOUSE FACILITIES COMMITTEE",
"updated_at": "2011-03-01 00:44:07",
"state": "md",
"chamber": "lower",
"members": [
{
"leg_id": "MDL000319",
"role": "member",
"name": "Mary Ann Love"
},
{
"leg_id": "MDL000334",
"role": "member",
"name": "LeRoy E. Myers, Jr."
},
{
"leg_id": "MDL000342",
"role": "member",
"name": "Shane E. Pendergrass"
}
]
}
And the JSON for the legislator looks like this (truncated for legibility):
{
"id" : "MDL000319",
"leg_id" : "MDL000319",
"full_name" : "Mary Ann Love",
"district" : "32",
"state" : "md",
"party" : "Democratic",
"chamber" : "lower",
"updated_at" : "2011-07-18 02:31:25",
"roles" : [
{
"term" : "2011-2014",
"level" : "state",
"chamber" : "lower",
"type" : "member"
},
{
"term" : "2011-2014",
"committee_id" : "MDC000043",
"chamber" : "lower",
"committee" : "ECONOMIC MATTERS COMMITTEE",
"type" : "committee member"
},
{
"term" : "2011-2014",
"committee_id" : "MDC000065",
"chamber" : "lower",
"committee" : "HOUSE FACILITIES COMMITTEE",
"type" : "committee member"
}
]
}
So we'll be inferring a committee position from the committee's "members" and from the legislator's "roles". Notice that a primary key does not exist for our CommitteePosition object. So, while mapping, we will compound two other keys (legislator and committee IDs) to create the position's primary key.
The following code block goes in your object loader delegate. (This case, that's the view controller that displays the details for an individual legislator. A similar code block also sits in my CommitteeViewController to catch the mappings coming from the other direction.
- (void)objectLoader:(RKObjectLoader*)loader willMapData:(inout id *)mappableData {
if (loader.objectMapping.objectClass == [SLFLegislator class]) {
NSArray* origRolesArray = [*mappableData valueForKeyPath:@"roles"]; // array of dictionaries
NSString *legID = [*mappableData objectForKey:@"leg_id"]; // this legislator's id
NSString *legName = [*mappableData objectForKey:@"full_name"]; // ... etc.
if (!legID)
legID = self.legislator.legID;
if (!legName)
legName = self.legislator.fullName;
NSMutableArray* newRolesArray = [[NSMutableArray alloc] initWithCapacity:[origRolesArray count]];
int roleIndex = 0;
for (NSDictionary* origRole in origRolesArray) {
//NSString *term = [origRole objectForKey:@"term"]; // our legislative session/year
NSString *comID = [origRole objectForKey:@"committee_id"];
// we use these to create a unique committee position object id
if (!comID || !legID) // include term ??
continue;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K = %@ AND %K == %@",
@"committeeID", comID, @"legID", legID];
SLFCommitteePosition *position = [SLFCommitteePosition objectWithPredicate:predicate];
if (!position) { // we didn't find one already in core data, create one.
position = [SLFCommitteePosition object];
position.committeeID = comID;
position.legID = legID;
}
// It doesn't hurt to just update these properties we know about anyway, I guess.
position.legislatorName = legName;
position.committeeName = [origRole objectForKey:@"committee"]; // committee's name
position.positionType = [origRole objectForKey:@"type"]; // member, or chairperson, etc.
// This seems like a klunky way to generate a unique primary key ID, but the
// aggregation of these three attributes *should* be unique across everything.
position.posID = [NSString stringWithFormat:@"%@|%@", comID, legID]; // include term?
[newRolesArray addObject:position];
roleIndex++;
}
[*mappableData removeObjectForKey:@"roles"]; //remove the old roles array from the legislator
[*mappableData setObject:newRolesArray forKey:@"roles"]; // inject our modified roles array.
[newRolesArray release];
}
}
Now, back in the app delegate (or wherever you normally set up your initial mappings:
RKManagedObjectMapping* posMapping = [RKManagedObjectMapping mappingForClass:[SLFCommitteePosition class]];
posMapping.primaryKeyAttribute = @"posID";
[posMapping mapAttributes:@"posID", @"positionType",@"legID",@"legislatorName",@"committeeID",@"committeeName",nil];
[comMapping mapKeyPath:@"members" toRelationship:@"positions" withObjectMapping:posMapping];
[legMapping mapKeyPath:@"roles" toRelationship:@"positions" withObjectMapping:posMapping];
[objectManager.mappingProvider setObjectMapping:comMapping forKeyPath:@"committee"];
[objectManager.mappingProvider setObjectMapping:legMapping forKeyPath:@"legislator"];
[objectManager.mappingProvider setObjectMapping:posMapping forKeyPath:@"position"];