Core Data: Save failing on mandatory field, but value should be set! - iphone

I have an abstract entity called Block which contains two attributes: column and order (which are not optional), and one relationship, thing, where it is the detail of a 1->M. I have another entity, Whatever, that has Block as its parent, and adds one attribute, someNumber.
My code looks like this:
Whatever *block = (Whatever *)[NSEntityDescription insertNewObjectForEntityForName:#"Whatever" inManagedObjectContext:managedObjectContext];
block.order = 0;
block.column = 0;
block.thing = self.thing;
When I try to save, I get this error:
Failed to save to data store: Operation could not be completed. (Cocoa error 1560.)
DetailedError: {
NSLocalizedDescription = "Operation could not be completed. (Cocoa error 1570.)";
NSValidationErrorKey = column;
NSValidationErrorObject = <Whatever: 0x5124890> (entity: someWhatever; id: 0x511b4e0 <x-coredata:///Whatever/t718B63A4-927B-4D88-A9E6-7F61CF9621675> ;
data: {
column = nil;
thing = 0x54367a0 <x-coredata://E6648244-E5FC-4202-B5F9-C7A91BACF8DA/Thing/p2>;
order = nil;
someNumber = 0;
});
I don't understand why it says that column and order are nil, as I've just set them the line before, so this shouldn't be a problem.
I've tried using the [block setColumn:0] style as well, without success.
Any help would be appreciated. Thanks!

You are setting them to nil since nil is just a null or zero pointer value.
Core Data properties must be set to objects (as opposed to primitive types).
Integers and floating point numbers are NSNumber objects.
I like to use the numberWith* convenience constructors.
For example:
block.order = [NSNumber numberWithInteger:0];
block.column = [NSNumber numberWithInteger:0];

To expand on gerry3's answer, a great way to ease coding with Core Data is to use Rentzsch's mogenerator. It would allow you to do:
block.orderValue = 0;
block.columnValue = 0;

Related

Core Data relationship not saving even though it is set

I have a mandatory genericCall relationship for a PlaylistCall entity that is being set prior to the PlaylistCall being saved. The logs show that the genericCall is not nil and yet I get a validation error when trying to save the managed object context stating that the relationship is not set.
What could cause a relationship that is already set to then become nil during the save?
What I have verified or attempted:
I use a shared singleton as my managed object context for the entire app so I am definitely working within the same managed object context
An inverse relationship exists between GenericCall and PlaylistCall
On a whim, I tried deleting the relationship and then recreating it, but that did nothing so it seems like a bug in my code instead of a random bug in Xcode
I tried using setValue:forKey instead of the dynamic property accessors (the dot notation) with no change
Here are the logs:
2013-03-25 11:48:35.400 Project[30599:c07] Supposed to add <PlaylistCall: 0xa1be450> (entity: PlaylistCall; id: 0xa17dea0 <x-coredata:///PlaylistCall/tBB209BC5-EF9A-4DAE-9A55-0001B97499392> ; data: {
delaySetting = 0;
genericCall = "0xd4ad520 <x-coredata://7EB1AFB2-7B49-431E-9700-275307B4D1C1/GenericCall/p367>";
orderInTable = 4;
playlist = nil;
repeatSetting = 0;
repeatType = 0;
volumeSetting = 100;
}) with generic call <GenericCall: 0xd4c3fd0> (entity: GenericCall; id: 0xd4ad520 <x-coredata://7EB1AFB2-7B49-431E-9700-275307B4D1C1/GenericCall/p367> ; data: {
animalCategory = "0xc0aaa80 <x-coredata://7EB1AFB2-7B49-431E-9700-275307B4D1C1/AnimalCategory/p19>";
callDescription = "Call Name";
numberOfTimesOnPlaylist = 0;
numberOfTimesPlayed = 0;
playlistCall = "0xa17dea0 <x-coredata:///PlaylistCall/tBB209BC5-EF9A-4DAE-9A55-0001B97499392>";
soundFile = "sound_file_name";
timeLength = "0.21";
})
2013-03-25 11:48:35.402 Project[30599:c07] Error saving. Reason(s):
PlaylistCall: The attribute 'genericCall' cannot be empty.
Here is how I am setting and saving the values:
GenericCall *genericCall = [self.fetchedResultsController objectAtIndexPath:indexPath];
PlaylistCall *playlistCall = (PlaylistCall *)[NSEntityDescription insertNewObjectForEntityForName:#"PlaylistCall" inManagedObjectContext:commonContext.managedObjectContext];
[self.playlist addPlaylistCall:playlistCall withGenericCall:genericCall orderInTable:[self.playlist.playlistCalls count]];
// Note: all other attributes are setup via model defaults
[commonContext save];
My NSManagedObject subclass for Playlist where I set the relationship:
- (void)addPlaylistCall:(PlaylistCall *)playlistCall withGenericCall:(GenericCall *)genericCall orderInTable:(int)orderInTable
{
NSMutableOrderedSet *tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.playlistCalls];
playlistCall.genericCall = genericCall;
playlistCall.orderInTable = [NSNumber numberWithInt:orderInTable];
NSLog(#"Supposed to add %# with generic call %#", playlistCall, playlistCall.genericCall);
[tempSet addObject:playlistCall];
self.playlistCalls = tempSet;
}
My common managed object context singleton (CommonContext) just looks like this (and yes I've verified that the self.managedObjectContext is not nil and doesn't change):
// Saves the managed object context
- (BOOL)save
{
NSError *anError;
if ([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&anError]){
[self handleSaveError:anError];
return NO;
} else {
return YES;
}
}
Any ideas for what I may be doing wrong? It seems straightforward and I use this same approach everywhere else in my app for setting relationships, so there has to be something that I am missing. Thanks for your time!
It's hard to be certain but here's a scenario that fits the symptoms:
You look up an existing GenericCall instance before creating a new PlaylistCall. But this GenericCall already has a non-nil value for playlistCall.
You make the assignment playlistCall.genericCall = genericCall using the new PlaylistCall.
The already-existing PlaylistCall instance has its genericCall relationship set to nil-- because this is a one-to-one relationship and that's the default behavior in this case.
Saving fails-- not because of the new PlaylistCall you created, which is fine, but because of the previous PlaylistCall that has been left with a dangling genericCall relationship.
If this is what's happening, you'll need to check for an existing playlistCall and do something reasonable with it before saving changes-- most likely either reassigning its genericCall to point to something else, or possibly just deleting the instance.

Correct way to modify results array from NSFectchRequest without changing Core Data objects

I am retrieving a fetch from my core data database and trying to iterate through the data and make changes to the data, if necessary. When I change the data in the results array, it turns out that my database is changing in the back end as well, without doing a save. I am wondering what would be a good practice to use to change the data without affecting the back end data.
Here is the code I change the data with:
self.singleDayDataPointsForGraph = [[self fuelPurchaseDataForTimePeriodInMonths:self.numberOfMonthsForGraphView] mutableCopy];
for (int i = 0; i < self.singleDayDataPointsForGraph.count; i++) {
FuelPurchase *currentFuelPurchase = [self.singleDayDataPointsForGraph objectAtIndex:i];
if (i < self.singleDayDataPointsForGraph.count + 1 && self.singleDayDataPointsForGraph.count >= 2) {
FuelPurchase *purchaseToCompare = [self.singleDayDataPointsForGraph objectAtIndex:i + 1];
NSDate *firstDate = currentFuelPurchase.dateTimeStamp;
NSDate *secondDate = purchaseToCompare.dateTimeStamp;
NSDateFormatter *dateComparisonFormatter = [[NSDateFormatter alloc] init];
[dateComparisonFormatter setDateFormat:#"yyyy-MM-dd"];
if([[dateComparisonFormatter stringFromDate:firstDate] isEqualToString:[dateComparisonFormatter stringFromDate:secondDate]] ) {
float firstValue = [purchaseToCompare.fillSavings floatValue];
float secondValue = [currentFuelPurchase.fillSavings floatValue];
purchaseToCompare.fillSavings = [NSNumber numberWithFloat:(firstValue + secondValue)];
[self.singleDayDataPointsForGraph removeObjectAtIndex:i];
}
}
The fuelPurchaseDataForTimePeriodInMonths: method is what performs the fetch and returns an NSArray of results. self. singleDayDataPointsForGraph is an NSMutableArray that stores the results array as a mutable copy. This method basically checks two entries in the database to see if they are the on the same day and if they are, then it adds the fuel purchase amounts to each other and deletes one of the records. I don't want this to change my back end data, but it is.
Thanks very much.
It is doing the right thing. If you take a core data object and modify it, it will reflect immediately whether you save it ot not. The saving part ensures, that if you quit the application and come back the data is saved as well.
So for your situation, I would avoid modifying the actual core data object. Rather create a structure which imitates the core data object and modify that structure.
Example, say my coredata object is Person with attributes name and age.
The object a get from a fetch is say
person1.
You have a class PersonSub with same attributes.
Now you can create
PersonSub *personSub = [[PersonSub alloc] init]; //You can create a custom init to initilize from Person core data if you like.
personSub.name = person1.name;
personSub.age = person1.age;
Now you can modify as follows
personSub.age = personSub.age + 1;

How to refer to an object in a NSDictionary that is in a NSArray

When I NSLog the array I get this:
(
{
content = "content a";
id = 452069;
timestamp = 1313341470;
},
{
content = "content b";
id = 451498;
timestamp = 1313261505;
},
etc...
)
How do you refer to specific index's? For example how would you get the content for the second index.
I've tried [[myArray objectAtIndex:1]objectForKey:#"content"] but that crashes the program.
Also doing [myArray objectAtIndex:1] crashes the program as well.
According to your edit your array is likely over released. Make sure your array is properly retained, if it uses a property make sure the property is set to copy or retain and if you set it internally be sure to use self.myArray = ...; and not myArray = ...'.
Have you tried -valueForKey:? The item at that index is not an object but a value.

Core data only storing last object of JSON feed

I´m using Core Data as local storage in my app. I´ve set it up properly and made subclasses of NSManagedObject for each entity. However, when I´m trying to insert values into my store, it only inserts the last object from my JSON feed.
res = [JSONHandler requestJSONResponse:jsonString];
shows = [res valueForKeyPath:#"Show.Name"];
NSUInteger showIndex = 0;
for(NSString *showName in shows){
showObject = [NSEntityDescription insertNewObjectForEntityForName:#"Show" inManagedObjectContext:managedObjectContext_];
showObject.name = showName;
showObject.iD = [[res valueForKeyPath:#"Show.Id"]objectAtIndex:showIndex];
showObject.desc = [[res valueForKeyPath:#"Show.Description"]objectAtIndex:showIndex];
showObject.activityType = [[res valueForKeyPath:#"Show.ActivityType"]objectAtIndex:showIndex];
showIndex++;
}
This only stores the last object from my JSON feed. Any idea why?
EDIT: It works fine when I do this:
res = [JSONHandler requestJSONResponse:jsonString];
shows = [res valueForKeyPath:#"Show.Name"];
NSUInteger index = 0;
for(NSString *showName in shows){
show = [NSEntityDescription insertNewObjectForEntityForName:#"Show" inManagedObjectContext:managedObjectContext_];
[show setValue:showName forKey:#"name"];
[show setValue:[[res valueForKeyPath:#"Show.Id"]objectAtIndex:index] forKey:#"iD"];
[show setValue:[[res valueForKeyPath:#"Show.Description"]objectAtIndex:index] forKey:#"desc"];
[show setValue:[[res valueForKeyPath:#"Show.ActivityType"]objectAtIndex:index] forKey:#"activityType"];
index++;
}
It´s basically the same thing, isn´t it? But I want to use subclasses of NSManagedObject instead of doing like I did above. Because in the snippet above show is NSManagedObject *show instead of what it should be: Show *show.
How many shows are there? You can find this by doing: NSLog(#"Number of shows: %d.", shows.count);, assuming that shows is an NSArray. It could be that your Core Data code is fine and the JSON parsing itself is at fault.
EDIT: Also, are you correctly saving the changes to the persistent store?
Usually when you see just one of several objects being saved like this, the problem is that a relationship that should be to-many is improperly set as to-one. No matter how many objects you try to add to the relationship, only the last one is set because the relationship can hold only one value.
I think in this circumstance the problem is most likely in the code of the custom subclass instead of the data model itself given that the data model works with generic NSManagedObjects.

Core Data Predicates with Subclassed NSManagedObjects

I have an AUDIO class. This audio has a SOUND_A subclass and a SOUND_B subclass. This is all done correctly and is working fine.
I have another model, lets call it PLAYLIST_JOIN, and this can contain (in the real world) SOUND_A's and SOUND_B's, so we give it a relationship of AUDIO and PLAYLIST.
This all works in the app.
The problem I am having now is querying the PLAYLIST_JOIN table with an NSPredicate. What I want to do is find an exact PLAYLIST_JOIN item by giving it 2 keys in the predicate
sound_a._sound_a_id = %# && playlist.playlist_id = %#
and
sound_b.sound_b_id = %# && playlist.playlist_id = %#
The main problem is that because the table does not store sound_a and sound_b, but stored audio, I cannot use this syntax. I do not have the option of reorganizing the sound_a and sound_b to use the same _id attribute name, so how do I do this?
Can I pass a method to the predicate? something like this:
[audio getID] = %# && playlist_id = %#
It gets a little complicated but you need to add a third condition to the predicate:
(entity.name = sound_a && _sound_a_id = %# && playlist.playlist_id = %#) && (entity.name = sound_b && sound_b_id = %# && playlist.playlist_id = %#)
This is assuming you are querying against the audio abstract and telling it to return subclasses. Because the condition is checked left to right, if the first condition fails it will move on and not throw errors because _sound_a_id does not exist.
The first condition is referencing the NSEntityDescription that is a part of the NSManagedObject and its name attribute is just a string.