How to query an attribute on an Object contained in a List

I’m using Realm in a Swift project to store my data. I want to use the Realm Swift Query API to query object attributes contained in a List.

My (simplified) objects look like:

open class QuestionModel: Object {
    
    @objc dynamic var uuid: String!
    override public static func primaryKey() -> String? {
        return "uuid"
    }
    // attributes about the media for this question are wrapped up in a MediaModel object
    @objc dynamic var media: MediaModel?
    
}

open class MediaModel: Object {
    // A question can have up to two ImageModels    
    open var images = List<ImageModel>()
   // there are other properties and requirements that justify this Object being here....

}

open class ImageModel: Object {
    // The filename on disk to use to create a UIImage
    @objc dynamic var filename: String? = nil
    // an description of the image
    @objc dynamic var altDescription: String? = nil
   
}

In some circumstances I want to only get QuestionModels where the first ImageModel.filename != “”

Here’s what I’ve tried:

res = realm?.objects(QuestionModel.self)

and I want to further filter the QuestionModels based on certain criteria

Using NSPredicate and Subquery seems to work, though I don’t think it understands which image.name is blank, it just gives me all questions where one of the image.filenames is filled in

res = res.filter("SUBQUERY(media.images, $image, $image.name != '').@count > 0")

However, I really want to move to the new Realm Swift Query API if possible.

res = res.where {
                $0.media.images.name != ""
            }

NSException * “Key paths that include a collection property must use aggregate operations”

res = res.where {
           ($0.media.images.name != "").count > 0
	    }

NSException * “Subqueries must contain a keypath starting with a collection.”

I’m very confused. I can’t seem to find an example online which makes me wonder if this is possible.
Any thought appreciated

That’s not going to work as-is.

There is no direct relationship between QuestionModel and ImageModel.

Also, there is no ‘first’ ImageModel in the database. The only place where that would occur would be in the MediaModel images List. List are ordered - nothing else is unless a sort construct is added to a query.

So the Question Model has one and only one MediaModel - are you wanting to query for a QuestionModel whose MediaModel has an empty list? ( images.count == 0)?

I think an inverse relationship may be a solution but we need more data.

Can you add some clarity on what result you’re expecting from the query?

Hi Jay,

I was pretty sure I was trying to do the impossible here, and your comments confirmed it. Thanks.

I agree that the first / second image concept is a little odd (there are good reasons for it, I prmise!). However I’ve refactored the requirements a little and my code can now cope with either ImageModel having a blank filename. The order is no longer important.

I just need to get back any QuestionModels that have at least one valid ImageModel.name (i.e. is not blank). I think my NSPredicate SUBQUERY does the right thing here. Would you agree that looks right?

Is there any way to rewrite the SUBQUERY NSPredicate in Swift Query API?

Thanks

For me, subqueries are great and a powerful tool but they can often indicate there may be a better way to model the data.

In your case there’s a direct forward relationship from QuestionModel to MediaModel. Why not create a relationship from MediaModel back to QuestionModel?

class QuestionModel: Object {
    @Persisted var media: MediaModel?
}

class MediaModel: Object {
    @Persisted var question: QuestionModel?
    @Persisted var images = RealmSwift.List<ImageModel>()
}

And here’s why it would be a cool solution:

With that model, now you can perform a simple query on the MediaModels directly, which then in turn gives you the corresponding QuestionModel

let mediaResults = realm.objects(MediaModel.self).where { $0.images.filename != "" }
mediaResults.forEach { media in
    print(media)
}

That simplifies the code, uses the newer Swift Query API format and is pretty tight!

Our application (a speech therapy app) allows the user to filter questions on lots of different criteria. These criteria are spread around different objects that all hang off the QuestionModel object. If the users wants the question to be shown on screen as a picture then we also need to restrict the QuestionModels to only those that have images set.

So some psuedo code might be:

res = realm.objects(QuestionModel.self)

if (filter on word length) {
   res = res.where {
      $0.settingsObject.wordLength > 4
  }
}


if (filter on word type) {
   res = res.where {
      $0.settingsObject.wordType == 'Verb'
  }
}


if (display as picture) {
   res = res.filter("SUBQUERY(media.images, $image, $image.name != '').@count > 0")
}

Your idea of creating a relationship back to the QuestionModel looks neat, but it would only work for me if I can use it to further filter down a result set that was originally created from the QuestionModel objects. I don’t know if that’s possible, but if it is I’d be interesting in learning how that works.

Hope that helps to explain a little more of the context for this question, and I really appreciate you taking the time to respond.

Oh - you have a typo in your predicate

"SUBQUERY(media.images, $image, $image.name != '').@count > 0"
                                       ^^^^

should be

$image.filename

to make it agree with your ImageModel. My code is similar

let questionPredicate = NSPredicate(format: "SUBQUERY(media.images, $image, $image.filename == %@).@count > 0", "")

let questions = realm.objects(QuestionModel.self)

print("Questions with Media image list with one+ valid filename is: \(questions.filter(questionPredicate).count)")

Brilliant. I’m going to go the NSPredicate and SUBQUERY route as a solution.
Thanks for helping me work through this

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.