CoreData Edit one attribute in One to Many relationship - iphone

I am new to CoreData and been looking to all the books and examples but none of them really tell me how to do this, so any help is greatly appreciated.
Basically, I have 2 Entities in one to Many relation. [other relationships are not important in this case]
The relationship and entities:
Now I can get All the MedicalCondition Entity based on given Profile Entity using NSFetchRequest
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"MedicalCondition" inManagedObjectContext:delegate.managedObjectContext];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"condition" ascending:YES]];
request.predicate = [NSPredicate predicateWithFormat:#"MedicalToProfile = %#", myProfile];
//request.fetchBatchSize = 20;
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:delegate.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
BOOL success = [frc performFetch:&error];
NSArray *fetchedObjectsFromCore;
[request release];
if (success) {
fetchedObjectsFromCore = [frc fetchedObjects];
}
This is ok. Now the problem comes in when I want to update one particular entry. I am not sure how to do it. I can add more MedicalCondition object just fine. But when it comes to edit, I am not sure.
The only way I cant think of is to have "ID" attribute in entity. I think there must be a better solution than this. Please help ! Thankz so much.

If you have a Core Data object from a valid context, then editing it is very easy. Modify the object data, then save its context. Let's say you have a MedicalCondition object that you got hands on somehow.
MedicalCondition *condition;
// modify a field
condition.date = [NSDate date];
// save
NSError *error;
[managedObjectContext save:&error];
Also if you have a given Profile object, you can get all associated MedicalCondition objects directly without having to perform a fetch as long as you do not care about order.
Profile *someonesProfile = ...;
someonesProfile.conditions
// and access a profile from a given MedicalCondition since
// it seems to be a bi-directional relationship.
MedicalCondition *someCondition = ...;
someCondition.profile.dateofbirth;
You should give more meaningful names to the relationships instead of MedicalToProfile, ProfileToMedication, etc. For example, instead of ProfileToMedical, maybe use:
medicalConditions
which is semantically nicer, and reads better in code:
someonesProfile.medicalConditions

Thankz again Anurag.
I kindna got it working.
this is how I did it
NSSet *newMedical = myProfile.ProfileToMedical;
NSMutableArray *arrayMedical = [NSMutableArray arrayWithArray:[newMedical allObjects]];
MedicalCondition *c = [arrayMedical objectAtIndex:1];
c.condition = #"Amazing";
And It update the right place :)
But now when I call again
NSSet *details = myProfile.ProfileToMedical;
NSMutableArray *arrayDetail = [NSMutableArray arrayWithArray:[details allObjects]];
The return NSSet is show the updated condition to be at other index. I understand that is because myProfile.ProfileToMedical is unsorted? so I must always sort the array first before I view/edit attribute to ensure the consistency?
Thankz again

Related

how to properly save into coredata one to many relationship

I am quite new into saving into coreData and using iOS dev.
What I am trying to achieve:
I want to be able to have a user in my db that has a unique identifier / is pulled with idFB and that user can create and retrieve their work out routines.
How far have I gone?
I managed (I think) to create a method that properly retriev the routineName from the Routine entity that is associated with the right User. See the fetch method.
My problem:
I think I am not saving with the right entities relationship association User (usersExercise) <--->> Routine (userID). In order words I think my save method is not right... as I am saving the whole user to userID and it just doesnt feel right? Mainly because when it spits out the Routine.userID it pulls the whole associated user instead of a specific ID? i dont really know what to expect
Could anyone please help me build these method properly? I am very confused with the whole process of coreData saving and making the right relationships.
- (void) save {
Routine *newRoutine = [NSEntityDescription insertNewObjectForEntityForName:#"Routine" inManagedObjectContext:context];
newRoutine.users = [self getCurrentUser];
newRoutine.routineName = #"myRoutine Test Name";
NSError* error;
[context save:&error ];
NSLog(#"Saved now try to fetch");
[self fetch];
}
-(void) fetch {
NSFetchRequest *fetchRequestItems = [[NSFetchRequest alloc] init];
NSEntityDescription *entityItem = [NSEntityDescription entityForName:#"Routine" inManagedObjectContext:context];
[fetchRequestItems setEntity:entityItem];
User* user = [self getCurrentUser];
// if i try [[self getCurrentUser] usersRoutine] it shows an error
[fetchRequestItems setPredicate:[NSPredicate predicateWithFormat:#"users == %#",user]];
//Sort by last edit ordered
NSArray *sortDescriptors = [NSArray arrayWithObjects:nil];
[fetchRequestItems setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray* Routines = [context executeFetchRequest:fetchRequestItems error:&error];
NSLog(#"result %#", [(Routine *)Routines[0] users] );
}
-(User *)getCurrentUser {
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
if (_appDelegate.isFB)
{
request.predicate = [NSPredicate predicateWithFormat:#"idFB LIKE %#",_appDelegate.fdID];
NSError *error = nil;
NSArray *matches = [[context executeFetchRequest:request error:&error] mutableCopy];
return (User *)matches[0];
} else
{
NSLog(#"CreateRoutinePOPUP NON FB TO BE TESTED");
request.predicate = [NSPredicate predicateWithFormat:#"email LIKE %#",_appDelegate.currentUser];
NSError *error = nil;
NSArray *matches = [[context executeFetchRequest:request error:&error] mutableCopy];
return (User *)matches[0];
}
This is what the NSLog in fetch is printing:
2013-04-28 22:33:26.555 iGym[7916:c07] result <User: 0xa480580> (entity: User; id: 0xa495a00 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/User/p1> ; data: {
dob = "1986-12-26 00:00:00 +0000";
email = ".com";
firstTime = nil;
gender = male;
height = nil;
idFB =3333;
idUserExternal = 0;
idUserInternal = 0;
isPT = nil;
language = "en_US";
location = "London, United Kingdom";
metricSystem = nil;
name = Joan;
nickname = nil;
password = nil;
surname = Thurft;
usersExercise = "<relationship fault: 0xa4824a0 'usersExercise'>";
usersRoutine = (
"0xa495f00 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p6>",
"0xa4877e0 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p1>",
"0xa4877f0 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p2>",
"0xa487800 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p3>",
"0xa487810 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p4>",
"0xa487820 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p5>"
);
weight = nil;
})
also when i add NSLog(#"get current result %#", [(User *)matches[0] usersRoutine] ); to the getCurrentUser method I get the whole user's data and the relationship says
usersExercise = "<relationship fault: 0xa464730 'usersExercise'>";
Core Data is not exactly like working with a standard database where you assign some foreign key like userID to another table where you want a relationship to the User object and then use that foreign ID to find the relationship like exercise.where('user_id = ?', userID). Instead, you define actual relationships and let Core Data handle everything behind the scenes for setting up any join tables or foreign keys.
Instead of how you have it set up, you'd just have in the User entity two relationships for exercises and routines that are mapped to the Exercise and Routine entities and then you'd have an inverse relationship on the Exercise and Routine called users if it's a has-and-belongs-to-many relationship. So now, you need to replace usersExercise with exercises, usersRoutine with routines and then userID with users for the Exercise and Routine entities.
Even if you don't actually need that inverse relationship, you still need it since Core Data uses it for data integrity purposes and Xcode will give you a warning if you leave it unpopulated.
When you set up those relationships, then you would call the routines or exercises like user.exercises which will return the associated set of exercises for that user. As you noticed, Core Data will return what they call a fault for a relationship that will get fired and the data returned when you actually need the contents of that relationship. Faults are there so that you are only returned exactly what info you need instead of running unnecessary queries on the data set.
Another thing to note is that Core Data doesn't reference unique id's like userID as you are doing. Instead, each object within Core Data has a unique ID found by [objectName objectID] (which is only permanent after it's been saved to the data store). You really shouldn't need to setup a unique ID as an attribute on an entity except for special cases.
Also, you really shouldn't need to use those unique objectID's unless you're passing objects around like in a multi-threaded application for background processing in which case NSManagedObjectID is thread-safe and you can use it to find the object again on a background thread/managed object context.
I'd really recommend reading a good intro to Core Data such as http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started
It can be a little strange at first converting to Core Data if you're used to normal database setup/architecture, but once you get used to it, it's actually a lot faster and handles all of the hard work behind the scenes for you.
Update from the comments:
You're misunderstanding the concept of relationships in Core Data. In Core Data, a relationship does not return an associated ID like a typical database join relationship would. Instead, it returns a fault which gets fired when you need the data from that relationship. So it's not returning the entire User object, but a fault to the associated User object which gets fired and queried when you do something like exercise.user.name
Your code is working exactly like it should be when you're saving, you are just under the incorrect assumption that it's not.
You need to use the provided method to add a "many object" in the one to many object. In your case it is called addRoutineObject:
Try this new save method:
- (void) save {
Routine *newRoutine = [NSEntityDescription insertNewObjectForEntityForName:#"Routine" inManagedObjectContext:context];
newRoutine.routineName = #"myRoutine Test Name";
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSArray *matches;
NSError *error = nil;
if (_appDelegate.isFB)
{
request.predicate = [NSPredicate predicateWithFormat:#"idFB LIKE %#",_appDelegate.fdID];
matches = [[context executeFetchRequest:request error:&error] mutableCopy];
} else
{
NSLog(#"CreateRoutinePOPUP NON FB TO BE TESTED");
request.predicate = [NSPredicate predicateWithFormat:#"email LIKE %#",_appDelegate.currentUser];
matches = [[context executeFetchRequest:request error:&error] mutableCopy];
}
if (matches.count == 0)
{
NSLog(#"no user matched");
}
else
{
User *aUser = [matches objectAtIndex:0];
[aUser addRoutineObject:newRoutine];
if (![context save:&error])
{
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
}
}

Core Data: Save unique object ID

I see that there is a method of getting the unique id of a managed object:
NSManagedObjectID *moID = [managedObject objectID];
But, as I have read, that changes when it is saved.
What is a better way of creating my own unique ID and saving it in each object, making sure that it IS unique and does't change?
You cannot save the NSManagedObjectID in a CoreData entity, and there's no properties in it that can be intended as integer or string ID.
Therefore building your own unique identifier algorithm is an acceptable solution, if it's impossible for you to keep track when the entity is saved, I did this for some applications.
For example I had a similiar problem time ago, when I needed to fill a UITableView cells with a reference to the entity, and retrieve the entity after clicking/touching the cell.
I found a suggestion by using the uri representation, but however I still needed to save the context before, but I manage to use the NSFetchedResultsController and found a more solid solution rather than an application built id.
[[myManagedObject objectID] URIRepresentation];
Then you can later retrieve the managed object id itself:
NSManagedObjectID* moid = [storeCoordinator managedObjectIDForURIRepresentation:[[myManagedObject objectID] URIRepresentation]];
And with the moid I could retrieve your managed object.
I have a created date property in my object, so I just ended up using that date including seconds, which, seems to me like it will be unique and work how I want.
You can create an id field for your object, and populate it during init using GUID. for how to create a GUID and optionally export it to string see UUID (GUID) Support in Cocoa
If it helps anyone else searching for this, this is how I do it:
Create an ID property on the object
Get the last used ID when creating an object with this code:
Playlist *latest;
// Define entity to use and set up fetch request
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Playlist" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define sorting
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"listID" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
// Fetch records and handle errors
NSError *error;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
if (!fetchResults) {
NSLog(#"ERROR IN FETCHING");
}
if ([fetchResults count] == 0) {
latestID = 0;
} else {
latest = [fetchResults objectAtIndex:0];
latestID = latest.listID;
}
Increment this by one on the new object.

Is there a more efficient alternative to find an object with a to-one relationship to two objects I have?

I have the following Core Data setup:
Project has-many Answer
Field has-many Answer
Answer has-one Field
Answer has-one Project
I need to find the Answer for each Field that is also owned by Project. I'm currently using a predicate for this and executing a fetch request:
NSEntityDescription *answerEntity = [NSEntityDescription entityForName:#"Answer" inManagedObjectContext:self.managedObjectContext];
NSPredicate *answerPredicate = [NSPredicate predicateWithFormat:#"ANY project == %# && field == %#", self.project, self.field];
NSFetchRequest *answerRequest = [[NSFetchRequest alloc] init];
[answerRequest setEntity:answerEntity];
[answerRequest setPredicate:answerPredicate];
NSError *error = nil;
NSArray *predicates = [self.managedObjectContext executeFetchRequest:answerRequest error:&error];
I'm still new to Core Data but I believe the fetchRequest is querying the database each time I call it, is there a more efficient way of finding these Answer objects?
If I understand correctly, you already have a Field object and an Project object and you want to find the Answer objects they have in common.
If so, the solution is a simple intersect set operation:
NSSet *answersInCommon=[[aFieldObj mutableSetValueForKey:#"answers"] intersectSet:[aProjectObj mutableSetValueForKey:#"answers"]];
... which will return only those Answer objects that appear in both relationships.
Update:
#pdenya in comment provides an enhancement :
Just want to clarify a minor error and a small point that makes this less than ideal. intersectSet returns (void) so the syntax for this would be:
NSMutableSet *answers=[field mutableSetValueForKey:#"answers"];
[answers intersectSet:[project mutableSetValueForKey:#"answers"]];
This solution also modifies the aFieldObj.answers array meaning you can't use this while iterating. setWithSet clears this right up. Example:
NSMutableSet *answers = [NSMutableSet setWithSet:[project mutableSetValueForKey:#"answers"]];
[answers intersectSet:[field mutableSetValueForKey:#"answers"]];
#pdenya's is the correct form.
Best alternate method I've found so far:
NSMutableSet *answers = [self.project mutableSetValueForKey:#"answers"];
NSPredicate *answerPredicate = [NSPredicate predicateWithFormat:#"field == %#", field];
[answers filterUsingPredicate:answerPredicate];
NSManagedObject *answer = nil;
if([answers count] > 0) {
answer = [[answers allObjects] objectAtIndex:0];
}

Core-Data: How to set up relationship from one object to specific one in many objects with same name?

I have a question in core data:
there are 2 Entities in the project, Books and Pages;
there are 3 Objects books user created in Entity Books;
there are several Objects pages user created in Entity Pages;
relationship inbetween is one page belongs to one book, one book has many pages.
and my question: there are 3 book with the same object name "book",and each has unique attribute .bookName : #"metal" ;#"plastic" ;#"glass". how to set page to the book with .bookName = #"glass" ?
//In BookViewController
Books *book = (Books *)[NSEntityDescription insertNewObjectForEntityForName:#"Books" inManagedObjectContext:managedObjectContext];
//textView.text is user input text
book.bookName = textView.text;
//In PageViewController
Pages *page = (Pages *)[NSEntityDescription insertNewObjectForEntityForName:#"Pages" inManagedObjectContext:managedObjectContext];
page.itsbook = WHAT?
Thank you for reading, I stuck here like: a day, really appreciate your help, love you!
If you need to find a specific book then you need to use a NSFetchRequest to ask Core data for it.
Quite some code is needed, so you probably add a convinience method to your Bookclass that looks something like this:
+(Book*)bookWithName:(NSString*)name
{
// 0. I assume you have something like this to get the context…
NSManagedObjectContext* context = [NSManagedObjectContext threadLocalContext];
// 1. Create an empty request
NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
// 2. Set the entity description for the object to fetch
NSEntityDescription* entity = [NSEntityDescription entityForName:#"Book"
inManagedObjectContext:context];
[request setEntity:entity];
// 3. Set a predicate asking for objects with a mathing bookName property
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"%K LIKE[cd] %#",
#"bookName",
name];
[request setPredicate:predicate];
// 4. Execute the request on the managed object context.
NSArray* objects = [context executeFetchRequest:request error:NULL];
// 5. Result is an array, maybe handle empty array and many objects?
return [objects lastObject];
}
I'm not sure what you mean. If you want to insert a page as a subset of the book you want this line of code:
[book addPageToBookObject:page]; // the method will be the relationship name
If you want to get the book from the page object you'll need an inverse relationship from the pages to the book.

Core Data : How to check for the presence of Many to Many relationship

I have a "Song" Entity and a "Tag" entity and they have a many to many relationship between them. A Song can have multiple Tags and a Tag can be applied to multiple Songs.
I want to check if a Song has a particular Tag associated with it. If the Song has the Tag associted with it, I want to show a checkmark in the table view.
For a similar logic, in Apple "TaggedLocations" sample code, the following check is made to check for the presence of the relationship.
if ([event.tags containsObject:tag]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
This may be inefficient if there are a lot of Tags in the database as this will fetch all of them in the memory. Please correct me if I am wrong here.
Is there a more efficient way to check if the Song is associated with a particular Tag instead of checking in Song.Tags?
It's actually pretty easy to do, if completely undocumented. You want to create a fetch request with a predicate that has a set operation. If we imagine that your Tag model has a property called tagValue, the predicate you care about is "ANY tags.tagValue == 'footag'"
NSString *tagSearch = #"footag";
// However you get your NSManagedObjectContext. If you use template code, it's from
// the UIApplicationDelegate
NSManagedObjectContext *context = [delegate managedObjectContext];
// Is there no shortcut for this? Maybe not, seems to be per context...
NSEntityDescription *songEntity = [NSEntityDescription entityForName:#"Song" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:songEntity];
// The request looks for this a group with the supplied name
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.tagValue == %#", tagSearch];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
[request release];
You are correct, using that code will retrieve the entire set and the object comparison may be quite complex, depending on how many properties and relationship are part of the object's entity.
Anyway, you can not avoid a set comparison for inclusion. Probably, the best you can do is to avoid fetching all of the properties/relationships by asking Core Data to retrieve NSManagedObjectID Objects only.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Tag" inManagedObjectContext:[self managedObjectContext]]];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSManagedObjectID objects are guaranteed to be unique, therefore you can safely use them to check for set inclusion. This should be much more efficient for a performance perspective.