Reputation: 14070
Let's say I have a Realm schema where I have a parent class and kids underneath it in a List
. Like this:
class Parent: Object{
@objc dynamic var name = ""
let kids = List<Kid>()
}
class Kid: Object{
@objc dynamic var name = ""
}
Let's say that, over time, whenever a Kid
was deleted, it was only removed from the Parent
object and wasn't deleted from the Realm:
let realm = try! Realm()
let parent = realm.objects(Parent.self)
realm.beginWrite()
for kid in parent.kids{
if let index = parent.kids.index(of: kid){
parent.kids.remove(at: index)
}
}
try! realm.commitWrite()
I know I can delete the kids
from the Realm in the same write transaction as the removal from the parent
:
let kids = parent.kids
realm.delete(kids)
...but I have reasons not to.
Is there a way to query the Realm database for all kids
that don't belong to a parent
? For example, if you were to open my Realm, you could see 100
kid
objects, but if you look at the parents, only 5
of the kid
objects are actually attached to a parent
object.
I have a special use case of Realm where I don't actually want to delete the child List
items unless I know they don't have a parent. Is it possible to query a Realm for all parentless kid
s?
Upvotes: 0
Views: 441
Reputation: 35658
This question is
Is there a way to query the Realm database for all kids that don't belong to a parent?
Sure! Super easy with one line of code.
let results = realm.objects(Parent.self).filter("ANY kids == %@", thisKid)
of course, evaluate to see if there are any results
if results.count == 0 {
print("kid: \(thisKid?.name) has no parents.")
} else {
print("found parents for kid: \(thisKid?.name)")
for kid in results {
print(kid)
}
}
EDIT:
In the above code, you can iterate over each kid and see if it has a parent and if not remove it. As a challenge, how about some code that will remove all kids that don't have parents instead of iterating. allKids is a realm List.
let foundParents = realm.objects(Parent.self).filter("ANY kids IN %@", allKids)
let kidsThatHaveParents: [Kid] = allKids.compactMap { kid in
let x = foundParents.first { $0.kids.contains( kid ) }
if x != nil { //this is just for clarity, could be shortened
return kid
}
return nil
}
let haveParentSet = Set(kidsThatHaveParents)
let kidsToCheckSet = Set(allKids)
let kidsToRemove = kidsToCheckSet.subtracting(haveParentSet)
the one downside of this is that I utilized a Set to subtract out the kids that have parents. That would load all of the kids into memory, bypassing the lazy loading aspect of realm. Another option would be just just remove each kid that has a parent from the kidsToLook for realm list.
Upvotes: 0
Reputation: 5073
Unless you use LinkingObjects there's no way to query realm for kids without parents directly. If it's not too late to change your data model then I would suggest using them.
class Parent: Object{
@objc dynamic var name = ""
let kids = List<Kid>()
}
class Kid: Object{
@objc dynamic var name = ""
let parents = LinkingObjects(fromType: Parent.self, property: "kids")
}
When you add a Kid
to Parent.kids
realm automatically handles the Kid.parents
relationship for you. When you delete a Parent
, Kid.parents
will be empty (assuming the Kid
only had one Parent
). The great thing about LinkingObjects
is that you can incorporate them into your queries. So to find all Kid
objects without parents the query would be:
func fetchKidsWithoutParents1() -> Results<Kid> {
let realm = try! Realm()
return realm.objects(Kid.self).filter("parents.@count == 0")
}
If you don't use LinkingObjects
, you have to query for all Kid
objects and all Parent
objects and see if the Kid
exists in any Parent.kids
List
. There are two downsides to this approach. The first is that that all Kid
objects will be loaded into memory as you filter them manually. The other is that you can't take advantage of realm notifications because the resulting kids would not be stored in a realm Result
object. Note that the following example assumes that there are no two Kid
objects with the same name:
func fetchKidsWithoutParents2() -> [Kid] {
let realm = try! Realm()
let kids = realm.objects(Kid.self)
let parents = realm.objects(Parent.self)
return kids.filter { kid in
parents.filter("SUBQUERY(kids, $kid, $kid.name == %@).@count > 0", kid.name).count == 0
}
}
Upvotes: 3