NSPredicate - How to check if every parent has visible == true [duplicate] - ios

I am using CoreData for an iPhone project and I am stuck trying to build a predicate.
My core data entity is
Folder
parent - Point to the folder class parent, can be null and is one to one.
secure - An enum that holds the security type.
The problem I have is that I am trying to make it so I don't show any folder that are in a secure folder.
Right now my predicate looks something like this.
NSPredicate *pred = [NSPredicate predicateWithFormat:#"secure = $# AND (parent = %# OR parent.secure = %#)",[NSNumber numberWithInteger:kNoSecurity], [NSNull null], [NSNumber numberWithInteger:kNoSecurity]];
This works find when I only have a chain like folder1 -> folder2 and folder1 is secure. But if I have folder1 -> folder2 -> folder3 (folder2 and folder3 are not secure). Folder3 gets returned because I only check one level up. Is there a way to get the predicate to do the check for a entire chain?
Thanks.

The problem is that this only goes up
one level. So if I have folder1 ->
folder2 -> folder3 -> folder4, and
folder1 is secure. Then folder2 is not
show but folder3 and folder4 are.
You can't recursively walk relationships in predicates because keypaths only describe the relationship between the abstract entities and not the concrete, living managed objects that actually contain the data. An entity graph can be very simple yet generate a vastly complex graph of live objects when populated at runtime. You can't logically capture the complexity of that live graph with a simple keypath.
In this case, you have a Folder entity which has a relationship to itself called parent and an attribute of secure. Therefore, a keypath can only describe at most those two properties with path parent.secure. You can't create a keypath of parent.parent.secure because no such relationship actually exists in the entity graph. Such a path only exist sometimes in the live object graph. It would be logically impossible to hard code a path that might or might not exist depending on the particulars of the data at any given time.
This type of situation is where the ability to create customized NSManagedObject subclasses really comes in handy. Your Folder entites don't have to be just dumb data, you can add behaviors to them so that each object can access its own state and return different data as needed.
In this case, I would recommend adding a transient boolean property named something like hasSecureAncestor. Then create a custom getter method like:
- (BOOL) hasSecureAncestor{
BOOL hasSecureAncestor=NO;
if (self.parent.secure==kNoSecurity) {
hasSecureAncestor=YES;
}else {
if (self.parent.parent!=nil) {
hasSecureAncestor=self.parent.hasSecureAncestor;
}else {
hasSecureAncestor=NO;
}
}
return hasSecureAncestor;
}
Then just create a predicate to test for "hasSecureAncestor==YES". The custom accessor will walk an arbitrarily deep recursive relationship looking for a secure ancestor.

Why not just grab all Folder entities with kNoSecurity?
NSPredicate *pred = [NSPredicate predicateWithFormat:#"secure = %# ", [NSNumber numberWithInteger:kNoSecurity]];

How about going back up through the relationship:
NSPredicate *pred = [NSPredicate predicateWithFormat:"parent.secure = %#", [NSNumber numberWithInteger:kNoSecurity]];

Related

Core Data recursive search using NSPredicate

I have a project using Core Data with "folder" and "document" entities. A folder can contain many documents, and also other folders.
I'm working on a search feature, and in the current version I have something like this which finds documents/folders within a particular folder that have a name that partially matches the search text:
let predicate = NSPredicate(format: "parentFolder == %# AND name CONTAINS[cd] %#", folderToSearch, searchText)
fetchedResultsController.fetchRequest.predicate = predicate
However, I'd like it to also search within folders that are children of the current one (and if it finds a match, include the containing folder in the results).
For example, if my folder/document structure was like this (assume the Shopping folder and Travel folder are contained within the same parent folder):
Shopping
Groceries
- toothpaste
- cheese
Travel
Packing
- toothpaste
- toothbrush
... and I searched for "toothpaste", I'd want the search to return the "Shopping" folder object and the "Travel" folder object.
What I need is something like this:
let predicate = NSPredicate(format: "parentFolder == %# AND name (OR ANY OF IT'S CHILDREN'S NAMES) CONTAINS[cd] %#", folderToSearch, searchText)
fetchedResultsController.fetchRequest.predicate = predicate
Is there any way to form an NSPredicate to do what I want?
Or something else I should consider doing?
UPDATE: Yes, there is a similar question to this already. However, the accepted answer there suggests using a "transient boolean property" and using that in the NSPredicate... but everything else I've read says that won't work when using an SQLite store. (See this and this.) So I think my question is still valid. I've also updated my question to clarify exactly what I need.
Note that when I first posted this question, I incorrectly thought about adding a transient property to determine if a document/folder is a child/grandchild/etc. of the folder that the search started from, and changed the predicate to this:
let predicate = NSPredicate(format: "isDescendentOfSearchParent == YES AND name CONTAINS[cd] %#", searchText)
fetchedResultsController.fetchRequest.predicate = predicate
... but quickly learned that transient properties don't work in NSPredicates when used with an SQLite store. (Plus, this would return the document/folder that matched the search text, not its containing folder... which isn't what I want.)

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

Modelling short-term objects in Core Data app

I have an app that talks to the server to get some items (Item class) for current user and store it. So far so good.
I want to implement search, that essentially returns me a set of Item objects, but obviously I do not want to persist every search result there ever be. Another use case is that server API has different endpoints like recommendations/ new/ upcoming/ that return the same Item object, but in different context, so I would like to differentiate between them somehow.
My first thought was to use a throw-away managed context, load objects from API in there, do fetch and when user is done just destroy the context. Is it a good idea in general? It saves code, because most of my VCs already talk to core data.
Rather than throwing the whole wonderful infrastructure of Core Data away, you should leverage it to achieve your purpose.
Add a timestamp attribute to your entity and use it to selectively display search results or even purge your store from old items.
Add a category attribute to your entity and filter by category when searching.
Both can be achieved with an NSPredicate that you add to your NSFetchRequest. For example:
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"timestamp > %#", [[NSDate date] dateByAddingTimeInterval:numberOfSeconds]];
or
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"category = %#", #"new"];

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.

Core Data: object in a predicate

In my core data object model I have 3 entities with appropriate relationships so that MyObject can have many MyObjectProperties, and each property can have one MyObjectPropertyImage.
Given a myObject I want to fetch all the images.
I try to do it using the following predicate, however I get an empty array:
[NSEntityDescription entityForName:#"MyObjectPropertyImage" inManagedObjectContext:managedObjectContext];
[NSPredicate predicateWithFormat:#"ANY myObjectProperty.myObject == %#", myObject];
Any ideas?
When working with Core Data it's best to think of your entities as just that: entities in an object graph, instead of tables in a database. Therefore, you don't need to fetch entities related to others using a predicate. Instead, navigate the object graph using the relationships defined in the model. To get all the images related to myObject:
// assuming the relationships are called 'myObjectProperties' and 'myObjectPropertyImage', respectively
NSSet *allImages = [myObject.myObjectProperties valueForKey:#"myObjectPropertyImage"];
Note that this may trigger additional trips to the database if your object graph is not loaded in memory for your myObject entity. To avoid that, make sure you set the pre-fetching relationship keypaths in your fetch request for myObject.
I hope this helps...
Since you have a MyObject instance in hand and it has the relationship path of myObjectProperties-->ObjectProperty-->>PropertyImages you just need to traverse the relationships. It's easy to do this with valueForKeyPath:
Thusly:
NSArray *images=[myObjectInstances valueForKeyPath:#"myObjectProperties.propertyImage"];
(Note: I might have your attribute names wrong but you can get the idea.)
As general rule, you never fetch when have an object from the graph available. You fetch to "pick out thread" of objects matching the predicate and then to find all related objects you follow the thread/relationships to the related objects.

Resources