Convert a Proxy object to Ember Data before calling save()

#1

I’m trying to understand the following use case. When we use one of DS.Store fetch methods like findAll, findRecord, etc., all of them return a Promise object. But if later you try to get the previously fetched Promise object and set a new value on one of its attributes, for example:

export default Controller.extend({
  currentUser: service(),
  currentShop: service(),
...
  actions: {
    async update() {
      let shop =  this.get('currentShop.shop');
      shop.set('modifiedBy', this.get('currentUser.user').get('username'));
      await shop.save();
 }

you will get an error like

shop.save() is not a function

When checkin what kind of shop is, you will get

Proxy {_belongsToState: BelongsToRelationship, isFulfilled: true, isRejected: false, _super: ƒ}

The question is what is the right way to get around of this, knowing that the above Shop instance has been already fetched and is available in the store. Use store.peekRecord() method like this:

let shop = store.peekRecord('shop', this.get('currentShop.shop.id'));
shop.set('modifiedBy', this.get('currentUser.user').get('username'));
await shop.save();

or there is another way ?

Thank you.

#2

Hi @belgoros! :wave:

I’ve been somewhat stymied by proxy objects as well, and I can’t claim I’ve got it all figured out.

However, I believe there are 2 options for you:

  1. Use shop.get('save')(). I haven’t tried this myself for save() but in theory, any call to get() on a proxy object will get the underlying object’s property by name. In this case, the save method.
  2. Better ember engineers will probably warn you away from this, but I’ve found you can get the underlying object with shop.content. The tricky bit is that if currentShop.shop is an async relationship, it might not be loaded yet when your code runs, in which case you’d want to do await currentShop.shop first.
  3. You could set the relationship up as {async: false} and be sure to always return the shop data in your api response, which would make this object be the real shop object and not a proxy to it.

#3 is probably what the awesome guys at EmberMap would suggest – in fact, they recommend making all your relationships {async: false}

#3

IMHO the following should work just fine:

let shop = await this.store.findRecord('shop', shopId);
set(shop, 'modifiedBy', userName);
await shop.save();

Maybe you are trying to do the equivalent of this:

let shop = this.store.findRecord('shop', shopId); // Note: no await
set(shop, 'modifiedBy', userName);
await shop.save();

If that is the case, then the solution is to await the initial promise to resolve before you actually use it.

1 Like
#4

As @shull, I’m also a little bit mystified and sometimes surprised by the Promise/Proxy object behaviour. Here is the flow what was going on:

  • get the signed-in User (current-user)
  • set the signed-in User onto currentUser service
  • set the shop the User (has belongsTo shop relation) onto currentShop service
let user = await this.get('store').queryRecord('user', { me: true });
if (user) {
        this.get('flashMessages').success(this.get('intl').t('flash.signed_in'));
        this.get('currentShop').setShop(user.get('shop'));
        this.set('user', user);
        this.get('shopService').loadShops();
        return user;
      } else {
        this._respondWithError();
      }
...

A list of all the shops already exists in store as it was fetched at the same time as a User was signed in in the corresponding route model hook.

It means that when updating a Shop like this:

let shop =  this.get('currentShop.shop');
shop.set('modifiedBy', this.get('currentUser.user').get('username'));
await shop.save();

will fail because I’m getting a Shop Proxy object.

As it is 100% sure that the user shop already exists in the store, the only solution I found was to use

let shop = store.peekRecord('shop', this.get('currentShop.shop.id'));

to not hit a new request.

This way, calling save will work:

shop.set('modifiedBy', this.get('currentUser.user').get('username'));
await shop.save();
#5

I stumbled on this function the other day that unwraps proxy objects. YMMV but it looks promising to me.