Core Data : calling validation in awakeFromInsert problem ?
-
Hi list !
I'm trying to implement validation on my managed objects and am
facing a strange problem.
I have a managed object with one property named "name". This property
is a string. I want this property to be unique in the managed context
of the object, so I've implemented "validateName..." method in my
managed object subclass (see code below).
Next, I want new objects inserted into the context to have a name set
to a validated one (that means : unique). So I have implemented
"awakeFromInsert..." method in my subclass that set the name property
to a valid one.
Here is the code for my custom class (implementation only, there is
nothing of interest in the header) :
//
// TestObject.m
// CoreData
//
// Created by Eric on 05/09/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//
#import "TestObject.h"
@implementation TestObject
- (NSString *)name
{
NSString * tmpValue;
[self willAccessValueForKey: @"name"];
tmpValue = [self primitiveValueForKey: @"name"];
[self didAccessValueForKey: @"name"];
return tmpValue;
}
- (void)setName:(NSString *)value
{
[self willChangeValueForKey: @"name"];
[self setPrimitiveValue: value forKey: @"name"];
[self didChangeValueForKey: @"name"];
}
- (void)awakeFromInsert
{
// Here I want the default name to be valid !
// In case objects already exit with the name "TestObjectName",
// append "(index++)" at the end of the name until this name
doesn't already exists
// in the managed object context
NSString * validName = nil;
NSString * defaultName = [NSString
stringWithString:@"TestObjectName"];
validName = defaultName;
int index = 0;
while ( ![self validateValue:&validName forKey:@"name" error:nil] )
{
index++;
validName = [defaultName stringByAppendingFormat:@" (%d)",
index];
}
[self setName:validName];
}
- (BOOL)validateName: (id *)valueRef error:(NSError **)outError
{
NSPredicate * predicate = [NSPredicate
predicateWithFormat:@"name == %@", *valueRef];
NSEntityDescription * entityDescription = [NSEntityDescription
entityForName:@"TestObject" inManagedObjectContext:[self
managedObjectContext]];
NSFetchRequest * fetchRequest = [[[NSFetchRequest alloc] init]
autorelease];
[fetchRequest setEntity:entityDescription];
[fetchRequest setPredicate:predicate];
NSArray * results = [[self managedObjectContext]
executeFetchRequest:fetchRequest error:nil];
return ( [results count] < 1 ) || ( [results objectAtIndex:0] ==
self );
}
@end
Now, everything looks fine to me.
I'm using a simple interface to test the insertion process : in fact
it is the standard interface created automatically when alt-dragging
the object from Xcode to IB - one table view, 3 buttons (Fetch, Add,
Remove), a text field to enter name and a search field. Then, the
problems begin : when I click on the add button, TWO objects are
added to the table view !
If I replace the line :
while ( ![self validateValue:&validName forKey:@"name" error:nil] )
by :
while ( index < 5 )
it works like a charm (every object is created with the same name
"TestObjectName (5)", of course). So it seems that calling the
validation method in the awakeFromInsert is causing a problem but I
can't put my finger on it.
Do someone know somethign about it ?
And do you think I'm using the right process to validate my name
property and mak sure my object are inserted with valid name ?
Thanks,
Eric. -
mmalcolm crawford Re: Core Data : calling validation in awakeFromInsert problem ? Sep 05 2005, 21:07On Sep 5, 2005, at 9:42 AM, Eric Morand wrote:
> I have a managed object with one property named "name". ThisI'm not sure if this is a bug (I'll follow up) but it's because
> property is a string. I want this property to be unique in the
> managed context of the object, so I've implemented
> "validateName..." method in my managed object subclass (see code
> below).
> Next, I want new objects inserted into the context to have a name
> set to a validated one (that means : unique). So I have implemented
> "awakeFromInsert..." method in my subclass that set the name
> property to a valid one.
> [...]
> Here is the code for my custom class (implementation only, there is
> nothing of interest in the header) :
> ]...]
> - (void)awakeFromInsert
> {
> [...]
> NSString * validName = nil;
> NSString * defaultName = [NSString
> stringWithString:@"TestObjectName"];
>
> validName = defaultName;
>
> int index = 0;
>
> while ( ![self validateValue:&validName forKey:@"name"
> error:nil] )
> {
> index++;
>
> validName = [defaultName stringByAppendingFormat:@" (%d)",
> index];
> }
>
> [self setName:validName];
> }
>
> - (BOOL)validateName: (id *)valueRef error:(NSError **)outError
> {
> NSPredicate * predicate = [NSPredicate
> predicateWithFormat:@"name == %@", *valueRef];
>
> NSEntityDescription * entityDescription = [NSEntityDescription
> entityForName:@"TestObject" inManagedObjectContext:[self
> managedObjectContext]];
> NSFetchRequest * fetchRequest = [[[NSFetchRequest alloc] init]
> autorelease];
>
> [fetchRequest setEntity:entityDescription];
> [fetchRequest setPredicate:predicate];
>
> NSArray * results = [[self managedObjectContext]
> executeFetchRequest:fetchRequest error:nil];
>
> return ( [results count] < 1 ) || ( [results objectAtIndex:0]
> == self );
> }
>
> @end
>
> I'm using a simple interface to test the insertion process : in
> fact it is the standard interface created automatically when alt-
> dragging the object from Xcode to IB - one table view, 3 buttons
> (Fetch, Add, Remove), a text field to enter name and a search
> field. Then, the problems begin : when I click on the add button,
> TWO objects are added to the table view !
>
you're executing a fetch (whose results will include self) before the
insertion has completed. You can sidestep this by setting the name
after awakeFromInsert:
- (void)awakeFromInsert
{
[super awakeFromInsert];
[self performSelector:@selector(setUniqueName) withObject:nil
afterDelay:0];
}
There are several issues with the remainder of the awakeFromInsert
code (now moved to the new method) and the validation method:
- (void)setUniqueName
{
NSString * defaultName = @"TestObjectName"; // no need for
stringWithString:
NSString * validName = defaultName; // no need for two assignments
int index = 0;
while ( ![self validateValue:&validName forKey:@"name" error:nil] )
{
index++;
validName = [defaultName stringByAppendingFormat:@" (%d)",
index];
}
[self setName:validName];
}
- (BOOL)validateName: (id *)valueRef error:(NSError **)outError
{
/*
Check (SELF != %@) in fetch; use 'LIKE' not '==' for string comparison
*/
NSPredicate * predicate = [NSPredicate
predicateWithFormat:@"(SELF != %@) AND (name LIKE %@)", self,
*valueRef];
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
/*
Use self's entity -- no need to use NSEntityDescription method to
retrieve (also avoids hard-wiring entity name)
*/
[fetchRequest setEntity:[self entity]];
[fetchRequest setPredicate:predicate];
/*
Should check for error
*/
NSArray * results = [[self managedObjectContext]
executeFetchRequest:fetchRequest error:nil];
[fetchRequest release];
return ( [results count] < 1 );
}
> And do you think I'm using the right process to validate my name
> property and mak sure my object are inserted with valid name ?
This approach will be very inefficient for anything but data sets
where there is little likelihood of name collisions -- you have to do
an additional fetch for every collision. It's not clear what your
goal is here. If you're simply wanting to set temporary distinct
names (like window titles) for small numbers of objects, this would
probably be OK. If these will be permanent values for an ever-
increasing number of objects, then you'll need a different approach...
mmalc -
Thanks for such a great explanation ! I'll try it ass soon as I've
typed this mail.
> This approach will be very inefficient for anything but data sets
> where there is little likelihood of name collisions -- you have to
> do an additional fetch for every collision. It's not clear what
> your goal is here. If you're simply wanting to set temporary
> distinct names (like window titles) for small numbers of objects,
> this would probably be OK. If these will be permanent values for
> an ever-increasing number of objects, then you'll need a different
> approach...
Well, that's simple : just like in iTunes, when you create two or
three playlist without renaming them, the playlist name is append
with "(index++). I need the same kind of behavior : in my app, user
can add managed objects via a "Add" button and I want the name of the
added object to "increase" automatically. Does it seem a bad idea to
you ?
How am I supposed to do if I want to be sure that a property is
unique for a given entity ?
Eric.
Le 5 sept. 05 à 21:07, mmalcolm crawford a écrit :
>
> On Sep 5, 2005, at 9:42 AM, Eric Morand wrote:
>
>> I have a managed object with one property named "name". This
>> property is a string. I want this property to be unique in the
>> managed context of the object, so I've implemented
>> "validateName..." method in my managed object subclass (see code
>> below).
>> Next, I want new objects inserted into the context to have a name
>> set to a validated one (that means : unique). So I have
>> implemented "awakeFromInsert..." method in my subclass that set
>> the name property to a valid one.
>> [...]
>> Here is the code for my custom class (implementation only, there
>> is nothing of interest in the header) :
>> ]...]
>> - (void)awakeFromInsert
>> {
>> [...]
>> NSString * validName = nil;
>> NSString * defaultName = [NSString
>> stringWithString:@"TestObjectName"];
>>
>> validName = defaultName;
>>
>> int index = 0;
>>
>> while ( ![self validateValue:&validName forKey:@"name"
>> error:nil] )
>> {
>> index++;
>>
>> validName = [defaultName stringByAppendingFormat:@" (%d)",
>> index];
>> }
>>
>> [self setName:validName];
>> }
>>
>> - (BOOL)validateName: (id *)valueRef error:(NSError **)outError
>> {
>> NSPredicate * predicate = [NSPredicate
>> predicateWithFormat:@"name == %@", *valueRef];
>>
>> NSEntityDescription * entityDescription = [NSEntityDescription
>> entityForName:@"TestObject" inManagedObjectContext:[self
>> managedObjectContext]];
>> NSFetchRequest * fetchRequest = [[[NSFetchRequest alloc] init]
>> autorelease];
>>
>> [fetchRequest setEntity:entityDescription];
>> [fetchRequest setPredicate:predicate];
>>
>> NSArray * results = [[self managedObjectContext]
>> executeFetchRequest:fetchRequest error:nil];
>>
>> return ( [results count] < 1 ) || ( [results objectAtIndex:0]
>> == self );
>> }
>>
>> @end
>>
>> I'm using a simple interface to test the insertion process : in
>> fact it is the standard interface created automatically when alt-
>> dragging the object from Xcode to IB - one table view, 3 buttons
>> (Fetch, Add, Remove), a text field to enter name and a search
>> field. Then, the problems begin : when I click on the add button,
>> TWO objects are added to the table view !
>>
>>
> I'm not sure if this is a bug (I'll follow up) but it's because
> you're executing a fetch (whose results will include self) before
> the insertion has completed. You can sidestep this by setting the
> name after awakeFromInsert:
>
> - (void)awakeFromInsert
> {
> [super awakeFromInsert];
> [self performSelector:@selector(setUniqueName) withObject:nil
> afterDelay:0];
> }
>
> There are several issues with the remainder of the awakeFromInsert
> code (now moved to the new method) and the validation method:
>
>
> - (void)setUniqueName
> {
> NSString * defaultName = @"TestObjectName"; // no need for
> stringWithString:
> NSString * validName = defaultName; // no need for two assignments
> int index = 0;
> while ( ![self validateValue:&validName forKey:@"name"
> error:nil] )
> {
> index++;
> validName = [defaultName stringByAppendingFormat:@" (%d)",
> index];
> }
> [self setName:validName];
> }
>
>
> - (BOOL)validateName: (id *)valueRef error:(NSError **)outError
> {
> /*
> Check (SELF != %@) in fetch; use 'LIKE' not '==' for string comparison
> */
> NSPredicate * predicate = [NSPredicate
> predicateWithFormat:@"(SELF != %@) AND (name LIKE %@)", self,
> *valueRef];
> NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
> /*
> Use self's entity -- no need to use NSEntityDescription method to
> retrieve (also avoids hard-wiring entity name)
> */
> [fetchRequest setEntity:[self entity]];
> [fetchRequest setPredicate:predicate];
>
> /*
> Should check for error
> */
> NSArray * results = [[self managedObjectContext]
> executeFetchRequest:fetchRequest error:nil];
>
> [fetchRequest release];
>
> return ( [results count] < 1 );
> }
>
>
>
>
>> And do you think I'm using the right process to validate my name
>> property and mak sure my object are inserted with valid name ?
>>
>
> This approach will be very inefficient for anything but data sets
> where there is little likelihood of name collisions -- you have to
> do an additional fetch for every collision. It's not clear what
> your goal is here. If you're simply wanting to set temporary
> distinct names (like window titles) for small numbers of objects,
> this would probably be OK. If these will be permanent values for
> an ever-increasing number of objects, then you'll need a different
> approach...
>
> mmalc
>
> _______________________________________________
> Do not post admin requests to the list. They will be ignored.
> Cocoa-dev mailing list (<Cocoa-dev...>)
> Help/Unsubscribe/Update your Subscription:
> http://lists.apple.com/mailman/options/cocoa-dev/<eric.morand...>
>
> This email sent to <eric.morand...>
>
-
guillaume garcera Re: Core Data : calling validation in awakeFromInsert problem ? Sep 06 2005, 17:17Le 5 sept. 05 à 21:07, mmalcolm crawford a écrit :
> This approach will be very inefficient for anything but data sets
> where there is little likelihood of name collisions -- you have to
> do an additional fetch for every collision. It's not clear what
> your goal is here. If you're simply wanting to set temporary
> distinct names (like window titles) for small numbers of objects,
> this would probably be OK. If these will be permanent values for
> an ever-increasing number of objects, then you'll need a different
> approach...
>
> mmalc
>
What kind of approach you think ?
I have the same goal i want make unique a propertie but i don't find
solution for this.
I have think to perform this verification in the add methods of the
coressponding controller but i'm not sure it a good solution. Do you
think its good or not ? and what it is the best solution ? -
mmalcolm crawford Re: Core Data : calling validation in awakeFromInsert problem ? Sep 07 2005, 02:36On Sep 5, 2005, at 1:08 PM, Eric Morand wrote:
> Well, that's simple : just like in iTunes, when you create two orNo, not at all, the idea is good, it's the implementation that might
> three playlist without renaming them, the playlist name is append
> with "(index++). I need the same kind of behavior : in my app, user
> can add managed objects via a "Add" button and I want the name of
> the added object to "increase" automatically. Does it seem a bad
> idea to you ?
>
be sub-optimal.
> How am I supposed to do if I want to be sure that a property isThe validation method is fine.
> unique for a given entity ?
>
The way you generate the new names might be improved -- executing an
additional fetch for each collision is a (comparatively) huge
overheard. If you're creating small numbers (< 10) of unique names
like this, then you can probably get away with this, otherwise the
approach you take will depend on exactly the behaviour you want. If
all you want is for the name to be unique, then you might for example
create a new entity that stores just a number, and that represents
the current highest value. You just have to retrieve that. If the
user is forced to give a different name, then you might do something
similar just in memory without another entity. Worst case scenario
would be to use the same approach you have at the moment but fetch
all the current instances in one go and iterate through them in
memory...
mmalc



