NSFetchresultsController multiple predicate - core-data

I have one CoreData model object that includes a type named sessionID (a uuid generated every time the user uses a feature in the app) - data keep coming in and recorded with the same sessionID for each session.
The recorded data would look like this: x number of lines with lets say sessionID:...1 and then y number of records for sessionID:...2
```
sessionID: 0000-0000-0000-0000-0001, ...
sessionID: 0000-0000-0000-0000-0001, ...
sessionID: 0000-0000-0000-0000-0002, ...
sessionID: 0000-0000-0000-0000-0002, ...
sessionID: 0000-0000-0000-0000-0002, ...
Etc.
```
I am using NSFetchResultsController to present the records to the user on a UITableView, (and it works) However, I would like to have an option toggle between presentation of all records and of only the unique sessionID as rows on the table view. Is that possible with NSFetchResultController?
While I am setting the request to return distinct results, it does not seem to work, I am still getting all the records.
```
[fetchRequest setEntity:entity];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setPropertiesToFetch:#[#"session_id"]];
```

returnsDistinctResults does not work for a fetchedResultsController. Use sectionNameKeyPath of the fetchedResultsController to group by sessionId.
The result will be many sections, with each section containing entries with the same sessionIds.
When you when to show all of the entries then show no header for the section and display every row in every section. When you want to show only the SessionId then show one row per section. You can get the sessionId by looking at the first element in the section.
This does not require you to change the fetchedResultsController at all - you are only changing how you are showing the information. Depending on your UI/UX you may find it easier to have two tableviews and the toggle shows and hides them. Or it might be easier to set a BOOL in your controller and call [tableview reloadData] when you toggle modes.

Related

Core-Data: fetching object count from MOC after saving

Im currently having a problem with my CoreData implementation. I try to query the number of objects with certain properties. I therefore execute the following code
NSUInteger itemsCount = [managedObjectContext countForFetchRequest:fetchRequest error:NULL];
NSLog(#"%i",(int)itemsCount);
So far so good. Everything works if i work with a fresh MOC where no data has ever been saved. The problem occures when i do the following:
I save the whole MOC
Delete one of those objects matching the fetch criteria from the MOC (without saving!)
Requery the Number of objects matching the criteria
The number of objects does not change although i deleted one of the objects.
I also tried the following query ...
fetchRequest.resultType = NSCountResultType;
NSArray* array = [managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
NSLog(#"%i",[[array firstObject] intValue]);
... with the same result.
Then I tried querying the objects directly ...
fetchRequest.resultType = NSManagedObjectResultType;
array = [managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
NSLog(#"%i",(int)[array count]);
... tada: Here it works. The array does not contain the deleted object.
When I set fetchRequest.includesPendingChanges = NO, i of course get all objects because i did not save yet. The property has no effect ob the first two queries though because they show too many objects in the first place.
So my question is, what am i doing wrong and why can i not obtain the right amount of objects once i saved them to the persistence store. Do i have to reset the MOC after saving?
--------------------------------------------------------------------------------
Update
I searched further for similar problems and found some interesting posts. Fetching objects from the MOC means also querying the persistence store. Since I did not save my changes immediately, I do or don't fetch deleted and newly inserted objects respectively. But strangely this only applies for queries involving dictionary result types or count fetches. When querying the objects itself i retrieve the correct number of objects including newly inserted and without the deleted ones.
Now I am wondering on how to deal with my problem. I have a table view which smoothly works with a NSFetchedResultsController (NSFRC). I do not have to save any changes since the controller handles all the updates and notifies me. But as soon as i insert or delete data and the NSFRC notifies me, i want to do further calculations to update my other views. Those calculations include average and number of objects with certain attribute etc.
Right now I update those view as soon as an insert or delete delegate method is being called. But those queries wont return the correct values.
Calling the following before querying has no effect on those queries.
[moc processPendingChanges]
fetch.includePendingChanges = YES
Apparently my approach is lacking something. How do i fetch my needed values correctly ? Since my NSFRC obviously keeps track of deleted and inserted values im curious if it would help to create a second NSFRC just to fetch the averages and so on? Is it even capable of that?
Thanks!

CoreData: How to refresh "calculated" attributes?

My NSFetchedResultsController work great, as long as only "basic" attributes get changed. However if I have a label which is calculated and I'm changing some attributes influencing this label in another view controller on the navigation controller stack, this label doesn't get updated.
For example my label should show the amount of a budget position left saved in the entity SpendingCategory.
self.budgetLeftLabel.text = [NSString stringWithFormat:#"%# %#", [[self.spendingCategory getExpendituresAmount] getLocalizedCurrencyStringWithDigits:0], NSLocalizedString(#"left", nil)];
I derive this value from the category on SpendingCategory with this method:
- (NSNumber *)getExpendituresAmount
{
return [self.hasExpenditures valueForKeyPath:#"#sum.amount"];
}
However this label doesn't get any updates by the NSFetchedResultsController. And I have several locations in my app where this doesn't happen because a value is calculated. What do I need to change that these updates happen?
EDIT with datastructure:
Ok my Spending Category datastructure is roughly (for budget):
name (string)
cost (double)
position (integer 16)
Relationsships: hasExpenditures
My Expenditures structure (for tracking):
amount (double)
date (Date)
description (string)
Relationsships: forSpendingCategory
I hope it's clearer now. So why do these values not get updated?
The NSFetchedResultsController gets tickled when attributes in the relevant NSManagedObject instances are updated. If you are changing something that is purely calculated then the update never fires. Why is this relevant?
If you are changing something in the Expenditures entity (btw, entities should be singular in name) and you are watching the Spending Category entity then the NSFetchedResultsController won't fire because you didn't change anything that is relevant.
How to fix this?
Depends. I normally keep that derived value in the entity and persist it. Further, whenever a child changes a relevant value, I have the parent recalculate. This will cause the NSFetchedResultsController to fire.
How do you watch the values?
Either you have the child call a method on the parent (icky) or you have the parent watch the values on its children via KVO (better). Your personal preference decides here.
Update 1
To keep the derived value in the entity you add a new attributed to the entity and store it. Nothing is special about the attribute. It helps to keep in mind that Core Data is not a database. Core Data is your data model that happens to persist to a database if you so choose. Therefore you want to denormalize the database in cases like this.
while I was searching SO to find a good link for watching children, I stumbled across this example.
KVO object properties within to-many relationship
While the accepted answer is not very good, the second answer, using a NSFetchedResultsController is quite interesting and is worth exploring. The basic idea is that your parent objects instantiate a NSFetchedResultsController on -awakeFromFetch or -awakeFromInsert and when it fires, they recalculate the derived value. Thus the value is always up to date and your view controller based NSFetchedResultController instances will fire because the parent object has changed.
I did something similiar time ago, basically you need to store your calculated value in a transient attribute in your CoreData model, rather than implement your own setter and getter. Then in the related NSManagedObject you need to implement two methods:
// this will populate the values when
// the entity is retrieved from the store
-(void)awakeFromFetch {
[self refreshCellInfo];
}
// this will refresh the values when
// the object goes to fault
// (for example when it is off screen)
-(void)willTurnIntoFault {
[self refreshCellInfo];
}
-(void)refreshCellInfo {
// update all your derived values...
}

Comparison function for Core Data NSFetchedResultsController

I am using NSFetchedResultsController to section my data into separate sections. The main sortDescriptor I give is this:
NSSortDescriptor *sortDescriptorSectionLetter = [[NSSortDescriptor alloc] initWithKey:#"sectionLetter" ascending:YES selector:#selector(localizedCompare:)];
Now my sectionLetter has a default value of "#". When the tableView is sectioned and presented, it places the '#' section at the top of the table. I want this to show up at the bottom of the list (like the Contacts app does it). But I can't figure out how to accomplish this.
I've also tried different types of characters as the default instead of #, like � and ~, but this doesn't seem to work either. According to the Core Data Programming Guide, I can't pass in a customized comparison function here.
So I'm not sure what my options are here (other than doing the sectioning myself and losing all the FRC delegate goodness to reload my tableviews with animation). Any good ideas?
Notice the remark in the documentation for NSFetchedResultsController:
Subclassing Notes
You create a subclass of this class if you want to customize the creation of sections and index titles. You override sectionIndexTitleForSectionName: if you want the section index title to be something other than the capitalized first letter of the section name. You override sectionIndexTitles if you want the index titles to be something other than the array created by calling sectionIndexTitleForSectionName: on all the known sections.

RestKit/CoreData not updating - creating duplicates

I have an ios 5 app which does not create any data - it simply makes a GET call to a REST webservice and populates the sqlite database with those records. The initial GET works great when there are no records in the local database. However when I make subsequent calls, I will only be returning a subset of records whose data has changed since the last GET. But what is happening is that the records are just being added again, not updating the existing records.
I have an ID field which is the primary key (or should be) and when a record comes in whose ID already exists, I want that data to be updated. If that ID does not exist, it should be an insert.
I didn't see a way to set my ID field as a 'primary key' in the datamodel in XCode. I tried doing this in my didFinishLaunchingWIthOptions method:
userMapping.primaryKeyAttribute = #"id";
But that alone didn't really seem to do anything.
This is my call to actually perform the GET:
// Load the object model via RestKit
[objectManager loadObjectsAtResourcePath:[#"/synchContacts" appendQueryParams:params] delegate:self];
Which seems to do everything automagically. I am lost at this point as to where I should be putting logic to check to see if the ID exists, and if so do an update vs an insert, or what.
As of the latest RESTKit version (0.23) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.
You seem to be doing it correctly and when your didLoadObjects callback happens you should be able to query Core Data for the objects you need.
You might be having an issue with the way your fetch requests are being set up. With the latest RestKit you can use RKObjectMappingProvider's
- (void)setObjectMapping:(RKObjectMappingDefinition *)objectMapping forResourcePathPattern:(NSString *)resourcePathPattern withFetchRequestBlock:(RKObjectMappingProviderFetchRequestBlock)fetchRequestBlock;
function and have the fetchRequestBlock fetch the proper data.
RestKit doesn't really handle partial update requests very well out of the box though. You might have more luck on the RestKit google group which is very active.
Quote:
I didn't see a way to set my ID field as a 'primary key' in the datamodel in XCode. I tried doing this in my didFinishLaunchingWIthOptions method:
userMapping.primaryKeyAttribute = #"id";
Keep in mind, the 'primaryKeyAttribute' is the one from your api payload, NOT a CoreData id, which CoreData manages on its own. RestKIt then maps the (invisible) CoreData primary key to the specified JSON key.

Core Data: Can relationship be used for sectionNameKeyPath?

I am trying to do exactly same thing as post in NSFetchResultsController + sectionNameKeyPath + section order, i.e. basically use 2 tables, let's say Categories <-->> Events. Category table consists of category field only, while Event consists of name, dateTimestamp.
I defined relationship 'category' in Events table and try to use that relationship as sectionNameKeyPath when creating fetchedResultsController:
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"category.category" cacheName:#"Root"];
Finally, I pre-populated Category table with some categories upon loading of the app (and verified with .dump that table is populated correctly)
Yet, I simulator fails on:
return [[self.fetchedResultsController sections] count];
I did extensive search and most people either suggest using one of the fields in the table as sectionNameKeyPath (this works!) or transient property (works too!) However, I just want to use relationship as it seems very logical to me in this case where events belong to some categories and there could be categories without events. Am I wrong in my assumption that relationship can be used as sectionNameKeyPath? The original link at the top of the question suggests it works, but guy does not know why or how. Documentation is very weak on what can be used as sectionNameKeyPath, so any help will be highly appreciated.
A relationship gets you a pointer to a managed object. It seems logical, though, that the sectionNameKeyPath parameter should be a key path that leads to a string, since NSFetchedResultsSectionInfo's name property is a string. The fetched results controller will follow that key path for each fetched object and group the objects into sections based on what they return for that key path, and it'll also use those strings as the names of their respective sections. You can't use a managed object for the name -- you have to use some string property of the managed object.
So, your Category entity must have an attribute that distinguishes one category from another, right? Use that as the key path and (as you've seen) everything will work out.
BTW, I think it's useful to try to get out of the database (rows/fields) mindset and try to think in object-oriented terms like entity and attribute. A big selling point of Core Data is that it provides an abstraction layer that hides the storage mechanism. Thinking in terms of tables is like thinking about blocks and sectors when you're reading or writing a file.
Caleb, thank you for your answer. I do believe my understanding was wrong to some degree. What I had was an entity Category and entity Event. Category has a string field 'category', thus 'category.category' path (first 'category' is relationship in the Event entity)
What I did not take in account, though, is that if there are no events, fetchresultscontroller cannot fetch anything (similar to 'left join')
What I wanted is to show categories even if there are no events. Relationship 'category' will not return anything in this case as there is nothing to return/sort/categorize.
What I had to do (wrong or right - not sure yet) is to treat [managed] object created from Category entity as a separate object in case there are no events and place in the table. When there is one event per category, I can switch to the original method of [automatic] showing events sorted by categories.
This is interesting issue of starting point (empty entities with relationships) where I feel core data is more confusing than traditional relationship database. I also believe that's why all books/articles/reports carefully stay away from this topic. In other words, I could not find analog of "left join" in core data. May be I am wrong because I am relatively new to all this. Below is the description of the entities:
Category <-->> Event
Category - parent
Category.category - attribute of type String
Category.event - relationship to Event entity
Event - child
Event.name - attribute of type String
Event.category - relationship to Category entity
Each event belongs to one category. Category may have multiple events.
Categories should be shown even if there are no events for this category.
I was trying to put Events under fetchresultscontroller. May be I should switch to Category first and then calculate cell based on category.event relationship, not the other way around - did not try that yet.

Resources