0
votes

I'm having trouble with RestKit mapping relationships (0.20.3). I've looked at other posts but haven't found a solution yet. I think I'm close but still missing something.

PROBLEM

Entity attributes are mapping just fine into Core Data; however, if the entity has a relationship it isn't getting mapped. I have a workaround in place that I need to get rid of as it is now reaching its limits.

For the workaround, after a successful mapping I have passed the managed object IDs to a foreground thread to hydrate a valid managed object. I then loop through the objects and establish the relationships then "resaving" to Core Data.

The problem with the workaround is that the mapped relationship is part of the unique key for the identificationAttributes parameter. Because the mapping isn't working properly in the background the existing record is not being properly identified and thus duplicate entries occur on future API calls.

BACKGROUND

Example source XML can be downloaded here

The basic structure of the XML is as follows:

<locations>
  <location lat="38.8561" lon="-94.6654" timezone="UTC" city="Overland Park" region="KS" country="US" zipcode="66223" offset="0" local_offset_hours="-5">
    <sfc_ob>
      <attribute1></attribute1>
    </sfc_ob>
    <daily_summaries>
      <daily_summary>
        <attribute2> </attribute2>
      <daily_summary>
    </daily_summaries>
    <hourly_summaries>
      <hourly_summary>
        <attribute3></attribute3>
      </hourly_summary>
    </hourly_summaries>
  </location>
</locations>

My Core Data Entities are as follows:

enter image description hereenter image description hereenter image description here

RESTKIT RELATED CODE

- (GLWeatherManager *)init {
self = [super init];

// setup logging
RKLogConfigureByName("RestKit/Network*", RKLogLevelTrace);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);

self.httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://weather.wdtinc.com"]];
[self.httpClient setDefaultHeader:@"Accept" value:RKMIMETypeXML];
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:@"application/xml"];
self.restKitManager = [[RKObjectManager alloc] initWithHTTPClient:self.httpClient];
self.restKitManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
[self.restKitManager.managedObjectStore createManagedObjectContexts];

// Locations
RKEntityMapping *locationMapping = [self buildMapForLocations];
RKEntityMapping *currentConditionsMapping = [self buildMapForCurrentConditions];
RKEntityMapping *dailySummariesMapping = [self buildMapForDailySummaries];
RKEntityMapping *hourlyForecastsMapping = [self buildMapForHourlyForecasts];

[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"locations.location.daily_summaries" toKeyPath:@"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"locations.location.hourly_summaries" toKeyPath:@"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"locations.location.sfc_ob" toKeyPath:@"currentConditions" withMapping:currentConditionsMapping]];

[currentConditionsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"locations.location" toKeyPath:@"location" withMapping:locationMapping]];
[dailySummariesMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"locations.location" toKeyPath:@"location" withMapping:locationMapping]];
[hourlyForecastsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"locations.location" toKeyPath:@"location" withMapping:locationMapping]];

RKResponseDescriptor *descriptor = [RKResponseDescriptor  responseDescriptorWithMapping:locationMapping pathPattern:@"/feeds/demofeeds20131031/mega.php" keyPath:@"locations.location" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];                        

// add mapping description to objectmanager
[self.restKitManager addResponseDescriptor:descriptor];

RKResponseDescriptor *descriptor2 = [RKResponseDescriptor responseDescriptorWithMapping:currentConditionsMapping pathPattern:@"/feeds/demofeeds20131031/mega.php" keyPath:@"locations.location.sfc_ob"                                                              statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

[self.restKitManager addResponseDescriptor:descriptor2];

RKResponseDescriptor *descriptor3 = [RKResponseDescriptor responseDescriptorWithMapping:dailySummariesMapping                                    pathPattern:@"/feeds/demofeeds20131031/mega.php"                                                 keyPath:@"locations.location.daily_summaries.daily_summary"                                          statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

    [self.restKitManager addResponseDescriptor:descriptor3];

    RKResponseDescriptor *descriptor4 = [RKResponseDescriptor responseDescriptorWithMapping:hourlyForecastsMapping                                               pathPattern:@"/feeds/demofeeds20131031/mega.php"                                                                keyPath:@"locations.location.hourly_summaries.hourly_summary"
                                                                                statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

    [self.restKitManager addResponseDescriptor:descriptor4];

// start the location manager to get the current location
self.locationManager = [[CLLocationManager alloc] init];
[self.locationManager setDelegate:self];
[self.locationManager startUpdatingLocation];

self.locations = [NSMutableArray arrayWithArray:[Locations findAll]];

[self getMegaFeed];

return self;

}


- (RKEntityMapping *)buildMapForLocations {
RKEntityMapping *locationMapping = [RKEntityMapping mappingForEntityForName:@"Locations" inManagedObjectStore:self.restKitManager.managedObjectStore];
[locationMapping addAttributeMappingsFromDictionary:@{
 @"lat" : @"latitude",
 @"lon" : @"longitude",
 @"city" : @"city",
 @"region" : @"region",
 @"country" : @"country",
 @"zipcode" : @"zipcode",
 }];
locationMapping.identificationAttributes = [NSArray arrayWithObject:@"zipcode"];

return locationMapping;
}

    - (RKEntityMapping *)buildMapForCurrentConditions {
    // Current Conditions
    RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"CurrentConditions" inManagedObjectStore:self.restKitManager.managedObjectStore];
    [mapping addAttributeMappingsFromDictionary:@{
     //@"stn" : @"stn",
     //@"location" : @"location",
     //@"stn_lat" : @"stnLatitude",
     //@"stn_lon" : @"stnLongitude",
     @"ob_time.text" : @"observationTime",
     @"day_of_week.text" : @"dayOfWeek",
     @"temp_C.text" : @"temperatureMetric",
     @"temp_F.text" : @"temperatureImperial",
     @"dewp_C.text" : @"dewPointMetric",
     @"dewp_F.text" : @"dewPointImperial",
     @"rh_pct.text" : @"relativeHumidity",
     @"wnd_dir.text" : @"windDirection",
     @"wnd_spd_mph.text" : @"windSpeedImperial",
     @"wnd_spd_kph.text" : @"windSpeedMetric",
     @"press_in.text" : @"pressureImperial",
     @"press_mb.text" : @"pressureMetric",
     @"wx.text" : @"conditionSummary",
     @"wx_code.text" : @"conditionCode",
     @"cld_cover.text" : @"cloudCover",
     @"visibility_ft.text" : @"visibilityImperial",
     @"visibility_m.text" : @"visibilityMetric",
     @"apparent_temp_F.text" : @"feelsLikeTemperatureImperial",
     @"apparent_temp_C.text" : @"feelsLikeTemperatureMetric",
     @"moon_phase.text" : @"moonPhase",
     @"sunrise_utc.text" : @"sunrise",
     @"sunset_utc.text" : @"sunset"
     }];

    [mapping setIdentificationAttributes:[NSArray arrayWithObjects:@"observationTime", nil]];

    return mapping;

}

- (RKEntityMapping *)buildMapForDailySummaries {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"DailySummaries" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:@{
 @"summary_date.text" : @"date",
 @"day_of_week.text" : @"dayOfWeek",
 @"max_temp_F.text" : @"tempMaxImperial",
 @"max_temp_C.text" : @"tempMaxMetric",
 @"min_temp_F.text" : @"tempMinImperial",
 @"min_temp_C.text" : @"tempMinMetric",
 @"wnd_spd_mph.text" : @"windSpeedImperial",
 @"wnd_spd_kph.text" : @"windSpeedMetric",
 @"min_wnd_spd_mph.text" : @"windSpeedMinImperial",
 @"min_wnd_spd_kph.text" : @"windSpeedMinMetric",
 @"max_wnd_spd_mph.text" : @"windSpeedMaxImperial",
 @"max_wnd_spd_kph.text" : @"windSpeedMaxMetric",
 @"wnd_gust_mph.text" : @"windGustImperial",
 @"wnd_gust_kph.text" : @"windGustMetric",
 @"wnd_dir.text" : @"windDirection",
 @"pop.text" : @"probabilityOfPrecipitation",
 @"wx.text" : @"conditionSummary",
 @"wx_code.text" : @"conditionCode",
 @"text_description.text" : @"textDescription",
 @"sunrise_utc.text" : @"sunrise",
 @"sunset_utc.text" : @"sunset",
 @"locations.location.zipcode.text" : @"locationZipcode"
 }];
mapping.identificationAttributes = [NSArray arrayWithObjects:@"date", @"locationZipcode", nil];
[mapping addConnectionForRelationship:@"location" connectedBy:@{@"locationZipcode": @"zipcode"}];
return mapping;

}

- (RKEntityMapping *)buildMapForHourlyForecasts {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"HourlyForecasts" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:@{
 @"day_of_week_utc.text" : @"dayOfWeek",
 @"time_utc.text" : @"forecastTime",
 @"temp_C.text" : @"temperatureMetric",
 @"temp_F.text" : @"temperatureImperial",
 @"dewp_C.text" : @"dewPointMetric",
 @"dewp_F.text" : @"dewPointImperial",
 @"app_temp_C.text" : @"feelsLikeTemperatureMetric",
 @"app_temp_F.text" : @"feelsLikeTemperatureImperial",
 @"rh_pct.text" : @"relativeHumidity",
 @"wx.text" : @"conditionSummary",
 @"wx_code.text" : @"conditionCode",
 @"day_night.text" : @"dayNight",
 @"pop.text" : @"probabilityOfPrecipitation",
 @"sky_cov_pct.text" : @"skyCoverPercent",
 @"wnd_dir.text" : @"windDirection",
 @"wnd_dir_degs.text" : @"windDirectionDegrees",
 @"wnd_spd_mph.text" : @"windSpeedImperial",
 @"wnd_spd_kph.text" : @"windSpeedMetric",
 @"locations.location.zipcode.text" : @"locationZipcode"
 }];

mapping.identificationAttributes = [NSArray arrayWithObjects:@"forecastTime", @"locationZipcode", nil];
[mapping addConnectionForRelationship:@"location" connectedBy:@{@"locationZipcode": @"zipcode"}];
return mapping;

}

- (void)getMegaFeed {
for (Locations *location in self.locations) {
    NSString *path = [NSString stringWithFormat:@"/feeds/demofeeds20131031/mega.php?ZIP=%@&UNITS=all",location.zipcode];
    // fetch data
    [self.restKitManager getObjectsAtPath:path
                               parameters:nil
                                  success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {

                                      NSArray *mappedObjects = [mappingResult array];
                                      NSMutableArray *validObjectIDs = [[NSMutableArray alloc] initWithCapacity:[mappedObjects count]];

                                      for (NSManagedObject *object in mappedObjects) {
                                          NSManagedObjectID *moID = [object objectID];
                                          [validObjectIDs addObject:moID];
                                      }
                                      [self notifyObservers:@selector(megaFeedDidFinish:location:) withObject:validObjectIDs withObject:location];


                                  }
                                  failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                      [REUtility showDefaultAlertWithError:error];
                                      [RELog error:@"%s Hit error:%@", __func__, error];
                                  }];

}

}

UPDATES

@WAIN

I tried changing the code as you suggested. The current conditions object seems to properly receive a location but it is a one-to-one relationship. The others are one-to-many are are still not mapping correctly. Code changes tested are below:

 [locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"daily_summaries" toKeyPath:@"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"hourly_summaries" toKeyPath:@"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"sfc_ob" toKeyPath:@"currentConditions" withMapping:currentConditionsMapping]];

[currentConditionsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"location" toKeyPath:@"location" withMapping:locationMapping]];
[dailySummariesMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"location" toKeyPath:@"location" withMapping:locationMapping]];
[hourlyForecastsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"location" toKeyPath:@"location" withMapping:locationMapping]];
1

1 Answers

1
votes

The paths are wrong for all of your relationship mappings. They currently contains something like @"locations.location.daily_summaries" but should be @"daily_summaries". You should have been seeing in the trace log that no relationships were found for the key path during the mapping process.