LinkingObjects subscribe - correct behaviour?


#1

Hi,

In a partial synced realm, is it by design that I need to subscribe to an item’s linked object to be able to access it?

e.g.


// Note: subscribe() is just the subscribing process wrapped in a promise. 

let jobs = realm.objects('Job');
await subscribe(jobs);
let job = jobs[0];

// Outputs empty Results object - is this correct? 
console.log(job.organization);

// subscribe to the linkingObjects
await subscribe(job.organization);

// Correctly outputs Results object with the Organization
console.log(job.organization);



// Schema below for refrence.....


const OrganizationSchema = {
    name: 'Organization',
    primaryKey: '_id',
    properties: {
        permissions: '__Permission[]', 
        _id: 'string',
        jobs: 'Job[]',
    },
};

const JobSchema = {

    name: 'Job',
    primaryKey: '_id',
    properties: {
        _id: 'string',
        permissions: '__Permission[]',
        organization: {
            type: 'linkingObjects',
            objectType: 'Organization',
            property: 'jobs',
        }
    },
};

#2

Any object which is part of a subscription will also have it’s lists and links populated automatically. However, this is not true for “linkingObjects” type properties. As you found out, you should explicitly subscribe to the objects that you will access across those types of “inverse” relationships. If you don’t want to subscribe twice like that, consider rewriting the query to subscribe to “Organization” objects which meet your criteria across it’s “jobs” list so that all related jobs will be brought in automatically. If rewriting the subscription query is something that you’d like to try, I’m happy to help you rewrite it if you want to share the original subscription.


#3

Hi @jstone - thanks for your reply and apologies for the lateness of mine, just got back to work after Christmas.

The subscribe method I use is as follows, I’d very interested in your automatic subscription approch:

    static subscribe(results, msg = '') {

        var subscription = results.subscribe();
        var _timeout;

        return new Promise((resolve, reject) => {
            subscription.addListener((subscription, state) => {

                if (state === Realm.Sync.SubscriptionState.Complete) {
                    clearTimeout(_timeout);
                    resolve();
                }
                else if (state === Realm.Sync.SubscriptionState.Error) {
                    clearTimeout(_timeout);
                    reject(subscription.error);
                }
            });
            // although this is a failure, it's better to just let it timeout
            _timeout = setTimeout(() => {
                console.log("SUBSCRIBE ERROR - timed out")
                clearTimeout(_timeout);
                reject('timeout');
            }, 15000);
        });

    }

#4

Happy new year!

The subscription method looks good to me.

What I meant by the automatic subscription is that you could potentially restructure your queries to operate on the model which contains the list property (in the forward direction) instead of operating on the model with the “linkingObjects” property.

I don’t know the specifics of your query on “Job” but for example let’s say you want all jobs that have the word “Javascript” in the description property.
As you described, your current implementation would look like this:

let jobs = realm.objects('Job').filtered('description contains[c] $0', "javascript");
await subscribe(jobs);
let job = jobs[0];
// Empty because "linkingObject" properties are not included in the subscription
console.log(job.organization);

Instead, we can transform the above to this:

let orgs = realm.objects('Organization').filtered('jobs.description contains[c] $0', "javascript"); // "Any" is implied in list queries
await subscribe(orgs); // subscription pulls in the organisations and all the attached jobs from the "list" property
// the jobs query is the same query as before, just run locally and not subscribed because we rely on the "orgs" subscription to pull in these jobs
let jobs = realm.objects('Job').filtered('description contains[c] $0', "javascript");
let job = jobs[0];
// this should be populated now
console.log(job.organization);

Be aware that this assumes that you do not care to subscribe to jobs which match the filter but are not part of any orgainisation. If you did care about those results, you’d also need to subscribe to the jobs query as before (you’d have two subscriptions which will have some overlap in their results, but this is fine it’s just a small performance hit).

Subscriptions not pulling in “linkingObjects” properties is by design (otherwise we’d probably pull in way more objects than expected) but it’s a quirk that I don’t think is documented yet. I’ve notified our documentation team about it. Hopefully you can work around this by using my suggestion.


#5

@jstone thanks for your reply!

I can see that this is a good way of doing it if I want all of the Jobs in a Results object to be pre-subscribed. One concern is it’s not very obvious what is going on so it potentially makes the code harder to come back to. Also, if you have more than one linkingObjects on a Model (which I do) this couldn’t be used.

As my desired use of this relationship is to lookup the Organization the Job belongs to, normally on a single basis, my previous method of a second subscription seems to be ok. I just needed to check that this was the correct behaviour rather than a bug.

I have gone one step further and created a method on the Job’s schema model e.g.

    async subscribeToLinkedObjects() {
        await RealmUtils.subscribe(this.organization);
        await RealmUtils.subscribe(this.teams);
    }

I guess I could loop the results in my my subscribe method and if the subscribeToLinkedObjects method exists call it to subscribe to the linkedObjects. Though, I’m not sure what the performance would be like as in effect I’d be doing what you have intentionally avoided.

Anyway, thanks for clearing that up.