Ember Data Proxy BelongsTo

Hey friends,

I have 2 models.

The parent has a hasMany relationship with the children.

Each child has a belongsTo relationship with its parent.

Before upgrading from Ember Data v2.18 → 3.7 I was able to get the parent from the child by doing child.parent or child.get("parent") and it just worked.

Now I am getting a Proxy object. I tried setting async: false on the parent belongsTo like this: parent: belongsTo("parent-model", { async: false })

This didn’t work either.

The children are embedded of the parent so they are all loaded in the same request. Any ideas? Thanks!

export default Model.extend({
    children: hasMany("child-model", { async: false }),
});
export default Model.extend({
    parent: belongsTo("parent-model")
});

Yeah this behavior changed in Ember data 3.2 or 3.3 or somewhere thereabouts. Broke some relationship id lookups in our app. I still haven’t got around to doing a deep dive on the reasoning or all of the implications, but as a workaround you can always reference the related model via the relationship reference e.g.:

child.belongsTo('parent').value()

or for just the id (whether the relationship is loaded or not):

child.belongsTo('parent').id()

Obviously that’s not as nice as being able to just reference the relationship directly but it works and in some cases (with async relationships) can be more helpful anyway.

1 Like

Maybe I am not understanding but this works for me:

1 Like

Thanks @sukima!

I adapted your Twiddle to show the actual error. It only happens when invoked from JS and not from a Template.

Use of this in JS is no different then the use in the template. If you noticed in the template I checked isPending to know when the parent model is available. Because we’ve asked ember data to associate the parent belongsTo as an asynchronous operation we have to use asynchronous code to manage it.

Since templates cannot manage Promises the async relationship is wrapped in a proxy object (PromiseProxyMixin) which offers derived state like isPending. When resolved the object will proxy any property lookups to the real model that it was resolved to.

95% of the time this is what you want. However, in your case you are accessing things in JS land. Since JS doesn’t rerender on state changes like a template does you have to rely on Promises and use the .then or async/await to know you have a resolve model. Otherwise asking for any properties on the proxy will result in undefined since the underlying model has not resolved yet.

This is an intentional API that any async code in JS has to deal with. Here is a revised version of your twiddle to demonstrate: Ember Twiddle

You have two options which @samselikoff has gone into great detail with in the past on his EmberMap series.

  1. Lean into the async/proxy API and assume that the value is not ready till after you await or .then the relationship
  2. Avoid the API by declaring the relationship { async: false } and making sure you are providing the relationship up front (usually with a JSON:API includes directive

Because I like the freedom of providing loading indicators if I wish I tend to prefer option 1. The later maybe convenient at the usage site does add on the overhead of you making sure you have included the relationship with your initial payload. Much of this depends on application design and your back end API. Much of the Ember community however, tends to prefer option 2 more.

1 Like

Thanks again @sukima!

The original issue is with async: false relationships anyways. In my use case parent and children are returned in a single response. children are embedded in the response of parent. I don’t want to await child.parent they are both loaded at the same time so it feels a bit weird to do that.

In your Twiddle the relationships are async: true so it isn’t 100% a fair reproduction. I am not sure how to tweak the Twiddle so that Mirage generates both models embedded and async: false. If you have the time to test that out it would be awesome :slight_smile:

Still thanks a lot!

1 Like

I updated the original twiddle to show there is no issue here. If you set the belongsTo to async: false you get the record, if you set it to async: true you get the proxy with the promise in the correct state. Ember Twiddle