Chris So
Chris So

Reputation: 833

Restkit - Deleting orphaned objects with the same URL path

I had read this document but it does not show any example solution for this problem: RKManagedObjectRequestOperation Class Reference

I want to use several addFetchRequestBlock methods to remove orphaned objects from the same API call I am making. When I remove some record in server side, the parent objects work fine. However, Restkit does not remove those nested objects.


My API JSON output - /regions :

(Region has many Districts and Buildings)

(District has many Buildings)

{
"regions": [
    {
        "id": 1,
        "name": "region A",
        "districts": [
            {
                "id": 1,
                "region_id": 1,
                "name": "district A",
                "buildings": [
                    {
                        "id": 1,
                        "region_id": 1,
                        "district_id": 1,
                        "name": "building A"
                    },
                    {
                        "id": 2,
                        "region_id": 1,
                        "district_id": 1,
                        "name": "building B"
                    }
                ]
            },
            {
                "id": 2,
                "region_id": 1,
                "name": "district B",
                "buildings": []
            },
            {
                "id": 3,
                "region_id": 1,
                "name": "district C",
                "buildings": []
            }
        ]
    },
    {
        "id": 2,
        "name": "region B",
        "districts": [
            {
                "id": 4,
                "region_id": 2,
                "name": "district D",
                "buildings": [
                    {
                        "id": 3,
                        "region_id": 2,
                        "district_id": 4,
                        "name": "building C"
                    }
                ]
            },
            {
                "id": 2,
                "region_id": 1,
                "name": "district E",
                "buildings": []
            }
        ]
    }
]
}

My core data model:

enter image description here


My Object Mapping and addFetchRequestBlock:

(I am trying to use 3 addFetchRequestBlock for deleting orphaned objects; Region, District and Building when the URL path is called)

(1, Restkit deletes those orphaned Region objects correctly)

(2. The deleted objects in District and Building are still in client side)

- (void) setupRegionMappings
{

// Object Mappings
// -------------------------------------------------
// Get RKObjectManager singleton
RKObjectManager *manager = [RKObjectManager sharedManager];



// Get default managed object store
RKManagedObjectStore *managedObjectStore = [RKManagedObjectStore defaultStore];

// Create the RKObjectMapping mapping for our object class:
RKEntityMapping *buildingMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Building class])
                                                  inManagedObjectStore:managedObjectStore];



[buildingMapping addAttributeMappingsFromDictionary:@{
                                                 @"id": @"buildingID",
                                                 @"district_id": @"districtID",
                                                 @"region_id": @"regionID",
                                                 @"name": @"name",
                                                 @"name_zh": @"nameZh",
                                                 @"name_cn": @"nameCn",
                                                 @"name_en": @"nameEn"
                                                 }];

// Identify the object in database
buildingMapping.identificationAttributes = @[@"buildingID"];


// Establish the connection for relation between attributes in core data
[buildingMapping addConnectionForRelationship:@"region" connectedBy:@{@"regionID": @"regionID"}];
[buildingMapping addConnectionForRelationship:@"district" connectedBy:@{@"districtID": @"districtID"}];
[buildingMapping addConnectionForRelationship:@"shops" connectedBy:@{@"buildingID": @"buildingID"}];



// Create the RKObjectMapping mapping for our object class:
RKEntityMapping *districtMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([District class])
                                                  inManagedObjectStore:managedObjectStore];



[districtMapping addAttributeMappingsFromDictionary:@{
                                                 @"id": @"districtID",
                                                 @"region_id": @"regionID",
                                                 @"name": @"name",
                                                 @"name_zh": @"nameZh",
                                                 @"name_cn": @"nameCn",
                                                 @"name_en": @"nameEn"
                                                 }];

// Identify the object in database
districtMapping.identificationAttributes = @[@"districtID"];


// Establish the connection for relation between attributes in core data
[districtMapping addConnectionForRelationship:@"region" connectedBy:@{@"regionID": @"regionID"}];
[districtMapping addConnectionForRelationship:@"buildings" connectedBy:@{@"districtID": @"districtID"}];
[districtMapping addConnectionForRelationship:@"shops" connectedBy:@{@"districtID": @"districtID"}];


// Define the relationship mapping on json
[districtMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"buildings"
                                                                                toKeyPath:@"buildings"
                                                                              withMapping:buildingMapping]];


// Create the RKObjectMapping mapping for our object class:
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Region class])
                                               inManagedObjectStore:managedObjectStore];
[mapping addAttributeMappingsFromDictionary:@{
                                              @"id": @"regionID",
                                              @"name": @"name",
                                              @"name_zh": @"nameZh",
                                              @"name_cn": @"nameCn",
                                              @"name_en": @"nameEn"
                                              }];
// Identify the object in database
mapping.identificationAttributes = @[@"regionID"];

// Establish the connection for relation between attributes in core data
[mapping addConnectionForRelationship:@"districts" connectedBy:@{@"regionID": @"regionID"}];
[mapping addConnectionForRelationship:@"buildings" connectedBy:@{@"regionID": @"regionID"}];
[mapping addConnectionForRelationship:@"shops" connectedBy:@{@"regionID": @"regionID"}];

// Define the relationship mapping on json
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"districts"
                                                                        toKeyPath:@"districts"
                                                                      withMapping:districtMapping]];




// The mapping will be triggered if a response status code is anything in 2xx
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);

// Put it all together in response descriptor (for a GET request method)
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping
                                                                                        method:RKRequestMethodGET
                                                                                   pathPattern:@"regions"
                                                                                       keyPath:@"regions"
                                                                                   statusCodes:statusCodes];


// Add response descriptor to our shared manager
[manager addResponseDescriptor:responseDescriptor];





// Routings
// -------------------------------------------------
// Route for list of type objects
RKRoute *indexRoute = [RKRoute routeWithName:@"regions" pathPattern:@"regions" method:RKRequestMethodGET];
indexRoute.shouldEscapePath = YES;


// Add defined routes to the Object Manager router
[manager.router.routeSet addRoutes:@[indexRoute]];



// Making Consistency
// -------------------------------------------------
// Deleting orphaned objects
// Define Fetch request to trigger on specific url
[manager addFetchRequestBlock:^NSFetchRequest *(NSURL *URL) {

    // Create a path matcher
    RKPathMatcher *pathMatcher = [RKPathMatcher pathMatcherWithPattern:@"regions"];

    // Dictionary to store request arguments
    NSDictionary *argsDict = nil;

    // Match the URL with pathMatcher and retrieve arguments
    BOOL match = [pathMatcher matchesPath:[URL relativePath] tokenizeQueryStrings:NO parsedArguments:&argsDict];


    // If url matched, create NSFetchRequest
    if (match) {
        NSFetchRequest *fetchRequest = [Region MR_requestAllSortedBy:@"regionID" ascending:YES];
        return fetchRequest;
    }

    return nil;
}];


[manager addFetchRequestBlock:^NSFetchRequest *(NSURL *URL) {

    // Create a path matcher
    RKPathMatcher *pathMatcher = [RKPathMatcher pathMatcherWithPattern:@"regions"];

    // Dictionary to store request arguments
    NSDictionary *argsDict = nil;

    // Match the URL with pathMatcher and retrieve arguments
    BOOL match = [pathMatcher matchesPath:[URL relativePath] tokenizeQueryStrings:NO parsedArguments:&argsDict];

    // If url matched, create NSFetchRequest
    if (match) {
        NSFetchRequest *fetchRequest = [District MR_requestAllSortedBy:@"districtID" ascending:YES];
        return fetchRequest;
    }

    return nil;
}];


[manager addFetchRequestBlock:^NSFetchRequest *(NSURL *URL) {
    // Create a path matcher
    RKPathMatcher *pathMatcher = [RKPathMatcher pathMatcherWithPattern:@"regions"];

    // Dictionary to store request arguments
    NSDictionary *argsDict = nil;

    // Match the URL with pathMatcher and retrieve arguments
    BOOL match = [pathMatcher matchesPath:[URL relativePath] tokenizeQueryStrings:NO parsedArguments:&argsDict];

    // If url matched, create NSFetchRequest
    if (match) {
        NSFetchRequest *fetchRequest = [Building MR_requestAllSortedBy:@"buildingID" ascending:YES];
        return fetchRequest;
    }

    return nil;
}];


}

How can I sync these nested entities when I am making the same API call once? Great Thanks!

Upvotes: 0

Views: 462

Answers (2)

Wain
Wain

Reputation: 119031

Only one fetch request block will be used. The correct approach to solve your problem would be to use the Core Data deletion rules on your relationships such that a deletion of a Region results in a cascade deletion of the associated Districts and Buildings.

The fetch request blocks you show in the question delete everything that wasn't received in the last request. Because you are using identificationAttributes and 1:many relationships it is possible for the received response to reconnect existing Districts and Buildings to a new Region before the Region is deleted, so the cascade would not apply to them.

Upvotes: 1

Chris So
Chris So

Reputation: 833

After I delete those addConnectionForRelationship functions. It works perfectly...

Upvotes: 0

Related Questions