views:

50

answers:

2

I know I can set default values either in the datamodel, or in the -awakeFromInsert method of the entity class. For example, to make a "date" property default to the current date:

- (void) awakeFromInsert
{
NSDate *now = [NSDate date];
self.date = now;
}

How though can I make an "idNumber" property default to one greater than the previous object's idNumber?

Thanks, Oli

EDIT: Relevant code for my attempt (now corrected)

- (void) awakeFromInsert
{
self.idNumber = [NSNumber numberWithInt:[self maxIdNumber] + 1];
}

-(int)maxIdNumber{
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Flight" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];

// Set example predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"idNumber > %@", [NSNumber numberWithInt:0]];
[request setPredicate:predicate];

[request setFetchLimit:1];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"idNumber" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];

NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil | array.count == 0)
{
    return 0;
} 
return [[[array objectAtIndex:0] valueForKey:@"idNumber"] intValue];
}

If the maxIdNumber method is called, the new object is added to the table twice!? (but with the correct idNumber). The two entries in the table are linked - editing / removing one also edits / removes the other. For this reason I believe it has something to do with the managed object context. For what its worth, the outcome (two copies) is the same no matter how many times the maxIdNumber method is called in the awakFromNib; even if self.idNumber is just set to [NSNumber numberWithInt:5] and the maxIdNumber method is just called for a throwaway variable.

Any clues??

+1  A: 

One Approach: Create a fetch request of all instances of your entity with a limit of 1, sorted by idNumber to get the highest number.

Another Approach: Keep the highest idNumber in your store's metadata and keep incrementing it.

There are plenty of arguments for and against either. Ultimately, those are the two most common and the choice is yours.

Joshua Nozzi
Thanks for your response. Ok, so I've managed to create a fetch request which returns the highest idNumber, great! However, whenever I use this code to set the idNumber property, the object gets added to the table twice!?!?! (Though removing either of the two instances, useing the remove button, removes both). Any ideas?
Charlie
Post your code. Not all of it, just the relevant parts.
Joshua Nozzi
There you go, I've put it in the original post to make it more legible.
Charlie
Looks like you're not showing how -maxIDNumber is being used.
Joshua Nozzi
Ah sorry. Ok all the relevant info should now be in the original post. Also, that code for the fetch request is rather verbose, I was planning on moving it to a category to provide a neater method along the lines of "fetchObjectsForEntityName: withPredicate: withFetchLimit: withSortDescriptors:". Bad idea, good idea?
Charlie
Why is your return type NSNumber when you're returning an int? I didn't catch that initially.
Joshua Nozzi
Its not, sorry I've changed the method to return int. Now corrected above. Still that wouldn't explain double entries...
Charlie
+1  A: 

SOLVED IT!

Ok, the problem of double entry occurs when a fetch request is performed from within the awakeFromInsert method. Quoting from the docs:

You are typically discouraged from performing fetches within an implementation of awakeFromInsert. Although it is allowed, execution of the fetch request can trigger the sending of internal Core Data notifications which may have unwanted side-effects. For example, on Mac OS X, an instance of NSArrayController may end up inserting a new object into its content array twice.

A way to get around it is to use the perfromSelector:withObject:afterDelay method as outlined here (I am only allowed to post one hyperlink :( ):http://www.cocoabuilder.com/archive/cocoa/232606-auto-incrementing-integer-attribute-in-awakefrominsert.html.

My working code is now as follows: (note, I have put the bulk of the fetching code used above into a category to tidy it up a little, this allows me to use the method fetchObjectsForEntityName:withPredicate:withFetchLimit:withSortDescriptors:)

- (void) awakeFromInsert
{
[self performSelector:@selector(setIdNumber) withObject:nil afterDelay:0];
self.date = [NSDate date];
}


-(void)setIdNumber
{
int num  = 0;

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"idNumber" ascending:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"idNumber > %@", [NSNumber numberWithInt:0]];

NSArray *array = [[self managedObjectContext] fetchObjectsForEntityName:@"Flight" 
                                                          withPredicate:predicate 
                                                         withFetchLimit:0
                                                    withSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];   

if (array != nil & array.count != 0)
{
    num = [[[array objectAtIndex:0] valueForKey:@"idNumber"] intValue]; 
} 

num ++;

[self setIdNumber:[NSNumber numberWithInt:num]];
}

Let me know what you think!

Charlie
Good catch and good approach.
Joshua Nozzi
If this is the answer, hit the checkmark so the system shows it as answered.
TechZen
I can't, I have to wait until tomorrow to accept my own answer :( !!
Charlie