How to reload a model instance kept in a service

#1

I attach a file with ember-active-storage and the image is not displayed after updating the model. I have to refresh the page to see it. I tried to call this.transitionToRoute('same_page_route') in a controller but without success. Actually, I’m keeping the current shop (selected by a User) instance in a currentShop service, that makes it available throughout all the pages related to the current shop.

So, when updating the shop information, I just peek it from the service in the corresponding controller by id and update it:

#controllers/information.js

actions: {

  addShopPhoto(blob) {
    this.get('currentShop.shop').set('photo', blob.get('signedId'));
  },
  
  async save() {
      this.errors.length = 0;
      let shop = this.store.peekRecord('shop', this.get('currentShop.shop.id'));
      shop.set('modifiedBy', this.get('currentUser.user').get('username'));
      shop.set('status', this.get('status'));

      if (shop.get('photo.signed_id')) {
        shop.set('photo', shop.get('photo.signed_id'));
      }
    try {
      if (shop.validations.isValid) {
        await shop.save();
        await this.get('flashMessages').success(this.get('intl').t('flash.shop.updated'));
        await this.transitionToRoute('information');
      }
    } catch(response) {
      let errorMessage = extractServerError(response.errors);
      this.errors.pushObject(errorMessage);
    }
    ...

The addShopPhoto action is triggered from the template component:

# templates/information.hbs

  {{file-upload
    onFileUploaded=(action "addShopPhoto")
    class="form-control-file"
  }}

The photo is atached to the shop but is nit displayed, I have to refresh the same page to see it. By the way, should I really keep await this.transitionToRoute('information'); if I stay on the same route/page ? Redirecting to another route changes nothing. I think the reason is that the actual information route has no model hook defined because I don’t need it, - all the information displayed in the information.hbs template is coming from currentShop.shop that I keep in currentShop service. Any ideas? Thank you.

1 Like
#2

Even after explicitly loading a shop in the model hook and using Route#refresh has not changed anything:

#routes/information.js

import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from '../mixins/authenticated-route-mixin';
import { inject as service } from '@ember/service';

export default Route.extend(AuthenticatedRouteMixin, {
  currentShop: service(),
  
  model() {
    return this.store.peekRecord('shop', this.get('currentShop.shop.id'));
  },
  
  actions: {
    refreshModel() {
      this.refresh();
    }
  }
});

Tried to call refreshModel from the controller:

#controllers/information.js

async save() {
...
  let shop = this.store.peekRecord('shop', this.get('currentShop.shop.id'));
  shop.set('modifiedBy', this.get('currentUser.user').get('username'));
      shop.set('status', this.get('status'));
      if (shop.get('photo.signed_id')) {
        shop.set('photo', shop.get('photo.signed_id'));
      }
      try {
        this.set('showErrors.name', true);
        if (shop.validations.isValid) {
          await shop.save();
          await this.get('currentShop').setShop(shop);
          await this.get('flashMessages').success(this.get('intl').t('flash.shop.updated'));
          await this.send('refreshModel');
          await this.transitionToRoute('information');
        }
      } catch(response) {
        let errorMessage = extractServerError(response.errors);
        this.errors.pushObject(errorMessage);
      }
...

Isn’t there really no way to refresh the shop model without hitting the backend ? Or I misundersand the way EmberData and Services work ?

#3

If I’m understanding correctly, you don’t want refresh at all. It’s for getting an update from the server.

Doing set on the model is all that should be required to cause the changes to render. Debug by examining the model using the ember inspector to see what its state really is. Maybe you have a promise where you expected a value.

#4

Hi @ef4 ! Thank you for the response. I think to have identified the problem (see more details in the issue I opened).

  • On the backend side (Rails ActiveStirage), Rails expects the value signed_id to be present in the request parameters.
  • the value signed_id is set up as shop photo when attaching a new image (see addShopPhoto action in the controller above.

That’s why I had to modify the Rails serializer for photo attributes so that it was sent as a Hash:

def photo
    { url: rails_blob_url(object.photo), signed_id: object.photo.signed_id } if object.photo.attached?
end

to have both a photo url and signed_id values on Ember side.

The problem is with updating a shop already having an attached photo without attaching a new one… In this case shop photo will be submitted as a Hash:

{url: "http://localhost:3000/rails/active_storage/blobs/e…6231764e899713ffe951a327b2cc04c2e68346b/jruby.jpg", signed_id: "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBOdz09IiwiZXhwIj…2lkIn19--d6231764e899713ffe951a327b2cc04c2e68346b"}

but Rails expects either a single value of photo as signed_id or nothing at all. That’s why it fails.

So I tried to set explicitly signed_id before updating a shop:

async save() {
...
// we are not sure if a photo was attached or not, that's why we check it before
if (shop.get('photo.signed_id')) {  
        shop.set('photo', shop.get('photo.signed_id'));
      }
      try {
        this.set('showErrors.name', true);
        if (shop.validations.isValid) {
          await shop.save();
         await this.get('currentShop').setShop(shop);
...

it works but, as you could guess, in this case the currentShop.shop.photo will contain signed_id value and no url . When refreshing the page, Ember will reload the same shop and currentShop.shop.photo will return a Hash:

{url: "http://localhost:3000/rails/active_storage/blobs/e…6231764e899713ffe951a327b2cc04c2e68346b/jruby.jpg", signed_id: "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBOdz09IiwiZXhwIj…2lkIn19--d6231764e899713ffe951a327b2cc04c2e68346b"}

that’s why the photo will be displayed.

More of that, the same happens when attaching a photo when the shop has no photo yet. After saving a shop

await shop.save();
await this.get('currentShop').setShop(shop);
...

the value of shop.photo contains only signed_id value, what means that the backend should be requested to get the correct value of the serialized photo attribute, - in this case it will contain the needed 2 values hash:

{url: "http://localhost:3000/rails/active_storage/blobs/e…6231764e899713ffe951a327b2cc04c2e68346b/jruby.jpg", signed_id: "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBOdz09IiwiZXhwIj…2lkIn19--d6231764e899713ffe951a327b2cc04c2e68346b"}

I think, I have to find a way to re-assign a photo to the currentShop.shop as a Hash containing url and signed_id in case if it is present. The problem is that I can’t see how to fetch url value.

I don’t know if it is clear enough, ActiveStorage with Ember is still not very frequent in use :confused:

#5

The solution I came to was to add the model hook to the information route as well as an action to refresh the model as follows:

# routes/information.js

export default Route.extend(AuthenticatedRouteMixin, {
  currentShop: service(),

  model() {
    let shop = this.store.findRecord('shop', this.get('currentShop.shop.id'));
    this.get('currentShop').setShop(shop);

    return shop;
  },

  actions: {
    refreshModel() {
      this.refresh();
    }
  }
});

Then, in information controller Ii call refreshModel action right after the shop is updated:

# controllers/information.js

async save() {
...
if (shop.validations.isValid) {
   await shop.save();
   await this.send('refreshModel');
   await this.get('flashMessages').success(this.get('intl').t('flash.shop.updated'));
}
...

and it worked.