RestKit Object Mapping: difficulty using setObjectMapping:forResourcePathPattern:withFetchRequestBlock - objective-c

My setup
The following all works fine:
RKManagedObjectMapping* chanMapping = [RKManagedObjectMapping mappingForClass:[Channel class] inManagedObjectStore:objectStore];
chanMapping.primaryKeyAttribute = #"chanId";
[chanMapping mapKeyPathsToAttributes:
#"id",#"chanId",
#"name", #"chanName",
nil];
[objectManager.mappingProvider setMapping:chanMapping forKeyPath:#"Channels.channel"];
I can call
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/channels" delegate:self];
and I get my channel's from the server and they're stored locally by Core Data. Perfect.
The issue
However, I now wan't to have RestKit automatically delete Channels from the Core Data store that have been removed from the server the next time a GET is performed. I understand this is supported by adding the following:
[objectManager.mappingProvider setObjectMapping:chanMapping forResourcePathPattern:#"/channels" withFetchRequestBlock:^ (NSString *resourcePath) {
return [Channel fetchRequest];
}];
However with this all the Channels get deleted whenever there is anything new on the server.
Things I've tried [UPDATED]
I've debugged using the steps in the answer below. It looks as though the mapping isn't working / is not being found (i.e. I haven't properly associated the mapping with the resource path).
In deleteCachedObjectsMissingFromResult the cachedObjects array looks good, has all the objects that should be there from the last time but the results array is empty which obviously results in [results containsObject:object] always being NO and all the objects being deleted.
Do I need to change something to do with the resource path mapping?

I looked at your updated description. Give this a try:
Switch back to the setObjectMapping:forResourcePathPattern:withFetchRequestBlock
Set the rootKeyPath on the object mapping you register to Channels.channel
Then give it another try. There is some API work in progress to provide URL and keyPath based mapping configuration in a single line, but its not merged to development yet.

Two things to check out to determine why you are seeing the described behavior:
Open up RKManagedObjectLoader and put a breakpoint within isResponseMappable. This method checks if the response was loaded from the cache and performs a load of the objects using the objects returned by the managed object cache if it returns YES. This is probably where you are seeing the return of the cached objects from.
As for the deletion of cached objects, put a breakpoint within deleteCachedObjectsMissingFromResult and see what is going on in there (if you are even making it into the routine).
The scenario to expect automatic pruning would be:
GET /channels returns 2xx status code with new payload
RKManagedObjectLoader performs pruning

Related

After adding a lot of info into Realm one of my items do not work

I've been facing a very strange problem in Realm which doesn't occur every time , but it's been really annoying.
I'm saving a lot of information offline which come from a Webservice using realm. I don't get any exception while saving all these information.
Later I'm able to get all these objects by accessing them through MyObject.allObjects().
These objects contains a property which is an UUID, which I use to download the file related to this object and save it locally, after I finish downloading the file, I update my Realm Object with the path of the file, but some times when I do it:
let pred = NSPredicate(format: "UUID = %#", fileId)
print("This is the select \(pred.predicateFormat)")
let results = IssueFile.objects(with: pred) as! RLMResults<IssueFile>
The variable results is empty, but if I check Realm Database via Realm Browser, I can find the item I'm looking for.
So my question is: why some times Realm can't find the object I'm selecting? As I'm saving a lot of information, is there something I should change in the Realm Configuration?
I've already set the UUID as primary key of the table:
+ (NSString *)primaryKey {
return #"UUID";
}
Thanks,

Cannot use removeObjectForKey in Parse (using Back4App on Objc)

I'm using Back4App's service to host Parse server and I can't seem to successfully remove a field from a row. The field in question is a pointer to another object, and calling removeObjectForKey followed by a save does not work. Other updates work in the same batch, and I have seen that 4 times out the 36 that I have tried, it did successfully delete the object.
[self.myProfile removeObjectForKey:#"partnership"];
[self.myProfile saveEventually:^(BOOL succeeded, NSError * _Nullable error) {
NSLog(#"success %d", succeeded); //always returns true
}];
Is this a known problem with Back4App? Or Parse itself? I tried the same code in swift and it worked.
Any ideas?
When you need to run a callback, to confirm when is deleted, is recommended the deleteInBackgroundWithBlock: or deleteInBackgroundWithTarget:selector: methods. You can delete a single field from an object with the removeObjectForKey method:
// After this, the playerName field will be empty
[classScore removeObjectForKey:#"customName"];
// Saves the field deletion to the Parse Cloud
[classScore saveInBackground];
About the saveEventually, most save functions execute immediately, and inform your app when the save is complete. If you don’t need to know when the save has finished, you can use saveEventually instead. The advantage is that if the user currently doesn’t have a network connection.
Content from Parse =D

Core Data adding entities at runtime

I am rewriting the question to help clarify and get rid of a lot of code I wrote that really doesn't help.
I am using a .xcdatamodel for my initial schema, but I need to add entities to my schema at runtime and therefore I need to add a new NSManagedObjectModel and copy over the existing entities and then add the new entities.
If I create a new NSPersistantStore first and then ask my NSMigrationManager to migrate, I get an error about how it can't move the source model to destination path because file already exists.
If I simply ask my NSMigrationManager to migrate, then it just crashes without any error codes or anything in the debugger.
NSMappingModel *mappingModel = [NSMappingModel inferredMappingModelForSourceModel:originalModel destinationModel:newModel error:&error];
NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:originalModel destinationModel:newModel];
if (![manager migrateStoreFromURL:[originalStore URL]
type:NSSQLiteStoreType
options:[self autoMigrationOptions]
withMappingModel:mappingModel
toDestinationURL:[NSPersistentStore MR_urlForStoreName:[self nextStoreName]]
destinationType:NSSQLiteStoreType
destinationOptions:[self autoMigrationOptions]
error:&error])
{
return NO;
}
The URL's are all good, the mapping model looks good when I log it to the console, the manager exists, etc. In this case I did not create the NSPersistantStore yet, but according to the NSMigrationManager class reference if a store does not exist at the destination URL then one is automatically created.
Anyone have a clue?

Why does my core data object not show up using fetch requests between related objects that were created in the same session?

This is a really weird situation which I have been battling to resolve over the last couple of days.
I have a Card entity with a To-Many relationship on a Transaction entity. I am also using Magical Record to remove the boilerplate code around managing my data store.
My problem occurs in the following order:
Create a Card object
Then create a Transaction object in another view
When I inspect the transaction count with [card.transactions count] I get 1 as the size
However, if I perform a fetch request using the same context the transaction is not there. My predicate looks like this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"card == %# && deleted == 0", card];
So logic says that I am perhaps not saving the context after saving the transaction, but the weird thing is that if I quit the app and then rerun it, the transaction is there. Steps 1-4 work perfectly too if I create the card, quit the app and then run the app and add a transaction. Then my save transaction code works fine.
So my question is, why aren't my objects showing up using fetch requests on a newly created parent object, where "newly created" means that it was created in the same session as the child object.
My code for creating the card looks like this:
GYCard *card = [GYCard MR_createInContext:[NSManagedObjectContext MR_defaultContext]];
[[NSManagedObjectContext MR_defaultContext] MR_save];
Then when I save the transaction my code looks like this:
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_defaultContext];
NSNumber *transactionAmount = [self.numberFormatter numberFromString:self.transactionAmountLabel.text];
GYTransaction *transaction = [GYTransaction MR_createInContext:localContext];
transaction.amount = transactionAmount;
transaction.deleted = [NSNumber numberWithBool:NO];
transaction.card = self.card;
[localContext MR_save];
The code that is not working is this:
+ (int) countTransactions:(GYCard *) card {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"card == %# && deleted == 0", card];
return [GYTransaction MR_countOfEntitiesWithPredicate:predicate inContext:[NSManagedObjectContext MR_defaultContext]];
}
The countTransactions always returns 0 if the card was created in the same session. But it works 100% if I quit the app and relaunch. It also works for adding any new transactions to the card after quitting and relaunching the app.
This appears to be a Magical Record issue. I was using the 2.0 beta branch of Magical Record and after reverting to the tagged version:1.8.3 the issue disappears. So 2.0 seems to be the culprit. It would still be interesting to know however, why version 2.0 is causing this problem.
I would strongly advise anybody who is using Magical Record to avoid the 2.0 branch until it comes out of beta.
Update: After further investigation what the 2.0 beta of Magical Record was doing was generating 2 save notifications for 1 save, this led to my app inadvertently have 2 versions of the card. This resulted in causing the transaction that I was logging to be logged on the first card but the second card obviously did not have a transaction on it. My fetch request was then fetching the second card and returning zero transactions. Quitting and restarting the app then made my app load the correct transaction from the data store and thus worked as expected.
This could be related to the includesPendingChanges property on NSFetchRequest. However the default for this is YES which means any unsaved changes should be included in any queries.
I'm not sure what the MR_ methods are doing under the hood, but the setIncludesPendingChanges: documentation states:
Special Considerations
A value of YES is not supported in conjunction with the result type
NSDictionaryResultType, including calculation of aggregate results
(such as max and min). For dictionaries, the array returned from the
fetch reflects the current state in the persistent store, and does not
take into account any pending changes, insertions, or deletions in the
context.
So I would make sure the MR_countOfEntitiesWithPredicate method is not doing anything funky. Maybe try calling the standard - (NSUInteger)countForFetchRequest:(NSFetchRequest *)request error: (NSError **)error method on your context and see what that returns.

Accessing the media:group of XML returned by GData's Obj-C library for Accessing YouTube Playlists

I have seen a couple similar questions about this, however none of them work correctly.
(I am guessing Google updated the GData Objective-C library since those questions)
The 2 similar questions are:
Load YouTube GData feed for a single video by id
gdata youtube query problem
The way #grobbins explains to do it in the first link is as follows:
for (GDataEntryYouTubeVideo *videoEntry in [feed entries]) {
GDataYouTubeMediaGroup *mediaGroup = [videoEntry mediaGroup];
NSString *videoID = [mediaGroup videoID];
NSArray *mediaContents = [mediaGroup mediaContents];
GDataMediaContent *flashContent =
[GDataUtilities firstObjectFromArray:mediaContents
withValue:#"application/x-shockwave-flash"
forKeyPath:#"type"];
NSLog(#"video ID = %#, flash content URL = %#",
videoID, [flashContent URLString]);
}
The problem is that calling 'mediaGroup' throws 'unrecognized selector sent to instance...'
I have tried this with setting up videoEntry as both GDataEntryBase and GDataEntryYouTubeVideo (which is a subclass of GDataEntryBase) and no matter what the debugger shows the variable videoEntry is always of the type GDataEntryBase (even when specifying GDataEntryYouTubeVideo as above)
I could grab the XML out and parse it myself, but then whats the point of using the library?
(Plus on Googles site it says you should never need to parse the XML directly)
What am I doing wrong?
How do I get the mediaGroup section of a returned Playlist?
FetchFeedWithURL: loads my method when finished, and my method has a header of:
- (void)loadedPlaylist:(GDataServiceTicket*)ticket finishedWithFeed:(GDataFeedBase*)feed error:(NSError*)error;
Found the problem!
The problem was that I forgot to add: -ObjC -all_load, to my Other Linker Flags.
Once I added those, the feed entries are coming back as type GDataEntryYouTubePlaylist.
Hope this helps someone else.