RestKit, Core Data, and Relationship problems. Parent object not recognizing child set - ios

I am trying to map a relationship between Articles and the Photos that belong to them using RestKit. Both objects get stored properly when requesting the resource, but it seems the relationship does not persist. In fact, the Article model seems to not even respond to the Photos selector (This may be the 'duh' spot, but I will provide full code to be through).
I've provided all code in a gist, as I find it easier to look through and format then on StackOverflow. Sorry if this is actually an inconvenience.
https://gist.github.com/3733334
And here is the image of the core data model and the relationships set up (sorry, I had to combine them since I can only post 2 hyperlinks currently):
http://imageshack.us/a/img33/5039/stackoverflowissue.jpg
Everything seems to be working properlly except the relationship between the objects when I try to access photos via anArticle.photos. The selector is unrecognized. I set up a convience method in the Photo model to return all photos with a matching article ID, but I feel this is an un-ideal solution as it sort of removes the whole idea of creating a relationship.
I feel there may be something simple I am missing and any help would be greatly appreciated.
Thanks!

So of course it was a "Duh" error. After some help from a local developer, he pointed out that my Photos variable in my Article.h file was an NSArray, and needed to be changed to an NSSet to store objects mapped by RestKit.

Theres some inconsistency between different versions of RestKit. If you are using the latest one mappings should be set up as shown here: https://github.com/RestKit/RestKit/wiki/Object-mapping. If you want to use entity classes for model specific methods make categories on your NSManagedObjects so that when you change your data model you can regenerate them (Do this only after you extract your methods to a category! Select an entity in your .xcdatamodeld and go to Editor -> Create NSManagedObject Subclass...).
I moved my mappings to the controller that is responsible for syncing with the remote API.
This shuld be helpful too: http://andriyadi.me/logging-in-restkit/.
Also Core Data guidelines stress that you should set up inverse relations (but it's not obligatory).
Fetching entities can also be done better in my opinion. In my project I have an NSObject subclass singleton that (among some other Core Data convenience functionalities) fetches by entity and predicate:
- (NSArray *)fetchEntities:(NSString *)entity usingPredicate:(NSPredicate *)predicate {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error) {
RLog(#"Error fetching entity %# using predicate %#", entity, predicate);
abort();
}
if ([results count] >= 1) {
return results;
}
return nil;
}
You can alter it to pass predicates as NSStrings too.
Side note:
Currently I'm also struggling with RestKit and object mapping myself :).

Related

Questions on core data

I am starting to develop core data database and i do have a few questions that I can,t understand. Can any one please explain in brief and please keep it simple.
1)
NSManagedObject *employee=[NSEntityDescription insertNewObjectForEntityForName:#"Employee" inManagedObjectContext:_managedObjectContext];
[employee setValue:self.empnametextfield.text forKey:#"empname"];
[employee setValue:self.empidtextfield.text forKey:#"empid"];
while saving the data into database into the database i use this code. But why I am creating instances of NSManagedObject & NsentityDescription?
2)
_fetchrequest=[[NSFetchRequest alloc]init];
NSEntityDescription *entity=[NSEntityDescription entityForName:#"Employee" inManagedObjectContext:_managedObjectContext];
[_fetchrequest setEntity:entity];
NSError *error;
_fetchedobjects=[_managedObjectContext executeFetchRequest:_fetchrequest error:&error];
And when i fetch data i use this coding. So my question is why do i use the instance of NSEntity description here?
3)What is the difference between the purposes for which we use "NSEntityDescription" while in saving & fetching data?
Please answer the above 3 questions of mine as I am quite stuck in it?
Thanks in advance.
When you build your core data stack you load a model which describes the data structure objects and relationships. This is built in terms of entity descriptions. They hold the format for the data, the names, types, multiplicities and rules associated. Without this you have no structure, you might as well just have a generic NSSet.
So, when you're doing operations on the data structure, creating new entity instances or querying, you need to get the description of the entity you're working with so the system knows the rules to work with.

Single fetchRequest for multiple entities - use relationships and/or entity inheritance?

I have a simple app which uses Core Data. As the user progresses through each ViewController, the managedObject gets passed through each view then saved to the store on completion. At the minute my Model has a single Entity with over 100 properties, which I would now like to separate into multiple entities. I am fairly new to iOS programming and not overly confident with database work, so apologies if my terminology is not correct.
An example of how my app works: On my ClientViewController, I declare managedObjectNGLS which currently stores all attributes.
// Identify the app delegate
NGLSAppDelegate *appDelegate = (NGLSAppDelegate *)[[UIApplication sharedApplication]delegate];
// Use appDelegate object to identify managed object context
NSManagedObjectContext *context = [appDelegate managedObjectContext];
// Create new managed object using the NGLS entity description
NSManagedObject *ManagedObjectNGLS;
ManagedObjectNGLS = [NSEntityDescription insertNewObjectForEntityForName:#"NGLS"
inManagedObjectContext:context];
// Declare managed object
self.managedObjectNGLS = ManagedObjectNGLS;
This gets passed through to ServicesViewController.
// Allocate & initialise ServicesViewController
ServicesViewController *services = [[ServicesViewController alloc]initWithNibName:#"ServicesViewController"
bundle:nil];
// Pass managedObject to view
services.managedObjectNGLS = self.managedObjectNGLS;
// Push next view
[self.navigationController pushViewController:services animated:YES];
Once the user has selected the desired services, the managedObjectNGLS gets stored to my Model.
[[self.managedObjectNGLS managedObjectContext] save:&error];
In the AdminViewController, the user can export the entire contents of the NGLS entity using a fetchRequest. Please see this post on how I am achieving this (apologies if this seems like a duplicate post, but I am still trying to understand all of this).
My question: I would very much like someone to explain the best solution regarding my entity situation. I have tried to set a Parent Entity which seems to work fine when only two entities are involved, but when more are introduced I get lost. I am not sure about relationships or an abstract entity?
Ideally I would like to have separate entities for NGLS (holds various important attributes like date stamp), Admin (username, site location, etc), Client, Employer (possibly separated further into Employer1,...,Employer10 eventually), and Services. I am not sure whether to create an Abstract or Parent entity with no attributes, then just hold relationships to that with the separate entities?
I have laid out what I would expect the entities to look like in the editor, but I am unsure if this is correct. The idea of the app is to store details about one Client per time, so on that basis the Client can have many Employers and many Services. I have implemented an extremely simple "login" system where the lastObject in my Admin entity gets stored in the NGLS entity (see this post for a description), so the Admin entity doesn't necessarily have a relationship with anything (I may be wrong here?).
I have tried the following code but it just crashes the app:
NSMutableArray *entityArray = [NSMutableArray array];
for (NSString *entitySuffix in #[#"NGLS", #"Client", #"Services", #"Employer"]) {
NSString *entitySelect = [NSString stringWithFormat:#"%#", entitySuffix];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entitySelect];
[entityArray addObjectsFromArray:
[context executeFetchRequest:request error:nil]];
}
// CHCSVParser
NSOutputStream *stream = [[NSOutputStream alloc]initToMemory];
CHCSVWriter *writer = [[CHCSVWriter alloc]initWithOutputStream:stream
encoding:NSUTF8StringEncoding
delimiter:','];
// Fetch objects to write to .csv
for (Model *results in entityArray) {
[writer writeLineOfFields:#[results.site,
results.username,
results.dateStamp,
results.clientTitle,
results.clientForename,
// results from different entities
Again, I apologise for this post as it seems like I am asking for an outright answer, but I really just need someone to point me in the right direction on how to set up separate entities and use a single fetchRequest to fetch data from the entire Model with all of it's entities. Thanks in advance!

Core Data, iCloud and Pre-Built Data with iOS 7

I'm a in a really common situation mostly driven by inexperience...
My application inserts pre-built data in the DB at the first launch.
I just check for db existence in the device... if it doesn't exist this is the first launch so I add my pre-built data after DB creation.
At the end of the development I decide to activate iCloud, wow! just one row of code to make it works... I tried it on different devices and my pre-built data were obviously duplicated :(
I understood that the better way to manage pre-built data is to work with more than one persistent store, creating read-only store for pre-built data (my pre-built data don't need to be modified by the user, so the read-only solution would be ok).
The problem is that I have relationship between data inserted by user so I can't work on multiple persistent store (core-data doesn't support relation between different stores).
I took a look at Fetched Properties but it seems to require a super drastic edit on my code.
I thought to go with de-duplicating data... so just removing data that are already in the DB, my doubt is that this process is really dangerous for relationship, how can I delete the object that is just a duplicate of the "original" created at the first launch?=
Given that my pre-build data are can't be modified or deleted by the users, which is in your opinion the best way to correct this problem?
// EDIT: Some information about the model:
The entity that users are dealing with (add/edit/modify)represents a TASK.
this entities have a name, and a CATEGORY.
CATEGORY are the entities that I pre-build.
So user add a name for the task and select a CATEGORY from the pre-built data.
In your Category entity, in the PrebuiltData model, create an "id" property (and make sure that this is consistent from version to version) This model should not be managed by iCloud.
In your Task entity, in the UserData model, create a "categoryId" property. This model should be managed by iCloud (or this discussion is meaningless)
Now you can create a method on your Category entity to fetch all the Tasks in the category using:
-(NSArray*)tasks
{
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Task"];
request.predicate = [NSPredicate predicateWithFormat:#"id = %#", self.id];
return [gTaskManagedObjectContext executeFetchRequest:request error:NULL];
}
Likewise, you can create a method on your Task entity to fetch the category:
-(Category*)category
{
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Category"];
request.predicate = [NSPredicate predicateWithFormat:#"id = %#", self.categoryId];
NSArray* results = [gTaskManagedObjectContext executeFetchRequest:request error:NULL];
return results.count > 0 ? results[0] : nil;
}

Using Core Data in app without UITableView

I have a simple to do list application. It uses dynamically generated text fields spaced programmatically for the tasks (I didn't use UITableView because of some custom animations and whatnot that I want to use).
In the app the user can create multiple lists (home, work, school, etc.) each with their own tasks.
I want to use Core Data to store the information
Saving the information is straightforward enough but updating my Core Data objects (List and Task) are where I'm getting stuck. Also how, with Core Data, to associate in a specific tasks with a specific list.
let me know if I need to clarify anything.
Your best bet is NSFetchedResultsController. You can use it exactly like in the pattern suggested by the Xcode templates (you can look at it by creating a new project Master-Detail and checking "User Core Data").
You can device your object model (entity Task) with a string attribute for the name as well as a NSNumber boolean attribute for done etc. I also recommend a timestamp and maybe a serial number for ordering it (I find NSOrderedSet unreliable). Your entity List should have a to-many relationship to Task. Pretty straight forward.
List <---->> Task
The only difference is now to find the right object, because you cannot use objectAtIndexPath. You can use the index in fetchedResultsController.fetchedObjects for that. Just make sure your objects are ordered as expected.
I'm not totally clear on your question, however, the task of updating a managed object is straightforward. When you're doing an initial add (similar to an "insert" in SQL) you might use code like this:
NSManagedObject *obj;
obj = [NSEntityDescription insertNewObjectForEntityForName:#"UserData" inManagedObjectContext:context];
[obj setValue:user forKey:#"userName"];
[obj setValue:goalCategory forKey:#"goalCategory"];
[obj setValue:goalDetail forKey:#"goalDetail"];
NSError __autoreleasing error;
[context save:&error];
That's about it for inserting a new item. For updating after you're found the managed object you're working on, you just change the values in the managed object and use [context save:&error]. Here's an example:
UserData *uData = (UserData *) managedObj;
uData.itemName = nameText;
NSError __autoreleasing *error;
[context save:&error];
That's pretty much it.
As to the update, once you have selected the object(s) to be updated, they are contained in
fetchedResultsController.fetchedObjects
which is an NSArray. So, you might do something like this:
UserData *uData = (UserData *) [fetchedResultsController.fetchedObjects objectAtIndex:3];
uData.completed = YES;
NSError __autoreleasing *error;
[context save:&error];
So, this would update the field completed in the UserData entity to be == YES for the object at index 3 in the fetchedObjects array.
I know there are other methods of updating and lots of options but I haven't found any need for them. fetchedObjects is an array containing the items returned by your fetch; to update them, cast each object to the entity (which is defined as a NSManagedObject), make the change then context save..
HTH.
First of all, think is it good idea to use Core Data for your project. If your model is light and simple, maybe it will be better to use plists.
If you choose Core Data, just remember 2 rules:
Each thread owns separate NSManagedObjectContext;
Perform operations with context only in its thread.
And don't worry about optimizations now. Realize any scheme of updating your storage. Make sure it works. And then you should try some other update methods.

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