Query table based on a value in FK - ios

I have a couple of tables:
User:
- userId (NString)
- name (NSString)
Report:
- reportName (NSString)
- user (foreign key to user table)
I have a userId and I want to get all reports for that user. Do I have to query for the user first from core data?
If so this is bad as I am trying to implement find and update logic as outline by apple. In my case I only have one report per user. I am getting back an array of reports from the server and each report contains a userId (assigned by server). If I don't have a report for that user yet I need to create one. If I have report for that user I need to update with the new report.
So basically I create an array of NSString (userId's) to use in a predicate:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Report" inManagedObjectContext:context]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:#"(userId IN %#)", userIds]];
However in this case userId does not really exist, user is a foreign relationship.
I could not make a foreign relationship and just store userId in report but then I need to manually join later. Is that a better option.

The notion of a foreign key does not exist in Core Data. Sure, they're used for one of the possible backing stores, but the foreign key is hidden. In its place, we have instead a relationship.
If you have a relationship established, you just walk the keypath. No further fetch is necessary.
You need inverses for your relationships. So if a Report has a User, the User has Reports.
For your purposes, I suggest implementing a reportWithReportName: (does a fetch), findOrCreateWithReportName: (tries a fetch, does an insert if necessary), userWithUserID: (does a fetch), and findOrCreateUserWithUserID:name: (fetch, insert if needed).
Report *aReport;
User *someUser;
NSLog(#"%#", aReport.user.name, aReport.user.userID);
NSLog (#"%#", someUser.userID, someUser.reports);
foreach (Report *report in someUser.reports) {
NSLog(#"%#", report.reportName);
}

Related

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;
}

iOS core data how to access single entity from different entities

I have a model like this:
How do I make it so that the "files" entity can be referenced by either Entity1 or Entity2 (or Items)
Basically I have a number of "files" with id's. If a change is made to a "file" (changed name for example), then when any Entity references the file, it should have the updated info...without having to update in each entity.
Is there a way to do this from the editor? Or do I have to write the code to first get the file id i'm looking for, and then once I have the file id, do a separate request for the "File" entity using the id I just got?
In sql I could just do a left join from any table. If it's possible to do in a predicate, that would work too, just not sure how.
EDIT:
So I updated my model, now it looks like this:
I added data for a"File" and then added data for Entity1 and Entity2
I have code that works to display Files, and then code that works that display the entities.
Here's a snippet of what I'm doing:
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Entity1" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error;
NSArray *entityArray = [context executeFetchRequest:request error:&error];
[entityArray enumerateObjectsUsingBlock:^(Entity1 *entity1, NSUInteger idx, BOOL *stop) {
NSLog(#"entity1: %#", entity1.name);
}];
How do I now get the 'name' from File for Entity1? Lets pretend the name field is a matching field. What do I need to add to the code to pull the "File"? This is assuming the "File" was there before the "Entity1" was there of course.
I'm currently running another method that looks something like: "getFileByName:(NSString *)name" and doing a separate request using a predicate. This works but I think there is a better way.
You could simply create relationships to the Files entity for any other entity that like to reference them (with the cardinality you desire).
Remember, although the Files.id is unique, the uniqueness offered by CoreData is the objectID ==> you could substitute the id referencing with the file objectID and get the same functionality.
Some suggestions:
don't use id as a property name, it is a reserved word in Objective-C
don't name your entities in a plural form (ex. Files --> File) they are an object definition or class (don't think in terms of tables, although this might be the underlying structure)

NSPredicate traversing relationship (StackMob)

I have been scouring Apple's Predicate Programming Guide and SO as well, trying to determine the correct way to write a predicate for a certain fetch request.
It seems like the way I am attempting to use dot notation to traverse a relationship is not being respected by the request.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Task" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"project.user == %#", self.user];
[fetchRequest setPredicate:predicate];
User, Project, and Task are all core data entities mapping to a BaaS provider (StackMob).
Relationships are as follows: User one-to-many Project one-to-many Task with inverse relationships as you would expect. What I am trying to do is get all tasks for the current logged in user. So, my thinking is: send a fetch request with the entity "Task". Where the project that owns the task is owned by the logged in user, get that task and display it. Simple enough.
And I think I should be able to use the dot notation in the predicate that I have shown above, but the fetch request is returning nil.
Everything else about the fetch request is fine. If I change the format string to #"project != NULL", I get all of the tasks back.
Am I just writing my format string incorrectly? Is dot notation somehow not valid when sending a fetch request to StackMob?
As always, any help is greatly appreciated.
Please let me know if more information is needed. I'm a little too close to this right now to see it clearly.
Fetching on an attribute of a relationship (task where project.user == X) is not supported by StackMob. However, it looks like you are trying to get all the tasks for a given project for the logged in user. If I am correct in assuming that the logged in user was the one to create the projects and tasks, then set the permissions of the project schema to "Allow to sm_owner". You can then fetch tasks based on the project ID (task where project IN 1234), or project based on ID and grab all tasks (project where projectId == 1234 and use value of tasks relationship). Either way, with that permission fetches will only return results where sm_owner == logged in user, so you don't need to set up the fetch such that it uses the user ID as a filter. Hope that helps!

Core Data: With a NSFetchRequest Is there a way to get fetched properties when the result type is set to NSDictionaryResultType

I have an application that uses two separate core data stores. One for user data, and one for read-only content data. In order to create relationships between them, it is of course forbidden to use relationships, and so references to the content store are managed by ids.
This works fine in most cases, but for some particular fetches it is proving difficult.
Take the following example: Say I have an application that has a few hundred movies (with a record for each stored in the read-only store) and whenever a user watches one a record is created in the writable user store. I might set up my model to have one Entity called Movie, and another called MovieHistory.
Movie has:
an attribute called identifier (NSNumber).
MovieHistory has:
an attribute called movieIdentifier (a cross-store reference to Movie),
a viewDate attribute (NSDate),
and a fetched property 'movie' with the destination set to Movie and a predicate of (SELF.identifier == $FETCH_SOURCE.movieIdentifier).
Say I now want to get the last 10 movies watched, without duplicates (if I watch a movie that I previously watched it should jump to the top of the list). I'd like to be able to use the following code:
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: #"MovieHistory"];
NSSortDescriptor *dateDescriptor = [[NSSortDescriptor alloc] initWithKey:#"viewDate" ascending:NO];
NSEntityDescription* entity = [NSEntityDescription entityForName:#"MovieHistory" inManagedObjectContext: moc];
NSAttributeDescription* movieIdentifierDesc = [entity.attributesByName objectForKey:#"movieIdentifier"];
NSFetchedPropertyDescription *movieDesc = [entity.propertiesByName objectForKey:#"movie"];
[request setFetchLimit: limit];
[request setSortDescriptors: [NSArray arrayWithObject: dateDescriptor]];
[request setPropertiesToFetch:[NSArray arrayWithObjects: movieIdentifierDesc, movieDesc, nil]];
[request setPropertiesToGroupBy:[NSArray arrayWithObject: movieIdentifierDesc]];
[request setResultType:NSDictionaryResultType];
NSArray *results = [moc executeFetchRequest: request error: &error];
if(error != nil)
NSLog(#"Error fetching last viewed movies: %#", error);
return results;
This code throws an exception stating that the fetched property ('movie' in 'MovieHistory') is an invalid property to use in this case. You can't use fetched properties when using NSDictionaryResultType. However, you also can't use setPropertiesToGroupBy: without the return type being NSDictionaryResultType. I need grouping in order to sort by date and remove duplicate Movies.
It works fine when I remove the fetched property from the array sent to setPropertiesToFetch:. But then of course I would have to pull out each MovieRecord one-by-one in a loop.
What is the best way to accomplish what I'm trying to do? Is there a way to do this without having to resort to fetching the IDs and then looping through the results one-by-one?
Thanks,
I didn't exactly follow your whole post, however...
You can issue two separate fetch operations. The first gets the MovieHistory objects from that database.
Put all the movie identifiers into a collection, and then fetch on the movie database, using the IN keyword.
NSPredicate *moviePredicate = [NSPredicate predicateWithFormat: #"identifier IN %#", allMovieIdentifiers];
It is not quite as elegant as some other solutions, but it is easy to write, and, more importantly, easy to read, understand, and change. Remember optimize first for programmers.

iOS - Core Data - Delete records using relationships and fetch request

Overview:
I have an iOS project in which I am using core data
I have an Employees entity and a Department entity.
1 department can contain many employees
So the entity Department has a "to many" relationship with the entity Employees, the relationship is called employees and the reverse relationship is called whichDepartment
Aim-1:
I want to delete all the employees in a specific department
Questions:
a) is the following correct, or would it cause mutation or some problems ?
b) is this is the correct way to do it ?
Pls Note - removeEmployees is a method that was auto generated while creating the subclasses of the entities
- (void) deleteAllEmployeesForDepartment: (Department*) requestedDepartment
{
[requestedDepartment removeEmployees:requestedDepartment.employees];
}
Aim-2:
I want to delete the employees based on some condition
I am deleting objects inside a fast enumeration loop for the fetched records
Questions:
c) Is the following correct, or would it cause some mutation ?
d) Is it like modifying the object in fast enumeration ?
e) Is there a better way to do it ?
Pls Note - removeEmployees is a method that was auto generated while creating the subclasses of the entities
- (void) deleteAllType1EmployeesWithDepartment: (Department*) requestedDepartment
{
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"type == %i AND whichDepartment ==%i", 1, requestedDepartment.departmentID];
NSError *error;
NSArray *listOfEmployeesToBeDeleted = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for(Employees *currentEmployee in listOfEmployeesToBeDeleted)
{
[self.managedObjectContext deleteObject:currentEmployee];
}
}
Firstly, in your deleteAllEmployeesForDepartment: this is fine to remove objects that way.
If in addition you want to delete the Employees objects from Core Data then you should add another rule, a delete rule set to cascade, meaning that when an Employee "losses" a Department (the relationship is broken, either by the Department being deleted, or the Department removing the Employee), it (the Employee) also is deleted.
Your second question is a little more interesting.
What I recommend is adding another method directly to the Department subclass of NSManagedObeject, you could call it - clearEmployeesOfType: passing a type number.
Since your Department has a reference to an NSSet of Employees via the to-many relationship you could use that NSSet and it's filteredSetUsingPredicate: method to filter out the ones you want.
The returning set can be used to message the removeEmployees: method on your Department, a little like the following (warning, code not tested).
- (void) clearEmployeesOfType:(NSUInteger)type
{
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"SELF.type == %d", type];
NSSet * firedEmployees = [self.employees filteredSetUsingPredicate:predicate];
[self removeEmployees:firedEmployees];
}
I would recommend this solution rather then loading objects and removing them one by one, whenever you can, rely on relationships and delete rules in Core Data.

Resources