coreData existing validation - ios

so I'm doing application form using CoreData
First I'm creating "Shop" with unique name and some properties.
In application you can edit that "Shop", and I'm trying to make validation by "shopName" to avoid a creation another "Shop" with same name.
I'm using this:
-(BOOL)uniqueEntityExistsWithEnityName {
BOOL returnValue = NO;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Shop"];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"shopName = [cd] %#", _shopName.text];
NSSortDescriptor *shop = [[NSSortDescriptor alloc] initWithKey:#"shopName" ascending:YES];
[request setSortDescriptors: #[shop]];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *matches = [self.managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"request = %#",predicate);
if (!matches) {
NSLog(#"Error: Couldn't execute fetch request %#", error);
}
else if([matches count] > 1) {
NSString *existShop = [NSString stringWithFormat:#"Could Be Only One %# Shop", _shopName.text];
UIAlertView *exist = [[UIAlertView alloc]initWithTitle:#"Shop Exists in Your Records"
message:existShop
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[exist show];
NSLog(#"Error: Have more than %lu records",
(unsigned long)[matches count]);
returnValue = YES;
}
else {
NSLog(#"%lu object in record", (unsigned long)[matches count]);
[self oldShopDelete];
[self checkShopPhoneNumber];
editShop.shopName = _shopName.text;
editShop.shopPhoneNumber = _shopPhoneNumber.text;
editShop.shopSecondPhoneNumber = _shopSecondPhoneNumber.text;
editShop.shopEmail = _shopEmail.text;
editShop.shopNote = _shopNoteView.text;
[super saveAndDissmiss];
returnValue = YES;
}
return returnValue;
}
With that code you still have opportunity to save one more edited "Shop" with same name.
But the thing is - I can not make [matches count] = 1 after this I'll no chance to edit that Shop
Maybe there are another way to do such validation?

Check for a name clash only when the Name is actually being set for the first time or edited.
You can also pass the current shop into the predicate to ensure AND SELF != %# so there will be no match with an existing shop being edited but with an unchanged name.

Related

IOS/Core Data: Retrieve single user object from entity

I am trying to retrieve a user object for the primary app user in order to have access to the user's name, photo etc.
The code below returns null. Is this valid code for retrieving an object? If not, it may be that the user info is not getting stored properly in core-data in the first place....if only you could easily view the underlying sql-lite file--but that is another matter. However, I'd like to rule out an issue with the code that retrieves the user object.
- (User *)getUserInfo:(NSNumber *)userid
inManagedObjectContext:(NSManagedObjectContext *)context
{
User *user = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"User"];
request.predicate = [NSPredicate predicateWithFormat:#"userid == %#",userid];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"userid" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [_managedObjectContext executeFetchRequest:request error:&error];
if (!matches || [matches count] > 1) {
// handle error
} else {
user = [matches lastObject];
}
return user;
}
//This is returning null currently. Is there an error visible somewhere?
Edit:
This is how it is called:
//in viewdidload
_managedObjectContext = [myModel sharedInstance].managedObjectContext;
NSNumber *useridnum =[[NSUserDefaults standardUserDefaults] valueForKey:#"userid"];
User *user = [self getDemoInfo:useridnum
inManagedObjectContext:_managedObjectContext];
NSLog(#"user is%#",user);//logs as null
One way your user will stay nil if the [matches count] > 1 is true , so check that you didn't made duplicated user objects with the same id

How to select particuler column in core data?

When the user has Pressed Login button i want to display the particular first name in alert view when the user has entered their username and password.
Please help me.
Thanks in advance.
Help will be appreciated.
- (IBAction)LoginPressed:(id)sender
{
if (managedObjContext == nil) {
AppDelegate *app = (AppDelegate*)[UIApplication sharedApplication].delegate;
managedObjContext= app.managedObjectContext;
}
NSFetchRequest *request =[[NSFetchRequest alloc]init];
NSEntityDescription *rocordTableEntity = [NSEntityDescription entityForName:#"RecordTable" inManagedObjectContext:managedObjContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"username==% #AND password==%#",L_usernameField.text,L_passwordField.text];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"firstname=%#",[managedObjContext valueForKey:#"firstname"]];
[request setEntity:rocordTableEntity];
[request setPredicate:predicate];
[request setPredicate:pred];
NSError *error;
NSArray *arrarforCheckingUserANDPass = [managedObjContext executeFetchRequest:request error:&error];
NSString *value = [arrarforCheckingUserANDPass valueForKey:#"firstname"];
if (arrarforCheckingUserANDPass.count != 0) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Welcome" message:value d elegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}
else{
NSLog(#"not matched");
}
}
Your second setPredicate overwrites the first one. You do not need it. You get the whole entity and then simply access the firstName property. Your first predicate has wrong syntax.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"RecordTable"];
request.predicate = [NSPredicate predicateWithFormat:
#"username==%# AND password==%#",L_usernameField.text,L_passwordField.text];
NSArray *result = [self.managedObjectContext executeFetchRequest:request error:nil];
RecordTable *user = result.firstObject;
if (user) {
NSLog(#"The first name is %#.", user.firstName);
}
BTW, why does your method start with a capital letter? That is a very bad habit. Your entity name is also a disaster. "RecordTable" is about the worst entity name you could choose. If it is a user, call it "User". Following convention, I camel-cased your firstName property as well.

IOS Core Data - Find or Create duplicates inserts

I'm missing something in my logic when trying to sync web service data with local store and I need your help. This is what I've got:
I have one NSArray of NSDictionaries describing each event object (downloaded from web), which I sort by event id. Then I fetch local store using IN predicate and also sort it by event id. Then I try to iterate and match the ids and if they match, i update record and if they don't match i create new NSManagedObject. It works fine if the newly downloaded event object has a greater eventID than last eventID in local store, but if the eventID from web service is smaller than the one in local store then it INSERTS ALL OBJECTS, no matter if they exist or not and that exactly is my problem.
So in other words, if a new record is at the beginning of sorted array it will add every object, but if it is at the end of sorted array it will update all except the new one. I need it to create the new one and update old ones.
Here's some code:
The function with the logic where I believe I'm missing something:
- (void)findOrCreateObject:(NSArray*)eventArray
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
//get sorted stored records
NSArray *fetchedRecords = [self.fetchedResultsController fetchedObjects];
//sort dictionaries
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"id" ascending:YES];
NSArray *downloadedRecords = [self.events sortedArrayUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
NSLog(#"DOWNLOADED EVENTS = %#", downloadedRecords);
NSLog(#"FETCHED EVENTS = %#", fetchedRecords);
//if store is not empty we need to walk through data and add/update records, otherwise/ELSE we need to import initial data
if (fetchedRecords.count != 0) {
//stores has records already
NSLog(#"FIND OR CREATE PROCESS");
if ([downloadedRecords count] > 0) {
NSArray *storedRecords = [self fetchEvents:eventArray withContext:context];
NSUInteger currentIndex = 0;
for (NSDictionary* event in downloadedRecords) {
Event* eventObject = nil;
if ([storedRecords count] > currentIndex) {
eventObject = [storedRecords objectAtIndex:currentIndex];
}
NSLog(#"STRING VALUE OF KEY = %#", [[eventObject valueForKey:#"eventID"]stringValue]);
if ([[event valueForKey:#"id"] isEqualToString:[[eventObject valueForKey:#"eventID"] stringValue]]) {
//Update Record
NSLog(#"Updating Record!!!");
[self updateManagedObject:eventObject withRecord:event inContext:context];
}
else
{
//New Record
NSLog(#"Inserting Record!!!");
eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
currentIndex++;
}
}
}
else
{
//import initial data
NSLog(#"IMPORTING INITIAL DATA");
for (NSDictionary* event in downloadedRecords) {
Event *eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
}
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
The FETCHEVENTS method:
-(NSArray*)fetchEvents:(NSArray*)eIDs withContext:(NSManagedObjectContext*)context
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(eventID IN %#)", eIDs];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:#[ [[NSSortDescriptor alloc] initWithKey: #"eventID" ascending:YES] ]];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"No rows returned");
}
return fetchedObjects;
}
The Update Object method:
- (void)updateManagedObject:(NSManagedObject*)object withRecord:(NSDictionary*)record inContext:(NSManagedObjectContext*)context
{
[object setValue:[self makeNumberFromString:[record valueForKey:#"id"]] forKey:#"eventID"];
[object setValue:[record valueForKey:#"title"] forKey:#"title"];
[object setValue:[record valueForKey:#"venue"] forKey:#"venue"];
}
I'm calling findOrCreate method once I download the web service data and parse it.
Let me know if you have any other questions.
Try this,
- (void)findOrCreateObject:(NSArray*)eventArray {
//if store is not empty we need to walk through data and add/update records, otherwise/ELSE we need to import initial data
if (fetchedRecords.count != 0) {
//stores has records already
NSLog(#"FIND OR CREATE PROCESS");
if ([downloadedRecords count] > 0) {
NSArray *storedRecords = [self fetchEvents:eventArray withContext:context];
for (NSDictionary* event in downloadedRecords) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"eventID = %#",[event valueForKey:#"id"]];
NSArray *matchedArray = [storedRecords filteredArrayUsing
Predicate:predicate];
Event* eventObject = nil;
if ([matchedArray count] > 0) {
//Update Record
NSLog(#"Updating Record!!!");
eventObject = [matchedArray objectAtIndex:0];
[self updateManagedObject:eventObject withRecord:event inContext:context];
}
else
{
//New Record
NSLog(#"Inserting Record!!!");
eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
}
}
} else {
.....
}
}
I think, every time you insert a new event object, you should update storedObjects such that it should now contain the inserted object.
Or more simply, you should put the initialisation line of storedObjects inside your for loop. (This would make sure that as you enumerate from the beginning of downloadedObjects every eventObject will have the same index on it as on storedObjects. But, with regards to your code this will only be true if all elements of storedObjects will always be found in downloadedObjects which, I assume is the case.)
One thing though, isn't fetchedRecords supposed to be the same as storedObjects, if they are you should just reassign storedObjects as [self.fetchedResultsController fetchedObjects], as it would reflect the changes in your context without executing another fetch request which would solve the inefficiency of the suggestion above.

How to update in Core Data?

I saw many questions about Core Data updates. Actually I am creating a simple application contact list app. It contains add, edit, delete and update functionalities. Here my update code. It works and updates, but it updates all the contact list. I need to update specific contacts only.
- (IBAction)updatePressed:(id)sender
{
delegate = [[AppDelegate alloc]init];
delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
name2 = emailtxt1.text;
email2 = nametext1.text;
mobile2 = numbertxt1.text;
dict = [[NSMutableDictionary alloc] init];
[dict setObject:nametext1.text forKey:#"NAME"];
[dict setObject:emailtxt1.text forKey:#"EMAIL"];
[dict setObject:numbertxt1.text forKey:#"MOBILE"];
[delegate UpdateDiary:dict];
}
- (void)UpdateDiary:(NSMutableDictionary *)dictionary
{
NSLog(#"update book Details Function Entered");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Diary"inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *mutableFetchResult = [[[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy] autorelease];
if (mutableFetchResult == nil) {
NSLog(#"Fetch result error %#, %#", error, [error userInfo]);
}
for (Diary *ob2 in mutableFetchResult)
{
{
ob2.name = [dictionary objectForKey:#"NAME"];
ob2.email=[dictionary objectForKey:#"EMAIL"];
ob2.phone=[dictionary objectForKey:#"MOBILE"];
}
}
if(![self.managedObjectContext save:&error])
{
if([error localizedDescription] != nil)
{
NSLog(#"%#",error);
}
else
{
}
}
}
You need to set a predicate on your fetch request. That's how it knows which object(s) you want, rather than just fetching them all.
You could do something like:
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"email == %#", anEmailAddress];
If you did that, then the result of executing the fetch request would just be objects that matched the email address you set in the predicate.
Note, of course, that if there is more than one object with the same email address, then the fetch request would fetch all of them.
A better design for your app might be, when you go into the edit form, keep around the Core Data object that you're editing, possibly in a property on your view controller. (You'll have it around at that point I reckon, since you'll need to know what to populate the fields with.) That way you don't need to perform a fetch at the time the user is trying to commit the edit — you can just use the object you've kept around.
- (void)UpdateBook:(NSMutableDictionary *)dictionary
{
NSLog(#"update book Details Function Entered");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Book"inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"bookID = %#", [dictionary objectForKey:#"vID"]];
NSArray *mutableFetchResult = [[[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy] autorelease];
if (mutableFetchResult == nil) {
NSLog(#"Fetch result error %#, %#", error, [error userInfo]);
}
for (Book *ob2 in mutableFetchResult)
{
{
ob2.name = [dictionary objectForKey:#"VName1"];
ob2.author=[dictionary objectForKey:#"VAuthor1"];
ob2.discription=[dictionary objectForKey:#"VDiscription1"];
ob2.bookID=[dictionary objectForKey:#"vID"];
}
}
if(![self.managedObjectContext save:&error])
{
if([error localizedDescription] != nil)
{
NSLog(#"%#",error);
}
else
{
}
}
}

ios UIAlertView: make alert wait for my response [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Make UIAlertView blocking
Been beating my head against the wall for hours on this one.
Here's what I've got: OrderDetailsViewController is set as an UIAlertViewDelegate
I've got a procedure that receives information back from a search form. It checks to see if the item is already on the order and if not continues adding the item(s). If it sees a duplicate, it pops up a UIAlertView asking what the user wants to do: there are 3 options, "Combine" - add the new qty to the old item, "Add" the duplicate as a separate line item, or "Cancel" throw the new item away. I need it to wait for an answer from the UIAlertView so that I can continue adding the dupe or throwing away the dupe -- the "Combine" is handled in the delegate, but I still need an answer for the main procedure.
Here's what I have so far:
- (void)returnItemAndQty:(ProductsSearchController *)productsSearchController
withItemsToAdd:(NSMutableArray *)itemsToAdd
withQty:(NSDictionary *)qtyToAdd andClose:(BOOL)close
{
if ([itemsToAdd count] == 0) {
return;
}
Items *items;
for (int index = 0; index < [itemsToAdd count]; index++) {
items = [itemsToAdd objectAtIndex:index];
qtyAddedToOrder = [NSDecimalNumber decimalNumberWithString:[qtyToAdd objectForKey:items.ItemCode]];
NSLog(#"Item Code: %#", items.ItemCode);
NSLog(#"Qty: %#", [qtyToAdd objectForKey:items.ItemCode]);
NSError *error;
//For handling duplicate items. . .
duplicateItemDisposition = -1; //Reset the dispostion for normal operation
if([self isItemOnOrder:items.ItemCode])
{
int i = [self itemIsAlreadyOnOrder:itemAlreadyOnOrder withQty:qtyAddedToOrder];
if (i == COMBINE || i == CANCEL){ //either Cancel or Combine was pressed.
items.Checked = NO;
if (![items.managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
continue;
}
}
//Add the new item or duplicate if that's what you want
OrdersDetails *newOrderDetail = [NSEntityDescription insertNewObjectForEntityForName:#"OrdersDetails"
inManagedObjectContext:self.managedObjectContext];
.//more code snipped, that handles the "ADD" or non-dupe
.
.
Here's where it tests for a duplicate. . .
- (BOOL)isItemOnOrder:(NSString *)itemCode
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"OrdersDetails"
inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]initWithKey:#"Desc1" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sort, nil];
[request setSortDescriptors:sortDescriptors];
[sort release];
[sortDescriptors release];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(ItemCode=%# AND OrderID=%#)", itemCode, orders.OrderID];
[request setPredicate:pred];
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:& error] mutableCopy];
[request release];
if (mutableFetchResults == nil) {
itemAlreadyOnOrder = nil;
return NO;
}else if ([mutableFetchResults count] > 0){
itemAlreadyOnOrder = [mutableFetchResults objectAtIndex:0];
return YES;
}else{
itemAlreadyOnOrder = nil;
return NO;
}
}
Here's where it sees that a dupe exists and the UIAlertview delegate with it. . .
- (int) itemIsAlreadyOnOrder:(OrdersDetails *)existingOrderDetail withQty:(NSDecimalNumber *)qty
{
if (existingOrderDetail == nil) {
return -1;
}
UIAlertView *duplicateAlert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:#"Duplicate Item %# found.",existingOrderDetail.ItemCode] message:#"Tap Combine to combine the items, Tap Add to add the duplicate item or Tap Cancel to discard the duplicate" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Combine", #"Add", nil];
[duplicateAlert show];
return duplicateItemDisposition;
[duplicateAlert release];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// Cancel = 0, Combine = 1, Add = 2
if (buttonIndex == CANCEL){
duplicateItemDisposition = CANCEL;
}else if (buttonIndex == COMBINE){
duplicateItemDisposition = COMBINE;
NSDecimalNumber *existingQty = [[NSDecimalNumber alloc] initWithDecimal:[itemAlreadyOnOrder.Qty decimalValue]];
NSDecimalNumber *existingPrice = itemAlreadyOnOrder.Price;
NSDecimalNumber *newQty = [existingQty decimalNumberByAdding:qtyAddedToOrder];
itemAlreadyOnOrder.ExtPrice = [newQty decimalNumberByMultiplyingBy:existingPrice];
[existingQty release];
NSError *error;
if (![itemAlreadyOnOrder.managedObjectContext save:&error]){
NSLog(#"Error saving. %#, %#", error, [error userInfo]);
[self handleFreeGoods:itemAlreadyOnOrder];
}else if (buttonIndex == ADD){
duplicateItemDisposition = ADD;
}
}
Now I read something on here about using an NSCondition in a background thread, but I have no idea what that means. I looked up NSCondition, and it was less than enlightening.
Any ideas on how to pause the execution?
The UIAlertView can't block, as it gets displayed at the end of the runloop. Instead of having the method itemIsAlreadyOnOrder: return a value indicating what should be done with the duplicate entry, the delegate method from the UIAlertView needs to notify your controller that the item in question has been resolved. Track the object in question somewhere (_objectToVerify or something like that), and in the delegate method from the UIAlertView call a method based on the users choice, which will act on that _objectToVerify.
I don't know how much this might change your current design, but the Cocoa design pattern would have the method that puts up the alert not return anything, so something like if (duplicate) showAlertView in pseudocode. Then in the alert view's delegate, call a method that actually handles the processing of the duplicate based on user input.

Resources