messeb
messeb

Reputation: 377

Core Data & RestKit: Multiple relation to same object

I have a problem with the object mapping from a JSON string to a NSManagedObject. I use RestKit via getObject request and the Core Data integration. And I want to automatically map the JSON response.

1.) I get from a webservice the following response ("A" object):

{
    "Bs":[
        { "id" : "abc", "name" : "name 1"}, 
        { "id" : "def", "name" : "name 2"}
        { "id" : "abc", "name" : "name 1"},
    ],
    "id": "1"
}

2.) This response has ordered values (B Objects), with sometimes the same object ("id" : "abc") more than one time. Further more, the order of the items is important.

3.) Core-Data has not the support to save multiple relations to the same object, because it used a NSSet (NSOrderedSet). And it ignores all double objects.

Has anyone an idea how I could solve the problem?

My try, which fails:

1.) I insert a new core data table (AB), which:

  1. has a reference to A
  2. has a position field
  3. has a reference to one B, from the A

enter image description here

2.) I try to map the object with a RKValueTransformer instance. This iterates over the JSON response for the B instances and create AB objects with the current position. These objects are saved in an NSSet, which return from the custom value transformer

RKValueTransformer *aabbTransformer = [RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class sourceClass, __unsafe_unretained Class destinationClass) {
        return ([sourceClass isSubclassOfClass:[NSArray class]] && [destinationClass isSubclassOfClass:[NSOrderedSet class]]);
    } transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, Class outputValueClass, NSError *__autoreleasing *error) {
        // Validate the input and output
        RKValueTransformerTestInputValueIsKindOfClass(inputValue, [NSArray class], error);
        RKValueTransformerTestOutputValueClassIsSubclassOfClass(outputValueClass, [NSOrderedSet class], error);

        NSMutableOrderedSet *outputSet = [[NSMutableOrderedSet alloc] init];
        NSInteger pos = 1;
        for (id b in inputValue) {
            // see JSON output at the top
            // B instance already exists in core data persistent store
            NSString *bid = [b valueForKeyPath:@"id"];
            B *b = [B bById:bid];

            // create AB instance
            AB *ab = [NSEntityDescription ...]
            ab.b = b;
            ab.position = [NSNumber numberWithInteger:pos];

            [outputSet addObject:ab];
            pos++;
        }
        // return for A.abs
        *outputValue = [[NSOrderedSet alloc] initWithOrderedSet:outputSet];
        return YES;
    }];

    RKAttributeMapping *aabbMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"bs" toKeyPath:@"abs"];
    aabbMapping.valueTransformer = aabbMappingTransformer;

3.) But I get an error: illegal attempt to establish a relationship 'abs' between objects in different contexts But I use always the same context.

If you don't have an better idea, do you have a solution for this problem?

Upvotes: 0

Views: 408

Answers (1)

Wain
Wain

Reputation: 119031

Your proposed model structure is a sensible solution for your duplicate data requirement. The only change you should make there is to ensure that all relationships are double ended (have an inverse) and have appropriate multiplicity. Be sure that the relationship from B to AB is to-many. The relationship from A to AB should also be to-many.

The general approach to this is to use multiple response descriptors:

  1. One to create the A object and AB objects, key path is nil
  2. One to create the B objects without any duplication, key path is Bs

The response descriptor to create the A object has a nested relationship mapping to create the AB objects with duplicates and order - for this you can not use any identification attributes for AB and you need to use the mapping metadata provided by RestKit.

Once the objects are created they then need to be connected and that would be done with a foreign key mapping. That means storing a transient attribute on the AB instances which contains the id for the appropriate B object and using that to make the connection.

Note that after updates you may have some orphaned objects (because you can't use identification attributes for AB) in the data store as a result of this manipulation and you should consider creating a fetch request block to purge them out (or do this yourself periodically).

Upvotes: 0

Related Questions