How can I save an object that's related to another object in core data? - ios

I'm having difficulty with a one to one relationship. At the highest level, I have a one to many relationship. I'll use the typical manager, employee, example to explain what I'm trying to do. And to take it a step further, I'm trying to add a one to one House relationship to the employe.
I have the employees being added no problem with the addEmployeesToManagereObject method that was created for me when I subclassed NSManagedObject. When I select an Employee in my table view, I set the currentEmployee of type Employee - which is declared in my .h.
Now that I have that current employee I would like to save the Houses entities attributes in relation to the current employee.
The part that I'm really struggling with is setting the managedObjectContext and setting and saving the houses attributes in relation to the currentEmployee.
I've tried several things but here's my last attempt:
NOTE: employeeToHouse is a property of type House that was created for
me when I subclassed NSManagedObject
House *theHouse = [NSEntityDescription
insertNewObjectForEntityForName:#"House"
inManagedObjectContext:self.managedObjectContext];
// This is where I'm lost, trying to set the House
// object through the employeeToHouse relationship
self.currentEmployee.employeeToHouse
How can I access and save the houses attributes to the currentEmployee?

since House is setup as an Entity it can be considered a table within the data store. If that truly is the case, you need to setup a 1 to 1 relationship between Employee and House in your data model.
If you have already done so, then it is as simple as calling. Although I'm not as familiar with one to one relationships with Core Data as I am with to-many. In either case, try one of the following
[self.currentEmployee addHouseObject: theHouse];
or
self.currentEmployee.employeeToHouse=theHouse;
then to the save to the managedObjectContext:
NSError *error=nil;
if (![self.managedObjectContext save:&error]{
NSLog(#"Core Data Save Error: %#", error);
}
Also, I'm not sure about your particular situation, but your self.managedObjectContext should already be the same as the one pointed to by self.currentEmployee.managedObjectContext.
Good luck,
Tim

Related

unwanted objects appearing in core data relationship

Long question---thanks in advance for your time. After saving new managed objects, I am finding them added to a relationship on another object in my core data database---one for which my code calls no setter method and that has no inverse relationship. I have pored over the code and used logs to isolate the occurrence the best I can, but I'm encountering bizarre behavior I cannot explain (or fix).
More specifically:
I have an entity called PendingSyncTracker. It simply has one relationship, objectsToSync. I have not yet added any line in my code to call a setter method on this relationship. It is a to-many relationship. It points to BaseEntity. For the "Inverse" option, I have selected "No Inverse Relationship."
When I load a particular table view, 3 objects are downloaded from a server and then parsed into managed objects and saved. By the time the table view begins loading cells, 2 of those 3 objects will mystifyingly be present in the objectsToSync relationship.
I have used NSLog all over my code to figure out exactly when these objects can first be found as members of the objectsToSync set.
NSSet *objectsToSync = [[[SyncEngine sharedEngine] fetchClassNamed:#"PendingSyncTracker" withPredicates:nil][0] valueForKey:#"objectsPendingSync"];
NSLog(#"PendingSyncTracker objectsToSync set (%lu objects): %#", (unsigned long)[objectsToSync count], objectsToSync);
The answer to when they first appear in the set actually varies depending on where I do/don't place those 2 lines of code!
The objects are never found on the relationship before the managed object context is saved in the course of saving my 3 new core data objects.
If I don't use those 2 lines till I'm back in the Table View Controller that sent the new objects off to the Sync Engine to be stored locally (where the MOC is accessed and saved), then the log will there reveal that 2 objects have been added to the relationship.
If I use those 2 lines immediately after saving the MOC in the Sync Engine, then the logs will indicate (both there and back in the TVC) that only 1 object has been added to the relationship.
If I use those 2 lines immediately before and after saving the MOC (and back in the TVC), then all 3 logs will reveal that the relationship contains an empty set.
I also have those 2 lines at the beginning of cellForRowAtIndexPath. Regardless of prior logs, that log will always indicate that 2 objects have been added to the relationship.
All 3 of the managed objects that are created in the Sync Engine are stored as entity types that are subEntities of BaseEntity (to which the objectsToSync relationship points). The 2 types that get added to the relationship are each defined to have a reciprocal relationship, but with a different object, not PendingSyncTracker (although the different object is a subEntity of BaseEntity!).
So.. what explains these observations? How are these objects getting added to the relationship?
UPDATE:
- (NSArray*) fetchClassNamed:(NSString*)className withPredicates:(id)parameters;
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:className inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// set predicates
if (!(parameters == nil)) {
[fetchRequest setPredicate:parameters];
}
NSError *error;
NSArray *fetchedResults = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
return fetchedResults;
}
First, what does [[[SyncEngine sharedEngine] fetchClassNamed... do? Just a guess but it is doing something with KVC to set the relationship for you.
Also, you should always, always, always have an inverse relationship. Even if you never use it, Core Data does. Not having an inverse can lead to lots of issues, including but not limited to performance problems and potentially data corruption.
Add an inverse relationship and update your question with what -fetchClassNamed... does.

Inserting one entity into another one

I don't even know how to title this one:
Lets say I have a manufacturer entity and a model entity, with a one-to-many relationship.
Each manufacturer can have multiple models (just using these as an example).
manufacturer has a tableview and its independent fetchedResultsController, then when you press on a manufacturer cell you go to models viewcontroller that also has its own tableview and fetchedResultsController, ofc showing the relevant added models.
Let's say I would like to take one of the models and copy them or cut them into another manufacturer, I was thinking of a method styled like:
-(void)copyThis:(Model*)model toThat:(Manufacturer*)manufacturer
I am grabbing the right manufacturer object and the right model object but how can I implement the insertion of one to another?
To copy
Model *newModel = [NSEntityDescription insertNewObjectForEntityForName:#"Model" inManagedObjectContext:self.context];
newModel.property = model.property; //For every property
model.relationShipName = manufacturer;
[self.context insertObject:copyModel];
To cut
model.relationShipName = manufacturer;
(I assume that you have an xcdatamodeld and have generated an NSManagedObjectSubclass of your Model and Manufacturer entities)
What do you want to achieve with copying? Do you want a 'new' model with the exact parameters added to the other manufacturer, or do you want the relationship to be with the same model object?
Assuming you want to keep a single instance of the Model object:
Manufacturer *fromManufacturer = ...
Model *model = [[fromManufacturer models] objectAtIndex:...];
Manufacturer *toManufacturer = ...
[toManufacturer insertModelObject:model];
if (isCut) [fromManufacturer removeModelObject:model];
To get the insertModelObject and removeModelObject methods automatically, you can use Xcode to generate NSManagedObject subclasses for you automatically. It's under the Editor menu when you're looking at the CoreData Model file. Note that the names of the methods and objects may be different depending on the CoredData model structure and relationship names you've created.

Adding a relationship in core data

I have been at this single task for several days trying to get the relationships between core data entities working. I have achieved this but now I need to change it so that the new attribute value has its relationship added to an existing object. It is a 1 - to - many database.
I am not sure how to add a relationship to a object that already exists. So in the new object that is getting added to RoutineDetail, how would I create the relationship to the object that already exists in the routine Entity?
I have looked at several examples all showing how to add relationships to newly added objects but I need it so the new object in RoutinesDetails has a relationship with the value that already exists in Routines.
The value of Routines is held in a string called RoutineText
rout is the NSmangedObject for the entity Routines
routDet is the NSmanagedObject for the entity RoutinesDetails
I have left the commented out code that allows me to add a relationship when both new objects are created.
This is the last thing I have to do in my project but it is driving me insane. I will be eternally grateful for the fix here. Any advice will be appreciated as this is the best knowledge portal there is. Thank You.
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new device
ExcerciseInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
//rout = [NSEntityDescription insertNewObjectForEntityForName:#"Routines" inManagedObjectContext:context];
routdet = [NSEntityDescription insertNewObjectForEntityForName:#"RoutinesDetails" inManagedObjectContext:context];
//Add attribute values
//[rout setValue: RoutineText forKey:#"routinename"];
[routdet setValue: info.name forKey:#"image"];
//Create Relationship
[rout addRoutinedetObject:routdet];
Your main problem statement is, I think, here:
I need it so the new object in RoutinesDetails has a relationship with the value that already exists in Routines.
I presume your data model looks like this:
Routine <----> RoutineDetail
i.e. every one routine has one routine detail (one-to-one relationship).
But this does not really make any sense. You could simply include the attributes of RoutineDetail in the Routine entity.
Instead of
desiredValue = routineDetail.routine.value;
you would simply have
desiredValue = routineDetail.value;
Also, please note that your code has a number of problems. The first line is completely unnecessary, just use self.managedObjectContext. Additionally, against the convention you are using Capital initials for variables. Those should be reserved for class names. Your method to add the relationship also does not look right.
You can add a relationship like this, without a method call:
routineObject.detail = detailObject;

Need assistance regarding core data

I am new to core data and trying to create a simple apps using core data. I am currently working on app to save data in to-many relationship, there are several questions and tutorials but I am still confused.
I have two entities person and contactNumbers, I am fetching person and its contact numbers from address book, person A has mobile number, iphone number, home, work, other... which creates to-many relationship.
In my code I didnt subclass any entity, Is it necessary to subclass entities to save data in relationship? I am asking this because I have read that its not necessary to subclass direct working with NSManagedObject class will do the job.
All I want is to save person A first name, last name in person entity and its contact numbers in contactNumbers entity. How to save data in to-many relationship using core data?
Please I request do not refer other stackoverflow questions, raywenderlich tutorial, app code blog tutorials etc.
Please provide a clearly understandable code with concepts to deal this situation. Thanks a lot.
This is my model
if I Do this
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"person" inManagedObjectContext:self.managedObjectContext];
[person setValue:#"first name A" forKey:#"firstName"];
[person setValue:#"last name B" forKey:#"lastName"];
[person setValue:#"123" forKey:#"mobile"];
I got this error
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSManagedObject 0x82abcc0> setValue:forUndefinedKey:]: the entity Person is not key value coding-compliant for the key "mobile".
If i do this
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
[person setValue:#"first name A" forKey:#"firstName"];
[person setValue:#"last name B" forKey:#"lastName"];
NSManagedObject *contactNumber = [NSEntityDescription insertNewObjectForEntityForName:#"ContactNumber" inManagedObjectContext:self.managedObjectContext];
[contactNumber setValue:#"123" forKey:#"home"];
[contactNumber setValue:#"456" forKey:#"iPhone"];
[contactNumber setValue:#"789" forKey:#"mobile"];
[contactNumber setValue:#"111" forKey:#"work"];
[contactNumber setValue:#"112" forKey:#"other"];
its working fine, but how data got related to each other in two entities?
You don't need to subclass.
You just need relations.
Make a relation on Person named contactNumbers that points to the ContactNumber entity, and a matching relation on ContactNumber entity called person that points back to person.
The contactNumber relation should be one to many (because one person has many contacts), while the relation from ContactNumber going back to Person should be one to one (because each contact entry will appear in only one person).
When you get a Person entity then, you'll be able to get a set of ContactNumber objects from the contactNumbers property, and then look through elements in the set. Or you can search ContactNumber entities where the person ID matches the person relation going back to Person.
Followup: Your first code does not work because you cannot reference a single instance through a set.
Your second section of code is fine, just add the line
contactNumber.person = person;
Then you can access the set of contacts from a person at any time with:
NSSet *contacts = person.contactNumbers;
And get the contact number you want. You cannot go directly from a person to one contact without some very tricky keypath stuff.
I would EXTREMELY STRONGLY suggest using Mogenerator to generate data objects after you set up the model, it makes the data clearer as to how you can use it. Using the setKey mechanism with Core Data is UTTERLY INSANE.
You do not need to do any subclassing at all to read and write Core Data relationships (or any other values for that matter). In Core Data, a relationship is represented as an NSMutableSet. Here is some sample code to show you how to retrieve this set and modify it:
// Assuming myPerson is an NSManagedObject instance of the Person entity
NSMutableSet *contactNumbers = [myPerson mutableSetValueForKey:#"contactNumbers"];
// Add numbers normally by calling the set's addObject property
[contactNumbers addObject:myNewNumber];
//etc.
I would also highly recommend taking a look at the Sensible TableView framework if you haven't already done so. The framework will automatically generate the UI for you Core Data entities, including all relationships. It will also automatically handle adding/removing new relationship objects. I myself wouldn't imagine going back to doing stuff manually again without it. Good luck!

How to perform reodering of cells in One-to-many relationship in CoreData

I am learning coreData and I am new it, I have created a one-to-many relationship of boss and employee, (i.e one boss and many employees). So I am showing all the bosses on firstTableView and when the user clicks on the cells, he can view the employees assigned to each boss and also he can add employees to any particular boss. Now I want to reorder the boss cells. So how it should be done?
Edited based on the discussion below
- (void)insertNewObject:(NSString *)fileName
{
Boss *bossName = [NSEntityDescription insertNewObjectForEntityForName:#"Boss" inManagedObjectContext:self.managedObjectContext];
[bossName setName:fileName];
NSManagedObject *lastObject = [self.controller.fetchedObjects lastObject];
float lastObjectDisplayOrder = [[lastObject valueForKey:#"displayOrder"] floatValue];
NSLog(#"%f",lastObjectDisplayOrder);
[bossName setValue:[NSNumber numberWithDouble:lastObjectDisplayOrder + 1.0] forKey:#"displayOrder"];
// Save the context.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[Specific Answer To Updated Question]
It would be either ....
[self.boss insertObject:newEmployeeObject inEmployeesAtIndex:0];
Which is a core-data generated method that is part of your Boss subclass if you choose to create subclasses from your model. Or....
NSMutableOrderedSet *employees = [self.boss mutableOrderedSetValueForKey:#"employees"];
[employees insertObject:newEmployee atIndex:0]
It's not that intuitive I know, you can't just make a mutable copy, you have to get a special proxy object from mutableOrderedSetValueForKey.
[Original General Answer]...
Core-data now has the ability to use "Ordered Relationships" which you can specify in your model. If you do so, relationships in your object model will be represented by a new class NSOrderedSet which is a hybrid of an NSArray and an NSSet. By re-ordering the objects in this relationship object and saving the context you will reorder the objects in the database and they will maintain their new order. This kind of ordered relationship tends to be used when there isn't natural ordering attribute on the object. For instance the order simply represents the users preference for ordering a list in the UI.
If on the other hand you have an attribute on one of your objects that describes the order for a collection then you can use that attribute to order the results of an NSFetchRequest by specifying the Sort Descriptors. The value of the attribute would specify the position the object would be in in the results of the NSFetchRequest.
If you are using Ordered Relationships you would need keep the order of the NSOrderedSet for that relationship and the UITableView in sync. If the change was driven from the UI then you respond to the UITableViewDataSource delegate methods such as - (void)moveRowAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex and use the information provided to move the corresponding object to it's new position in the core-data relationship either by using the proxy object from mutableOrderedSetValueForKey: or the Core-data generated accessors of a generated subclass.
If the change to order were driven from the data side you would use the methods on UITableView such as insertRowsAtIndexPaths:withRowAnimation: and moveRowAtIndexPath:toIndexPath: to sync the rows in the UITableView with the changes you were making in the data.
If you are using NSFetchRequests you have a similar task. In this case you respond to user driven changes in the order by updating the sort attributes on your objects to match the new order that is described by the UITableView through the UITableViewDataSource delegate. Or if the ordering changes are starting at the data side you update the UITableView through it's methods to match the changes you are making to the sort attributes on the data. In this case you will be working with the results from the NSFetchResults as an NSArray, you would also have to keep that object in sync until the next time you ran the NSFetchRequest. You could use the same sort descriptor to sort the array, or create an NSMutableArray and use it's methods to move the data to match the table.
Finally if you are using NSFetchRequest you may like to look at NSFetchedResultsController It's job it is to simplify task of syncing a sorted NSFetchRequest and a UITableView. There is good sample code for this in the documentation. In this case you may find that the ordering of the data will take care of itself. For instance say your table is ordered by "assignment date" (i.e. the date at which an employee was assigned to a boss) then simply creating the objects with the correct information would trigger the correct results in the table.
Please note that ordered relationships do not work with iCloud. However in my opinion iCloud doesn't work anyway so that's not a problem.

Resources