coredata override set value for relationship - core-data

I have a working model in coredata with two entity:
- Customer
- Invoices
Customer has "invoices" property (1 to many) and Invoices has "customer" property (1 to 1).
Everything worked until i override the "setCustomer" method on Invoice class.
I write this code
-(void)setCustomer:(Customer *)customer {
[self willChangeValueForKey:#"Customer"];
[self setPrimitiveValue:customer forKey:#"Customer"];
[self didChangeValueForKey:#"Customer"];
[self recalulatePriceAndDiscounts];
}
where "recalculatePriceAndDiscount" is my own method that i need to call when i select different customer.
When i use this code the inverse relationship (customer->invoices) isn't immediately available, i need to close and reopen my application to see customer's invoices.
There is some other method that i need to call?
thanks

If the property is called "customer" (with a lower-case "c") then you have to use that as key, e.g.
[self willChangeValueForKey:#"customer"];
// ... etc.

Related

Limit Core Data Entities Shown In UITableView row

I think I'm just missing something obvious here, but it's one of those frustrating things that's somehow eluding me.
I have a Core Data Entity called ProjectEntry. The ProjectEntry objects are displayed in uitableviews, using various attributes, arranged by date (attributes include things like "dateAsNSDate"[NSDate], "month"[NSString], "year"[NSString], "dayOfWeek"[NSString]).
I'm using an NSFetchedResultsController to populate the table views.
When I initially create and save the ProjectEntry object, the "dateAsNSDate" attribute is parsed and converted into various NSStrings. One string, also an attribute, is called "concatMonthAndYear". It takes the "month" and "year" strings and just joins them. So I get things such as "January 2014", "February 2015", etc.
I use the "concatMonthAndYear" as my cell.textLabel.text string to display in my tableview cells.
I use the NSDate attribute to sort the tableview rows (sortDescriptor), and the "year" attribute as my section headers (sectionNameKeyPath).
So right now, I'd have a tableview section called "2014", with tableview rows each representing a Core Data object, named things like "January 2014", February 2014", etc, in said section.
I can tap on one of those rows, segue to another tableview, and list all objects created in January 2014, for example, by using an NSPredicate on the second tableview.
However, on my first tableview, each Core Data object created is represented by its own tableview row. So I'll get multiple rows reading "January 2014" or "May 2015" or whatever. They're valid saved objects, and I want them, but I'd like to prevent a new row from being created if that "concatMonthAndYear" already exists. If a row titled "January 2014" already exists, I don't want a new row created. I want the new Core Data object stored, just not a new tableviewrow representing it. I only need one row with "January 2014", for example, to segue into a table listing ALL the entities from January 2014.
I know how to use an NSPredicate to get ALL the January 2014 objects into the second table, but how do I get JUST ONE object into the first table?
Is NSPredicate not the right device for that? Should I be somehow preventing a new cell from being created in the UITableView delegate methods? Each tableview row should be unique, and I'm stuck on whether it should be handled with the NSFetchedResults controller or in the tableview delegate methods?
Or some other way?
Can someone point in the right direction?
EDITED TO INCLUDE CODE:
- (void)setupFetchedResultsController
{
// 1 - Decide which Entity
NSString *entityName = #"ProjectEntry";
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
[request setReturnsDistinctResults:YES];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:#[#"monthYearTableSecHeader", #"year"]];
// 3 - Filter it
//request.predicate = [NSPredicate predicateWithFormat:#" "];
// 4 - Sort it
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"year"
ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:#"dateAsNSDate"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)], nil];
//5 - Fetch
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"year"
cacheName:nil];
[self performFetch];
}
You could use
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:#[#"concatMonthAndYear", #"year"]];
This will cause the fetch request to return distinct dictionary objects corresponding to "January 2014", etc. objects.
However, you cannot use a fetch request controller's delegate methods (to hear of updates to the data).
If you need to hear updates, I suggest you add a layer of indirection to your data, where MonthEntry is an object representing yearly months and have a one to many relationship with ProjectEntry, which is your normal entity. This way, you can set the fetch request entity to MonthEntry.

Core Data NSFetchRequest Sort by Category Method Return Value

How do I sort my fetched results by a value that is returned by a method in a category of the entity I'm fetching?
In my category, I sum up several values from the entity's to-many relationship, then divide by the number of objects in the relationship, effectively creating an average that I return in my category method as a float value.
Here is my code:
In the Category.h
- (float)smallPenaltyAvg;
In the Category.m
- (float)smallPenaltyAvg{
float smallPenaltyAvg = 0;
for (Match *mtch in self.matches) {
smallPenaltyAvg += [mtch.penaltySmall floatValue];
}
if ([self.matches count] > 0) {
smallPenaltyAvg = (float)smallPenaltyAvg/(float)[self.matches count];
}
return smallPenaltyAvg;
}
And when I call it in the Core Data Table View Controller class that I created...
NSFetchRequest *poolRequest = [[NSFetchRequest alloc] initWithEntityName:#"Team"];
poolRequest.predicate = [NSPredicate predicateWithFormat:#"regionalIn.name = %#", _regionalToDisplay];
poolRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"smallPenaltyAvg" ascending:YES]];
And I have the Category.h file imported on every file previously mentioned outside of the Category.h file itself.
It gives me the error of:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath smallPenaltyAvg not found in entity <NSSQLEntity Team id=5>
Am I not allowed to do this?
If I am, what am I doing wrong?
I do not think this has anything to do with the kind of persistent store.
The trick is to create an appropriate attribute in the managed object model, and mark it as Transient. Then override the getter of this attribute to do your calculations.
Now your fetch request should work as expected (although there are some caveats with fetched results controllers).
As for the SQLite problem, when you add the SQLite store with
- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType
configuration:(NSString *)configuration
URL:(NSURL *)storeURL
options:(NSDictionary *)options
error:(NSError **)error
just pass NSSQLiteStoreType as the storeType. The other options are binary and in-memory, so in this sense this is indeed the "default".
This is not possible when using a backing SQLite store.
My suggestion is you persist the average property, and maintain it yourself by overriding the Match setCategory: property and making the calculation there for every match added.
What I did to solve my problem was create a new attribute for every average or sum that I needed in the Team object from all of its Match objects' attributes and then created a method in the TeamCategory file that populated those averages and that method was called every time a Match object was inserted into the Team object. It took a while to do, but it works now. If there is a better solution, I'm still open for suggestions.

Sorting NSFetchedResultsController using a to-many relationship property

SCENARIO
I have two entities: Item and ListDetail (which contains prices for different lists for every item). This is absolutely needed and I can't provide a price attribute for the Item entity because every item can have more prices for different dynamic lists (retail, b2b ecc.).
The relationship is:
Item (lists) <------->> (item) ListDetail
The current active list in my app change dinamically, so let's say I have an integer variable with the current active list: _ACTIVE_LIST_CODE_. When I need a price for an item object I use an helper method on the Item class:
-(NSNumber*) getPrice {
NSSet *lists=[self.lists filteredSetUsingPredicate: [NSPredicate predicateWithFormat:#"listId == %d",_ACTIVE_LIST_CODE_]];
ListDetail *activeList=[[lists allObjects] objectAtIndex:0];
return activeList.price;
}
THE PROBLEM
I use a UITableView with NSFetchedResultController in order to select and show some items for different sections. Nothing special. I would like to order the fetchedObjects using the items price for the active list. If price was an attribute of Item I would added simply a sort descriptor to the fetch request like so:
[NSSortDescriptor sortDescriptorWithKey:#"price" ascending:YES];
But as said before this is not possible, price is a dynamic attribute.
If using transient properties was possible for sort descriptors, I would set a price transient properties calculated on fly using my helper method. Nothing to do.
Using a keypath in the descriptor like "lists.price" is not possible (or maybe I don't know how to do that), just because it's a to-many relationship and it's modeled with a NSSet.
I tried some workaround, without success:
1) observing _ACTIVE_LIST_CODE_ changes to set items price in a non-transient attribute.
2) after the fetch request, before presenting the table view, reorder a brand new array with fetched objects using the transient "price" property, iterate the orderdered array following an ascending integer index "i" and assigning this value to a non-transient property "order" for the Item entity. Using "order" for sort descriptor in the fetch request. (This approach is described here: Re-ordering NSFetchedResultsController)
Both of them works, but they slow down performance because I have thousands of items in the fetch results... Any idea?
How about fetching ListDetail instead? You could restrict and sort with the appropriate predicates and sort descriptors, exactly as you propose.
fetchRequest.predicate =
[NSPredicate predicateWithFormat:#"listID = %#", activeListCode];
fetchRequest.sortDescriptors =
#[[NSSortDescriptor sortDescriptorWithKey:#"price" ascending:YES]];
Now, to group by some attribute of item should be simple and efficient because it is a to-one relationship. Your fetched results controller's sectionNameKeyPath can be something like
#"item.category"

Avoiding a fetch in a custom setter

I have two entities, Item and Category. Each item has one category, and a category can have 0-many items.
I have a special category, the misc category. I denote this with a boolean property, isMisc, so category.isMisc=YES.
When I delete a category, I want to reassign any of its items to the "misc" category. So I wrote the following custom setter for item:
- (void)setCategory:(Category *)category
{
[self willChangeValueForKey:#"category"];
if (category == nil) {
category = [Database theMiscCategory];
}
[self setPrimitiveValue:category forKey:#"category"];
[self didChangeValueForKey:#"category"];
}
The problem is, [Database theMiscCategory] performs a fetch, which I believe is discouraged. Is there another way to do this?
I have looked at just letting item.category = nil, but this introduces enough complications in other areas of the code that I'd much rather have a "misc" category.
You could create or fetch the "misc" category object once in your program, e.g. after creating the managed object context. Then your custom setter method can always use this instance.
If you work with several managed object contexts, then you would have to create one object for each context.

Core Data object update when a property of its related object changes

I'm still getting used to how Core Data works and I've looked around for information about my problem but I haven't found any answers that obviously address my exact problem.
I have three classes of managed objects: loan, borrower, and photo. Borrower objects have a one-to-many relationship with loan objects (meaning a borrower can have more than one loan but a loan can only have one borrower). Borrower objects also have a one-to-one relationship with a photo object.
I am using an NSFetchedResultsController to keep a table up to date with changes in a set of loan objects. When other borrower properties change the change notification reaches the NSFetchedResultsController and my table updates. But when the photo property changes to point to another photo object then no notification is passed to the NSFetchedResultsController. It seems that none of the loans that are related to the borrower are told when the borrower changes its photo relationship.
Please help!
You can try to handle the NSManagedObjectContextObjectsDidChangeNotification notification.
It's a little squirrelly, but you could do the following, assuming that you have subclassed NSManagedObject for Loan, Borrower and Photo classes.
1.) In Loan, set up KVO for loan's photo's 'image' property.
2.) In Loan, add changeCount property (NSNumber*).
3.) When a loan is alerted to change in its photo's image, increment changeCount.
So, in very rough code, something like this:
in Load.m:
- (void) awakeFromFetch
{
[super awakeFromFetch];
[[self photo] addObserver:self
forKeyPath:#"image"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"image"])
{
NSInteger temp = [[self changeCount] intValue];
++temp;
[self setChangeCount:[NSNumber numberWithInteger:temp]];
}
}
There are some caveats.
1) This only deals with 'fetch' and not with 'insert' (i.e., new loans).
2) This assumes that a fetched loan will have a photo.
3) This assumes that you have added 'changeCount' property to Loan.
4) I haven't tested this, though I use remotely similar mechanisms in one of my apps.

Resources