Data: belongsTo retrieved via "links" attribute cannot be accessed via get()

Created on 11 Oct 2013  路  13Comments  路  Source: emberjs/data

If I have a model "User" which has a relationship: membership: DS.belongsTo('membership', {async : true}) that is fetched from the links hash in the payload from the server, that membership is correctly fetched and loaded on to the User. That being said, saying user.get('membership') does not return the membership model. Instead, user.get('membership').get('content') is required to actually use the model...this seems like a bug to me. Am I missing something?

Most helpful comment

@toobulkeh relationships are async by default in ember-data since v2.x (if I am not mistaken), see #3366.

All 13 comments

I have just run into this issue too. Is this a known change in the API for ember-data 1.0? I haven't seen it mentioned anywhere in the docs.

In my case I wasn't using a "links" hash but an array of IDs.

In Ember Data 1.0, user.get('membership') on an async relationship returns a Promise, not a shell of a record:

user.get('membership').then(function(membership) {
  // use membership here
});

You can return a promise directly from the model hook in Ember:

App.MembershipRoute = Ember.Route.extend({
  model: function() {
    return this.modelFor('user').get('membership');
  }
});

@wycats That is not the issue. The issue is that when the promise resolves, it is not providing the model, but rather that it's providing a construct with requires me to call user.get('membership').get('content') even when the promise has been resolved...I feel like user.get('membership') should just give me the model...

Are you saying that in here: user.get('membership').then(function(membership) { /* membership is not a model */ })?

@wycats In this use case, I think the promise was resolved, so as a user when using a belongsTo, even with async I would like to be able to use user.get('membership') returns the corresponding model.
But I guess that does not matter, because using user.get('membership').then(function(membership)聽{}) is synchronous when the promise is already resolved ?

@abobwhite Did I correctly understood your expectation ?

This is actually extremely critical to how the whole thing works:

When you make a relationship async, it will always, 100% of the time return a promise when you get it. Otherwise, code could randomly change between returning a promise and a value based on other, non-local code. That would be disastrous.

A lot of promise-related annoyances will become easier with ES6:

spawn(function*() {
  var memberships = yield this.get('memberships');
  // memberships is the value that would have been passed to .then
});

Also, for what it's worth, I'm considering making all relationships async, because the current way it works has proven to be somewhat confusing and prone to people hacking at options until they get the behavior they want.

@abobwhite in user.get('membership').get('content') the content property is an internal implementation detail. The relationship get('membership') uses a proxy for data-binding purposes and content is the target of that proxy when the promise resolves, it isn't meant to be used directly in user code. So while content is a public api of ObjectProxy the fact that an async relationship is a proxy is not a public api, that it is a promise is the public API.

+1 on making it always async. It will be a little annoying for the always embedded case, it is too subtle and leads to other complexity that I think is harder to deal with.

@wycats @kselden Even when in some controller logic if I would like to just use this.get('membership'). I instead have to use this.get('membership').then(function(membership){ /* do things here */}); even though the promise is already resolved...this functionality used to work just fine. But when other improvements were made, this changed. @wycats I understand your reasoning about always returning the promise but it sure makes the use of a relationship a bit wonky when used in javascript (not just in templates). It would be nice to see this handled a bit differently.

@sly7-7 Yes, you understood me correctly.

@wycats This seems to be causing issues with computed properties too, for example in 0.13 I was using this code:

showAttachments: (->
  (@get('viewingSelf') || @get('isFriend')) && @get('user.attachments.length')
).property('user.attachments.@each', 'viewingSelf', 'isFriend')

Which no longer works because @get('user.attachments') is referring to a promise object. How should this type of access to relationships work now?

I am having the same problem. Even after the record is fully loaded, now that my relationship is async, it becomes a PromiseObject instead of a model instance. I want to be able to call model methods on that object. Am I missing something? At what point does a PromiseObject turn into a materialized model?

update
I ended up doing this

Em.RSVP.all([getModel1, getModel2]).then(function(realModels){
 model1 = realModels[0];
 model2 = realModels[1];
  //call model methods
});

Is this the best way to do this?

For posterity, whatever happened to "async as default" as discussed above?

@toobulkeh relationships are async by default in ember-data since v2.x (if I am not mistaken), see #3366.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

toobulkeh picture toobulkeh  路  3Comments

kennethlarsen picture kennethlarsen  路  3Comments

HenryVonfire picture HenryVonfire  路  3Comments

jlami picture jlami  路  3Comments

bekicot picture bekicot  路  4Comments