Filtering Realm with nested subqueries - ios

My app has data that looks like this.
class ShelfCollection: Object {
let shelves: List<Shelf>
}
class Shelf: Object {
let items: List<Item>
}
class Item: Object {
var name: String
let infos: List<String>
}
I'm trying to get all shelves in a shelf collection where any items match the query either by name or by an element in their infos list. From my understanding this predicate should be correct, but it crashes.
let wildQuery = "*" + query + "*"
shelfResults = shelfCollection.shelves.filter(
"SUBQUERY(items, $item, $item.name LIKE[c] %# OR SUBQUERY($item.infos, $info, info LIKE[c] %#).#count > 0).#count > 0",
wildQuery, wildQuery
)
It complies as a NSPredicate, but crashes when Realm is attempting to parse it, throwing me
'RLMException', reason: 'Object type '(null)' not managed by the Realm'
I suspect the nested subquery might be what fails, but I don't know enough about NSPredicate to be sure. Is this an acceptable query, and how can I make it.. work?

This is an answer and a solution but there's going to be a number of issues with the way the objects are structured which may cause other problems. It was difficult to create a matching dataset since many objects appear within other objects.
The issue:
Realm cannot currently filter on a List of primitives
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
so this Item property will not work for filtering:
let infos: List<String>
However, you can create another object that has a String property and filter on that object
class InfoClass: Object {
#objc dynamic var info_name = ""
}
and then the Item class looks like this
class Item: Object {
var name: String
let infos = List<InfoClass>()
}
and then you filter based on the InfoClass object, not it's string property. So you would have some objects
let info0 = InfoClass()
info0.info_name = "Info 0 name"
let info1 = InfoClass()
info1.info_name = "Info 1 name"
let info2 = InfoClass()
info2.info_name = "Info 2 name"
which are stored in the Item->infos list. Then the question
I'm trying to get all shelves in a shelf collection...
states you want to filter for a collection, c0 in this case, shelves whose items contain a particular info in their list. Lets say we want to get those shelves whose items have info2 in their list
//first get the info2 object that we want to filter for
guard let info2 = realm.objects(InfoClass.self).filter("info_name == 'Info 2 name'").first else {
print("info2 not found")
return
}
print("info2 found, continuing")
//get the c0 collection that we want to get the shelves for
if let c0 = realm.objects(ShelfCollection.self).filter("collection_name == 'c0'").first {
let shelfResults = c0.shelves.filter("ANY items.infoList == %#", info2)
for shelf in shelfResults {
print(shelf.shelf_name)
}
} else {
print("c0 not found")
}
I omitted filtering for the name property since you know how to do that already.
The issue here is the infos could appear in many items, and those items could appear in many shelf lists. So because of the depth of data, with my test data, it was hard to have the filter return discreet data - it would probably make more sense (to me) if I had example data to work with.
Either way, the answer works for this use case, but I am thinking another structure may be better but I don't know the full use case so hard to suggest that.

Related

Return object realm not accessible

I'm having problems with an object that returns me in Realm, the strange thing is that if I printo console the object if I start it well but however if I try to access its value it tells me that it is empty.
The structure of the object is as follows:
class Favourite : Object {
var character : Character!
}
I create an object and add it to the DB
let fav = Favourite()
fav.character = character
FavouriteDao.sharedInstance.addFavourite(characterFavourite: fav)
Get all objects of favorite type
func getAllFavourites() -> Results {
return realm.objects(Favourite.self)
}
When I get the item and do a print
Favourite {
character = Character {
name = Spider-Man;
descriptionC = Bitten by a radioactive spider, high school student Peter Parker gained the speed, strength and powers of a spider. Adopting the name Spider-Man, Peter hoped to start a career using his new abilities. Taught that with great power comes great responsibility, Spidey has vowed to use his powers to help people.;
thumbnail = Thumbnail {
id = 815D93D0-C116-4267-978C-9E47C0074D0D;
path = http://i.annihil.us/u/prod/marvel/i/mg/3/50/526548a343e4b;
extensionImage = jpg;
};
};
If I try to access the character element it tells me that it is nil
Somebody manages to understand because if I make a print of the favorite object it shows me that there is inside a character object but nevertheless if I try to accede to it it says that it does not exist?
What you do is totally wrong from the very beginning. You should read the realm docs first. https://realm.io/docs/swift/latest/#getting-started
For example.
class Favourite : Object {
var character : Character!
}
is not something you should do in Realm.
Assuming your Character is well-defined, the code should be dynamic var character : Character? = nil at least.

Swift: Backendless sorting relationship objects

I have this Backendless tables: Posts and Comments
Posts has a column comments which is has a one-to-many relationship to Comments table.
I'm try to get the comments in a latestOrder first behavior.
Currently, I have this query:
let query = BackendlessDataQuery()
let queryOptions = QueryOptions()
queryOptions.pageSize = size
queryOptions.related = ["comments", "comments.user", "user", "media"]
query.queryOptions = queryOptions
// Used PostObject since it is already mapped using `mapTableToClass`
backendlessInstance.persistenceService.of(PostObject.ofClass()).find(
query,
response: { backendlessPostsList in
let backendlessPostsListOfOffset = backendlessPostsList.getPage(offset)
guard let postObjects = backendlessPostsListOfOffset.getCurrentPage() as? [PostObject] else {
reject(BackendlessError.InvalidTypeForObject(name: "Post"))
return
}
return postObjects
},
error: { fault in
// TODO Find a way to convert a Fault to ErrorType
print("Server reported an error (1): \(fault)")
}
)
What I'm doing currently to sort Post.comments in the view model is reversing it. Post.comments.reverse().
Is there a way to explicitly sort the comments in Backendless level?
You don't have options when using related, and you should really just use it for to-one relationships, not to-many because of this. It would work ok if you have a one-to-few relationship.
So, you should make a separate request for the comments so you can specify a specific page size and sort order. Note that when you specify the variable to sort by (created) you can also add asc or desc to specify the direction of the sort (so created desc).
You could use the sort function.
let sortedComments = Post.comments.sort { $0.date > $1.date }
Of corse your comments will need a published date field for this to work.

Sort fetch request based on relationship attribute

Let's say I have to entities; Item and LocalizableString. The LocalizableString entity have two attributes; string and locale. The Item entity have a to-many relationship to LocalizableString called localizedTitles. Where there's one string entity per "locale".
Now, in my app I have some context/setting that determines what locale I want to use when showing the UI. So let's say I have a UITableViewController with an NSFetchedResultsController handling the data flow.
My question is: can I sort the Item entities based on the LocalizableString's string property where locale is for example en? And can I do this in the fetch request, not after having fetched all items?
Sure you can do that, but you do not have to fetch any particular locale upfront. Your fetched results controller would just fetch the Item objects, and you can then refer to the correct string as you need it.
One way to implement this is to add a convenience function to the Item class, something like the following
func titleWithLocale(identifier: String) -> String? {
if let title = self.localizedTitles.filter
{ $0.locale == identifier }
.first as? LocalizableString {
return title.string
}
return nil
}
Most likely you will have to tweak this code to conform to the strict typing of Swift, but you get the idea.
If you need to sort by the title string, perhaps you need a different approach. If you know that each Item has a localized title in the respective locale you can fetch the LocalizedTitle entity with a predicate the filters for the locale:
request.predicate = NSPredicate(format: "locale = %#", "en")
Obviously, you can sort by string with the usual sort descriptors. Now, to populate your table, you get to the item by following the relationshp in the other direction.
let title = fetchedResultsController.objectAtIndexPath(indexPath)!
let item = title.item
I ended up doing a fetch request for my LocalizableString entity instead. With a predicate that checks the locale and the inverse relationship:
NSPredicate(format: "locale == %# AND itemName != nil", locale)

I'm having trouble fetching entities from core data based on relationships with predicates

Quick background: I have en ExamEntity, which is linked via a relationship to sections covered in the exam (SectionEntity), which is linked via a relationship to the subsections under each section (SubsectionEntity).
What I'm try to do is fetch the sections and related subsections which will be covered be a selected Exam, and store them in a dictionary which can hold multiple string values per key.
My code starts like this:
var sectionsDictionary = [String:[String]]()
//---Fetch Section Entity
let sectionPredicate = NSPredicate(format: "(exam = %#)", selectedExam)
let sectionsFetchRequest:NSFetchRequest = SectionEntity.MR_requestAllWithPredicate(sectionPredicate)
let fetchedSections:NSArray = SectionEntity.MR_executeFetchRequest(sectionsFetchRequest)
All good here. But then I try and fetch the subsections related to the selected sections and things go wrong.
following Code:
for section in fetchedSections {
let sectionName = section.valueForKey("name") as! String
// //error occurs on the line below
let subsectionPredicate = NSPredicate(format: "(section = %#)", section as! SectionEntity)
let subsectionsFetchRequest:NSFetchRequest = SubsectionEntity.MR_requestAllWithPredicate(subsectionPredicate)
let fetchedSubsections:NSArray = SubsectionEntity.MR_executeFetchRequest(subsectionsFetchRequest)
The error I get is:
Could not cast value of type 'NSManagedObject_SectionEntity_' (0x7fb363e18580) to 'MyApp.SectionEntity' (0x10c9213e0).
The next few lines are supposed to iterate through the fetched results, adding their names as values to the dictionary with section names as keys.
for subsection in fetchedSubsections{
let subsectionName = subsection.valueForKey("name") as! String
sectionsDictionary[sectionName] = [subsectionName]
}
}
thanks in advance.
You're not defining the type of the class for your record
Go to the Project.xcdatamodeld file, and select your SectionEntity
Check if the field Class is like this
If it is then write in the Class field "SectionEntity" and press Enter, it should be like this
Then try again
You need to make sure that the elements of the collection you enumerate are of the correct type.
Presumably, Magical Record will return an NSArray of NSManagedObject items. You need to make sure Swift knows what type section is.
for object in fetchedSections {
let section = object as! SectionEntity
// ...
}
Also, see my answer about the unsuccessful casts to NSManagedObject subclasses.

Strange behavior from Core Data in iOS swift app

I am assigning non-nil values to entities, saving them, and then immediately retrieving them. But the values come back as nil. This is iOS 8.1 and Swift on Xcode (latest GA release) with identical behavior on both simulator and a device.
I have three entities: Match, Score and Team. Each Match has two teams, team1 and team2. Score also has team1 and team2. Match has a 1x1 relationship with Score, the fields being bet and betFor respectively. Match is identified by its field matchNumber.
So I have the following code:
var match:CBMatch?
var betCard:CBScore?
for matchNum in 1...42 {
match = CBMatch.lookup(self.managedObjectContext!, matchNumber: matchNum)
betCard = CBScore.addForBet(self.managedObjectContext!, match: match!)
betCard!.team1 = match!.team1
betCard!.team2 = match!.team2
match!.bet = betCard!
println("For match \(matchNum), \(betCard!.team1.shortname) vs \(betCard!.team2.shortname)")
// This is fine for all matchNum values
}
// Verify
if true {
var err:NSError? = nil
self.managedObjectContext!.save(&err)
if err != nil {
abort() // Hasn't happened yet
}
match = CBMatch.lookup(self.managedObjectContext!, matchNumber: 1)
betCard = match!.bet
println("For match 1, \(betCard!.team1.shortname) vs \(betCard!.team2.shortname)")
// Crashes with NPE
// It complains that betCard!.team2 is nil but betCard!.team1 is fine
}
Basically an attempt to retrieve the information I just set comes back as nil.
CBMatch:lookup and CBScore:addForBet are trivial:
class func lookup(moc: NSManagedObjectContext, matchNumber: Int) -> CBMatch? {
var fetchRequest = NSFetchRequest(entityName: "CBMatch")
var predicate = NSPredicate(format: "matchNumber == %d", matchNumber)
fetchRequest.fetchLimit = 1
fetchRequest.predicate = predicate
// fetchRequest.returnsObjectsAsFaults = false
// With or without didn't make a difference
var err:NSError? = nil
let matches = moc.executeFetchRequest(fetchRequest, error: &err)!
if matches.count == 1 && err == nil {
let fetchedMatch = matches[0] as CBMatch
return fetchedMatch
}
println("Could not find match \(matchNumber)")
return nil
}
class func addForBet(moc: NSManagedObjectContext, match:CBMatch) -> CBScore {
var entity = NSEntityDescription.insertNewObjectForEntityForName("CBScore", inManagedObjectContext: moc) as CBScore
entity.betFor = match
return entity
}
I cannot simplify the data model by simply discarding bet.team1 etc, since there will be situations where bet.team1 and match.team1 will be different and I need to be able to compare them. For this (simplest) case they are identical, as this is the first time Score objects are being initialized.
I have tried several things, including returnsObjectsAsFault = false during the fetch etc. When I open the .sqlite file using sqlite3, I can see the entries being made. I tried using sql debug flag set to 3, but nothing jumped out. I am not saying that avenue is exhausted, but I definitely am.
Any thoughts on what could be going on here? I am stuck on this for quite some time and any thoughts would be appreciated.
The error is EXC_BAD_ACCESS, with the problem that match.bet.team1 is nil . This is the same assignment I did barely a few lines above.
It seems to me that there is a flaw in your data model. You are referencing each team twice, which is really not necessary. The Bet entity does not need to know about the teams, as it has a to-one relationship with the Match entity. Bet could contain something along the lines of firstValue and secondValue.
While theoretically your setup should also work, you are introducing unnecessary complexity. You are not giving enough information to troubleshoot your error, but I think it is not necessary to even bother hunting down that bug.
Simply use match.team1 instead of match.bet.team1.
BTW, the obvious thing to look out for is that either match is nil, score is nil, or match.team1 is nil - in all cases score.team1 is also nil.
Found the bug in my code - the team1 and team2 relationships were 1:1, but in order for allowing multiple allocations they needed to be 1:N.

Resources