Core Data recursive search using NSPredicate - ios

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.)

Related

FetchRequest with NSPredicate not finding results?

I am storing data in a table that has the columns "name" and "series". I am using this NSPredicate to query:
var request : NSFetchRequest = NSFetchRequest(entityName: "Entry")
request.returnsObjectsAsFaults = false
request.predicate = NSPredicate(format: "name = %# AND series = %#", name, series)
return request
The 'name' and 'series' variables are passed in as String arguments, which are also the data types on the table.
For some reason, this query returns no data. If I do a fetch request on this table without specifying a predicate I can see that the data is indeed there. I am not sure what I'm doing wrong.
For what it's worth I have tried enclosing the conditionals in parens but that didn't seem to make a difference either. Thanks in advance!
UPDATE: I've tried many different things but so far nothing is working. I'm really stumped.
For what it's worth it seems like I am having the same issue as this person: NSPredicate Returns No Results with Fetch Request, Works with Array Filtering
But, there isn't anything on that entry stating specifically what the solution to the problem was.
You can check if the data is there by
printing the URL of the application documents directory to the console.
going to this directory in Terminal
running sqlite3 <databaseName>
trying select * from z<entityName> where name = '<nameData>'
You will be able to explore the data and check if it contains what you expect. Don't forget to also check the content of your name and series variables in the code.
Oh wow...thank you everyone so much for all your answers. In the end it turned out that when I was populating the id for inserting a new row into the table, I was looking at a different table to calculate the new primary key id.
So I was overwriting existing records, which is why my query kept failing.
Kids, if you copy/paste your code without rigorously checking it, you're gonna have a bad time.

Search in array of different objects

I'm struggling with my predicate search here, probably a lack of vocabulary.
I have a (kind of) weak design here that my deadline doesn't allow me to change too deeply, and the situation is the following.
I'm searching in a tableview of contacts AND users, my two objects of concern here. The search works fine when there are only contacts (and none of the objects listed are users).
Now if it so happens (which it will, very often) that some of these contacts are users, when I search the tableview, my predicate key doesn't match, and I obviously get an exception.
How could I proceed to go around this, knowing I would like the search to still include everyone. (I have a backup plan where I just remove the users from the search feature and it works like a charm).
I tried using the OR in my predicate, like so :
#"compositeName contains[c] %# OR name contains[c] %#" //(where %# is my search string)
but it's not enough to skip the fact that my contact has a "compositeName" and my user has a "name", which causes an exception.
I don't want to modify the key name of my users to "compositeName" because it would imply to reinstall the app for all my beta testers, which is (due to deadlines and app-generated content) also not possible. Unless there is a way for them to have the new data model without having to reinstall, then I would do that and simply call them all "compositeName". (or "name").
I'm using core data and the array is full of NSManagedObject (of user & contact types).
Any idea?
EDIT 1 : Is this a good idea?
I'm thinking of splitting the arrays if there are users, then using different predicates in both, then finally combine them again and show those results. But it seems like a lot of processing and maybe there is something more efficient?
EDIT 2: Crash log
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: the entity User is not key value coding-compliant for the key "compositeName".'
EDIT 3 : Class check
According to comments and other posts on stack, you can actually add a condition check, and also do class checks in predicate. Now I tried to combine both by writing this :
#"((className == %#) name contains[c] %#) OR ((className == %#) compositeName contains[c] %#)", #"User",searchText, #"Contact", searchText
which isn't a correct format. This is the first time i'm playing with predicates so if you have any clue how to write a predicate that says
(If class = User, then name contains [c], if class = Contact, then compositeName contains [c])
Your issue is that your combined predicate is going to send valueForKey for both of the properties to each object - and a given object only supports 1 of the two properties, so you get the exception.
If you combine an object class test using an AND with the name/compositeName test you can avoid this issue - by testing the class first, the predicate will skip the second test (for name/compositeName) because the first test has already returned false - making it pointless checking the second clause of the AND.
You can use a predicate something like
#"(className == %# AND name contains[c] %#) OR (className == %# AND compositeName contains[c] %#)",[User className],searchString,[Contact classname],searchString

NSPredicate not working with abstract entity attributes in core data

I have a core data class, SSSLicense that has attributes like name and type. It inherits from an abstract entity called SSSArchivableEntity which has a boolean attribute named isArchived (among others).
I've already fetched the full set of license entities from the DB and am now trying to filter based on type and the isArchived flag. However, despite many variations of my predicate, I cannot get a valid result.
Here is the relevant code:
NSSet *licenses = [person licenseList] ;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(licenseType like %#) AND (isArchived == NO)",
lType];
NSSet *filteredLicenses = [licenses filteredSetUsingPredicate:predicate];
If I change my predicate to use another attribute of SSSLicense, say name (instead of isArchived), the predicate works. I even added a simple boolean attribute to SSSLicense and filtered using it successfully.
It seems like it has something to do with the fact that isArchived is an attribute of the abstract entity. Is there something special I need to do to filter with inherited attributes?
Thanks!
Maybe hard-coding the value does not work. I recommend using the standard syntax:
[NSPredicate predicateWithFormat:
#"(licenseType like %#) && (isArchived = %#)", lType, #(NO)];
If this does not work I would manually check the value of your isArchived property via sqlite3 command line tool or SQLite Manager plugin in Firefox. Maybe you are expecting a value there that has not been persisted.
If all these are verified it could be that you are reading the wrong values or displaying the wrong values although the predicate actually works.
I'd rather prefer better the blocks instead of the format, hence I would try something like this:
[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return ([[evaluatedObject licenseType] rangeOfString:lType].location == NSNotFound && [[evaluatedObject isArchived] boolValue] == NO);
}];
The issue turns out to be one of how the predicate tests the conditional versus a direct test via an if statement. My seed data created the license via direct setter calls and I was not explicitly setting isArchived to NO. Within the app, when a license was created it was being set. So, seed data was failing to be returned via the predicate. However, if I manually looped through the set and did a test via "[license isArchived] == NO" both the seed data and the app data were returned as expected.
My guess is that a direct boolean test checks for a valid 'YES' and if not there assumes NO whereas the predicate is explicitly checking YES=1 NO=0 (or however it is represented).

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"];

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

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

Resources