Search code examples
iosswiftrealmsql-order-by

Is there a way in Realm to implement order_by for nested objects


I am trying to retrieve nested objects sorted in a specific order when I retrieve the parent object using realm.objects(). And equally importantly, maintain the sort order if any nested object is modified.

I have searched related issues but they are either dated or not quite the issue I am describing

Here's an example: I have 'user's in Realm, each of which have 'task's (with extraneous fields omitted for clarity)

class task: Object {
    @objc dynamic var priority = 10
}

class user: Object {
    @objc dynamic var name = ""
    let tasks = List<task>()
}

I create a few users and then append tasks for any given user (a table in my UI with section for each User and Tasks for that User as rows in that user's section, SORTED by priority).

Default priority starts at 10, and can be changed in the range from 1-10 at any time.

When I retrieve users:

// (1)
users = realm.objects(user.self).sorted(byKeyPath: "name")

I want to retrieve their tasks sorted by priority.

Note that the priority is modified after the initial retrieval of users (meaning Results's task object's priority is changed under a realm.write()). To clarify, somewhere in my code, for a given user's task, I do the following:

realm.write() {
    task1.priority = newPriority
?

Which means that the user's task list should always be sorted by priority and not require repeating (1) above. I can't sort the user.tasks property because it is a "let" variable.

Note that both user and task objects have a sort order.

I could do index(remove/add) on tasks after I update 'priority' above but have not tried this yet. However, rather than do this manually (and assuming it works, and assuming there isn't a delete/add of 'task' happening alongside the reshuflle, ...), isn't that the whole idea behind ORMs, to make such things straightforward?

Anyway to do this? With Extensions on Realm? ??? Open to suggestions on alternative approaches vs nested objects.


Solution

  • Great question and an answer that really shows off Realms live updating capability.

    Restating the question

    A user has tasks and we want to work with those tasks as an ordered list, and if there's a change to the order, keep them ordered by the new order

    Using the two classes presented in the question, we have a button that calls a function to query realm for a user and store that user in a class var

    var myUser: UserClass!
    
    func loadUser() {
        if let realm = gGetRealm() { //my code to connect to Realm
            let userResults = realm.objects(UserClass.self)
            if userResults.count > 0 {
                let user = userResults[0]
                self.myUser = user
            }
        }
    }
    

    then a button that calls a function to simply print out that users tasks, ordered by priority

    func printOrderedTasks() {
        let sortedTasks = self.myUser.tasks.sorted(byKeyPath: "priority")
        for task in sortedTasks {
            print(task)
        }
    }
    

    So for this example, I created 4 tasks, and added them to a users tasks list and wrote the user to realm, the priority initial order is: 10, 0, 1, 4

    Then loadUser loads in and store the first user available and assigned it to the class var.

    The printOrderedTasks outputs the tasks, in ascending order. So after loading the user, clicking printOrderedTasks the output is

    0
    1
    4
    10
    

    then, using Realm Studio, change the 1 to a 6 and click the printOrderedTasks again and the output is

    0
    4
    6
    10
    

    without having to reload anything.

    Realm objects are live updating so as long as you have a reference to them in your code, any changes are reflected live.

    You could expand upon this by adding an observer to that object and Realm will notify the app of the event, to which you could reload a tableView or let the user know of the change.

    Edit:

    To take this a step further, the users tasks are also live updating objects and if you set a sort on them, those results maintain their sort.

    For example, let's re-write the above code to keep track of a users tasks that maintain a live sort. I've re-written the above code and eliminated the user class var and added a tasks class var. Note that we never need to re-sort the tasks, the sort order is set initially and they will stay sorted from that point forward.

    var myUserTasks: Results<TaskClass>!
    
    func loadUserAndGetTasks() {
        if let realm = gGetRealm() {
            let userResults = realm.objects(UserClass.self)
            if userResults.count > 0 {
                let user = userResults[0]
                self.myUserTasks = user.tasks.sorted(byKeyPath: "priority")
            }
        }
    }
    
    func printTasks() {
        for t in self.myUserTasks {
            print(t)
        }
    }
    

    The initial order was 10, 0, 1, 4 as above. If we then change the 1 to a six using Realm Studio, and then run the printTasks function, you'll see the ordering was automagically done because the Results are live updating.

    0
    4
    6
    10
    

    The cool thing here is that you don't need to keep resorting the tasks - they maintain their sort order.