Search code examples
swiftrealm

Realm filter results based on values in child object list


This is how my Realm objects look:

class Restaurant: Object {
    @objc dynamic var name: String? = nil
    let meals = List<Meal>()
}

class Meal: Object {
    @objc dynamic var mealName: String? = nil
    let tag = RealmOptional<Int>()
}

I'm trying to fetch all meals that have some tags (I know I can filter all Realm objects of type Meal for specific tags), but the goal is to fetch all Restaurant objects and filter it's Meal child objects based on tag values.

I tried filtering like this:

restaurants = realm.objects(Restaurant.self).filter("meals.@tags IN %@", selectedTags)

but this won't work. Is there a way to filter results based on values in child object list?

To clarify the question, this is an example how filtering should work for selectedTags = [1, 2, 3] This is the whole Restaurant model that is saved in Realm.

[Restaurant {
    name = "Foo"
    meals = [
        Meal {
            mealName = "Meal 1"
            tag = 1
        },
        Meal {
            mealName = "Meal 2"
            tag = 2
        },
        Meal {
            mealName = "Meal 7"
            tag = 7
        }
    ]
}]

Filtering should return this:

[Restaurant {
    name = "Foo"
    meals = [
        Meal {
            mealName = "Meal 1"
            tag = 1
        },
        Meal {
            mealName = "Meal 2"
            tag = 2
        }
    ]
}]

Solution

  • Here's one possible solution - add a reverse refererence to the restaurant for each meal object

    class Restaurant: Object {
        @objc dynamic var name: String? = nil
        let meals = List<Meal>()
    }
    
    class Meal: Object {
        @objc dynamic var mealName: String? = nil
        let tag = RealmOptional<Int>()
        @objc dynamic var restaurant: Restaurant?  //Add this
    }
    

    then query the meals for that restaurant with the tags you want.

    let results = realm.objects(Meal.self).filter("restaurant.name == %@ AND tag IN %@", "Foo", [1,2])
    

    LinkingObjects could also be leveraged but it depends on what kind of queries will be needed and what the relationships are between Restaurants and Meals - I am assuming 1-Many in this case.

    if you want ALL restaurants, then LinkingObjects is the way to go.

    Edit:

    Thought of another solution. This will work without adding a reference or an inverse relationship and will return an array of restaurants that have meals with the selected tags.

    let selectedTags = [1,2]
    let results = realm.objects(Restaurant.self).filter( {
        for meal in $0.meals {
            if let thisTag = meal.tag.value { //optional so safely unwrap it
                if selectedTags.contains(thisTag) {
                    return true //the tag for this meal was in the list, return true
                }
            } else {
                return false //tag was nil so return false
            }
        }
        return false
    })