Search code examples
realmrealm-list

How to Find Orphaned List Items in Realm


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 kids?


Solution

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