Vlad Brylinskiy
Vlad Brylinskiy

Reputation: 21

RestKit 0.9.3 connectRelationship:withObjectForPrimaryKeyAttribute:

I am trying to use example RKCatalog from new RestKit 0.9.3. In RKRelationshipMappingExample creators have forgotten to add connection code between Task and User, but even after adding it doesn't work correctly.

{"project": {
    "id": 123,
    "name": "Produce RestKit Sample Code",
    "description": "We need more sample code!",
    "user": {
        "id": 1,
        "name": "Blake Watters",
        "email": "[email protected]"
    },
    "tasks": [
        {"id": 1, "name": "Identify samples to write", "assigned_user_id": 1},
        {"id": 2, "name": "Write the code", "assigned_user_id": 1},
        {"id": 3, "name": "Push to Github", "assigned_user_id": 1},
        {"id": 4, "name": "Update the mailing list", "assigned_user_id": 1}
    ]
}}

[taskMapping connectRelationship:@"assignedUser" withObjectForPrimaryKeyAttribute:@"assignedUserID"];

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:gRKCatalogBaseURL];
        objectManager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:@"RKRelationshipMappingExample.sqlite"];

        RKManagedObjectMapping* taskMapping = [RKManagedObjectMapping mappingForClass:[Task class]];
        [taskMapping mapKeyPath:@"id" toAttribute:@"taskID"];
        [taskMapping setPrimaryKeyAttribute:@"taskID"];
        [taskMapping mapKeyPath:@"name" toAttribute:@"name"];
        [taskMapping mapKeyPath:@"assigned_user_id" toAttribute:@"assignedUserID"];
        [taskMapping connectRelationship:@"assignedUser" withObjectForPrimaryKeyAttribute:@"assignedUserID"];
        [objectManager.mappingProvider setMapping:taskMapping forKeyPath:@"task"];

        RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class]];
        [userMapping mapAttributes:@"name", @"email", nil];
        [userMapping mapKeyPath:@"id" toAttribute:@"userID"];
        [userMapping setPrimaryKeyAttribute:@"userID"];
        [userMapping mapRelationship:@"tasks" withMapping:taskMapping];
        [objectManager.mappingProvider setMapping:userMapping forKeyPath:@"user"];

        // NOTE - Project is not backed by Core Data
        RKObjectMapping* projectMapping = [RKObjectMapping mappingForClass:[Project class]];
        [projectMapping mapKeyPath:@"id" toAttribute:@"projectID"];
        [projectMapping mapAttributes:@"name", @"description", nil];
        [projectMapping mapRelationship:@"user" withMapping:userMapping];
        [projectMapping mapRelationship:@"tasks" withMapping:taskMapping];
        [objectManager.mappingProvider setMapping:projectMapping forKeyPath:@"project"];
    }

    return self;
}

Am i doing everything alright? -- it doesn't connect task to user by user's ID.

Upvotes: 2

Views: 1201

Answers (3)

jstevenco
jstevenco

Reputation: 2953

UPDATED: I should have read Blake's linked post more carefully -- it would have pointed me to the development branch more quickly.

BOTTOM LINE: the one-to-many mapping issue (discussed here in the context of the inverse hydration of the User object's tasks) is broken in 0.9.3. A pull of the development branch as of 3/8/2012 confirms that the problem is fixed there -- Blake addressed it in issue #284. You do still need to add the connectRelationship:withObjectForPrimaryKeyAttribute: logic that the example does not contain.


@Evan, thanks for the tip on RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace) -- it's been helpful for profiling what's actually happening.

I'm still very much in the "finding my way" stage of RestKit, so YMMV. However, according to this post by Blake, it actually looks like you need both -- it's the only way I could get the example to work. However, the reference to the primary key on the related object is referred to using it's local [i.e., Task's ivar] attribute:

        RKManagedObjectMapping* taskMapping = [RKManagedObjectMapping mappingForClass:[Task class]];
        [taskMapping mapKeyPath:@"id" toAttribute:@"taskID"];
        [taskMapping mapKeyPath:@"name" toAttribute:@"name"];
        [taskMapping mapKeyPath:@"assigned_user_id" toAttribute:@"assignedUserID"];
        taskMapping.primaryKeyAttribute = @"taskID";

        RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class]];
        [userMapping mapAttributes:@"name", @"email", nil];
        [userMapping mapKeyPath:@"id" toAttribute:@"userID"];
        [userMapping mapRelationship:@"tasks" withMapping:taskMapping];
        userMapping.primaryKeyAttribute = @"userID";
        [objectManager.mappingProvider setMapping:userMapping forKeyPath:@"user"];

        [taskMapping mapRelationship:@"assignedUser" withMapping:userMapping];
        [taskMapping connectRelationship:@"assignedUser" withObjectForPrimaryKeyAttribute:@"assignedUserID"];
        [objectManager.mappingProvider setMapping:taskMapping forKeyPath:@"task"]; 

This was run against a pull from master of about 3 days ago.

Upvotes: 2

Alex Stone
Alex Stone

Reputation: 47348

I've been messing with this code for close to an hour and a half, and finally got it to work. Steps that I've taken: 1) Rearrange the creation of relationships like below 2) Unchecked the "optional" checkmark for the RKRelationshipMappingExample.xcdatamodel > task.assignedUser relationship. I noticed it was both "optional" and minimum 1, maximum 1. I deleted the app after this change and reinstalled it. Now the users are added to tasks and tasks to users!

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:gRKCatalogBaseURL];
        objectManager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:@"RKRelationshipMappingExample.sqlite"];


        RKManagedObjectMapping* taskMapping = [RKManagedObjectMapping mappingForClass:[Task class]];
        [taskMapping mapKeyPath:@"id" toAttribute:@"taskID"];
        [taskMapping mapKeyPath:@"name" toAttribute:@"name"];
        [taskMapping mapKeyPath:@"assigned_user_id" toAttribute:@"assignedUserID"];
        taskMapping.primaryKeyAttribute = @"taskID";

        RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class]];
        [userMapping mapAttributes:@"name", @"email", nil];
        [userMapping mapKeyPath:@"id" toAttribute:@"userID"];

        userMapping.primaryKeyAttribute = @"userID";
        [objectManager.mappingProvider setMapping:userMapping forKeyPath:@"user"];
        [objectManager.mappingProvider setMapping:taskMapping forKeyPath:@"task"];

        [userMapping mapRelationship:@"tasks" withMapping:taskMapping];        
        [taskMapping mapRelationship:@"assignedUser" withMapping:userMapping];

        [taskMapping connectRelationship:@"assignedUser" withObjectForPrimaryKeyAttribute:@"assignedUserID"];


        // NOTE - Project is not backed by Core Data
        RKObjectMapping* projectMapping = [RKObjectMapping mappingForClass:[Project class]];
        [projectMapping mapKeyPath:@"id" toAttribute:@"projectID"];
        [projectMapping mapAttributes:@"name", @"description", nil];
        [projectMapping mapRelationship:@"user" withMapping:userMapping];
        [projectMapping mapRelationship:@"tasks" withMapping:taskMapping];
        [objectManager.mappingProvider setMapping:projectMapping forKeyPath:@"project"];



    }

    return self;
}

//to see NSLog messages, change the LOGGER_DEBUG to 2 (in LoggerClient.m) like this:
#define LOGGER_DEBUG 2
#ifdef NSLog
    #undef NSLog
#endif



//test method to make sure all relationships are created.
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
    _objects = [objects retain];

    for(Project* managedObject in objects)
    {   NSLog(@" project user: %@", [managedObject.user description]);
        NSLog(@"###### user tasks count: %i",[managedObject.user.tasks  count]);
        for(Task* task in managedObject.user.tasks)
        {
            NSLog(@"+++++managedObject.user.tasks: %@", [task description]);
        }

        for(Task* task in managedObject.tasks)
        {
            NSLog(@"*****task: %@", [task description]);
        }

    }
    [self.tableView reloadData];
}

Upvotes: 1

Evan Cordell
Evan Cordell

Reputation: 4118

The user -> task mapping is set up on userMapping, with the line [userMapping mapRelationship:@"tasks" withMapping:taskMapping];. I assume you're saying that the inverse was not created?

If that's the issue, I think you'll find that this commit fixes the issue, though I haven't tried it myself yet. (You could pull the tree at that point or, better, pull the current master branch).

That aside, you have a problem with the line [taskMapping connectRelationship:@"assignedUser" withObjectForPrimaryKeyAttribute:@"assignedUserID"]; It's searching for a an object with a primary key attribute of "assignedUserID", but looking at the other mappings, no such object exists. Note that on userMapping, the primary key is defined by [userMapping setPrimaryKeyAttribute:@"userID"];

You probably want [taskMapping connectRelationship:@"assignedUser" withObjectForPrimaryKeyAttribute:@"userID"];, or even better [taskMapping mapRelationship:@"assignedUser" withMapping:userMapping];

(And with the commit I linked, I'm not even sure if it's necessary to add anything at all to get the inverse - you can try both and see.)

If there are still problems you can turn on logs for the ObjectMapper with RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);

Upvotes: 1

Related Questions