Ember 2, модели отношений фильтров (hasMany, принадлежит To) и вычисляют вычисленное свойство на основе отношений

Это мои файлы:

модели

app / models / export default DS . Модель . extension ({ name : DS . attr ( 'string' ), house : DS . принадлежит ( 'house' , { async : true }), поля : DS . hasMany ( 'box' , { async : true }) }); .js:

export default DS.Model.extend({
  qty: DS.attr('number'),
  basket: DS.belongsTo('basket'),
  cartLines: DS.hasMany('cart-line', { async: true })
});

приложение / модели / box.js:

export default DS.Model.extend({
  qty: DS.attr('number'),
  box: DS.belongsTo('box'),
  product: DS.belongsTo('product')
});

приложение / модель / корзина-line.js:

export default DS.Model.extend({
  name: DS.attr('string'),
  price: DS.attr('number')
});

приложение / модели / product.js:

export default Ember.Route.extend({
  model(params) {
    return Ember.RSVP.hash({
      basket: this.store.findRecord('basket', params.basket_id),
      boxes: this.store.findAll('box'),
      products: this.store.findAll('product')
    });
  },
  setupController(controller, models) {
    controller.setProperties(models);
    }
});

Маршруты

приложение / маршруты / basket.js:

export default Ember.Controller.extend({
  subTotal: Ember.computed('boxes.@each.cartLines', function () {
    return this.products.reduce((price, product) => {
      var total = price + product.get('price');
      return total;
    }, 0);
  })
});

Контроллеры

приложение / контроллеры / basket.js:

boxes: this.store.findAll('box')

Вопросов:

Я новичок, поэтому я изучаю и делаю ошибки. Сожалею.

1) Каков наилучший метод Ember для фильтрации отношений при первом входе в маршрут? Например, теперь я загружаю каждую коробку в свое приложение console.log(tot). Мне нужен способ не загружать все ящики в моем webapp, просто в корзине. Мне нужно " субтотальная : Ember . Вычислен ( 'basket.boxes @ each.cartLines.' , Функция () { пусть счетчик = 0 ; консоль . Журнал ( 'называется субтотальная:' , подсчет ); // Это должно быть 0 никогда кол = кол + 1 , вернуть это . получить ( 'basket.boxes' .) затем ( коробки => { коробки . Foreach ( коробка => { коробка . получить ( 'cartLines' ). затем ( cartLines => { cartLines . уменьшить ( function ( tot , value ) { console . log ( 'tot:' , tot + value . get ( 'product.price' )); return tot + value . get ( 'product.price' ); }, 0 ); }); }); }); }), с фильтром "непосредственно из бэкэнда?

ОБНОВЛЕННЫЙ ВОПРОС 2) Каков наилучший метод Ember для вычисления subTotal? Теперь, с приведенным ниже кодом, Ember дает мне subTotal, но только {{log subTotal}}после того, как subTotal вызвал : 0 ember . отлаживать . js : 10095 Класс { __ember1476746185015 : "ember802" , __ember_meta__ : Meta } subTotal называется : 0 ember . отлаживать . js : 10095 Класс { __ember1476746185015 : "ember934" , __ember_meta__ : Meta } ember . отлаживать . js : 10095 Класс { isFulfilled : true , __ember1476746185015 : "ember934" , __ember_meta__ : Meta } subTotal называется : 0 ember . отлаживать . js : 10095 Класс { __ember1476746185015 : "ember1011" , __ember_meta__ : Meta } ember . отлаживать . js : 10095 Класс { isFulfilled : true , __ember1476746185015 : "ember1011" , __ember_meta__ : Meta } tot : 3.5 tot : 6 tot : 13.5 tot : 21 tot : 24.5 tot : 27 tot : 3.5 tot : 6 tot : 13.5 tot : 21 tot : 24.5 tot : 27 tot : 3.5 tot : 6 tot : 13.5 tot : 21 tot : 24.5 tot : 27 s! Почему это? Как я могу ждать обещаний? Я не понимаю, что делать:

subTotal called:  0

Он дает мне шаблон [object Object], потому что я также использую hbs subTotal called: 0и в консоли, это дает мне следующее:

import Ember from 'ember';
import DS from 'ember-data';

export default Ember.Controller.extend({

  totalCount: Ember.computed('basket.boxes.@each.cartLines', function () {
    let total = 0;
    const promise = this.get('basket.boxes').then(boxes => {
      boxes.map(box => {
      // const trypromise = boxes.map(box => {
        console.log('box:', box);
        box.get('cartLines').then(cartLines => {
          console.log('cartLines:', cartLines);
          const cartLinesPromise = cartLines.map(cartLine => {
              console.log('cartLine:', cartLine);
              // return cartLine.get('qty');
              // return cartLine;
              // });
              return {
                qty: cartLine.get('qty'),
                price: cartLine.get('product.price')
              };
              //     return cartLines.map(cartLine => {
              //       console.log('cartLine:', cartLine);
              //       return cartLine.get('qty');
              //       //   return {
              //       //     qty: cartLine.get('qty'),
              //       //     price: cartLine.get('product.price')
              //       //   };
              //     });
            })
            // });
        return Ember.RSVP
          .all(cartLinesPromise)
          .then(cartLinesPromise => {
            console.log('cartLinesPromise:', cartLinesPromise);
            // cartLinesPromise.reduce((tot, price) => {
            //   console.log('tot:', tot);
            //   console.log('price:', price);
            //   console.log('tot+price:', tot + price);
            //   return tot + price, 0;
            // });

            return total = 10;
            // return total;
          })
        });

      });

      // return total;
    });

    return DS.PromiseObject.create({ promise });
  })

})

Почему он показывает три раза , независимо от того, есть ли ноль или одна или тысяча продуктов. Он всегда звонит трижды , почему ?{{log 'HBS totalCount:' totalCount}} {{log 'HBS totalCount.content:' totalCount.content}} Total: {{totalCount.content}}promise

Хорошо ли использовать рассчитанные свойства с обещаниями?

3) Правильно ли я с этим заключением отношений?

ОБНОВЛЕННЫЙ ВОПРОС 2 :

Теперь я использую этот код, но безуспешно:

null

Комментарии для многих попробовать.

В шаблоне я использую:

return

Но promiseиметь контент.model(params) { // we will return basket but make boxes ready return this.get('store').find('basket', params.basket_id).then(basket => { return basket.get('boxes').then(() => basket); }); }

Где я ошибаюсь?

Неправильно you need to send ids with basket?

Правильно ли этот код «многообещающий»?

javascript,ember.js,ember-data,relationship,ember-controllers,

7

Ответов: 2


1 принят

Нет ничего плохого в том, чтобы быть новым для технологии, особенно когда ваш вопрос хорошо отформатирован и продуман.

1) Каков наилучший способ Ember-Data для фильтрации отношений?

Это сложный вопрос с множеством возможных окончаний.

Проще всего просто спросить об этой модели.

Спросить в корзине

С учетом вашей модели вы можете:

basket

Но это имеет мало ограничений и преимуществ

  • {basket: {id: 1, boxes: [1,2,3], ...}
  • вам нужно включить coalesceFindRequests, чтобы сделать его нормальным
  • он будет загружать только те коробки, которые не хранятся

Изменить: you need to send ids with basket это означает, что basketв вашей полезной нагрузке необходимо будет указать идентификаторы для своих ящиков. В случае покоя апи: /boxes?ids[]=1&ids[]=3. Он будет проверить , какие идентификаторы не загружаются в магазин уже и спросить апи здесь (при условии , что поле с идентификатором 2 уже загружен): .model(params) { const store = this.get('store'); const basket = params.basket_id; return RSVP.hash({ model: store.find('basket', basket), boxes: store.query('box', {basket}), }); },

Спроси себя

query
  • С другой стороны, этот подход отправит запрос на корзину, только если корзина уже не находится в магазине (такая же, как и раньше), но всегда запрашивает поля (если вам это не нравится, вам придется использовать peekAll и фильтровать, чтобы проверить, есть ли у вас все они или smt, как это).
  • Полагаю, что запросы будут параллельными, а не серийными, так что это может ускорить процесс.
  • Корзина также не должна отправлять идентификаторы своих ящиков.
  • Вы можете выполнить фильтрацию на стороне сервера, изменив queryпараметр

Изменить: if you don't like it you would have to use peekAll and filter to check if you have all of them вы можете проверить это с помощью hasMany .

Следить за ними

Вместо отправки двух запросов на сервер вы можете сделать свой api так, чтобы он добавлял ящики в полезную нагрузку.

Загрузите только корзину и оставьте для загрузки с шаблона

Вы можете загружать только голый минимум (например, корзину с загрузкой), позволить ember продолжить и отображать страницу. Он увидит, что вы получаете доступ к basket.boxesсвойству и извлекаете его. Это не будет выглядеть само по себе и потребует дополнительной работы, как прядильщики и так далее. Но это один из способов ускорить загрузку и начальное время рендеринга.

2) Каков наилучший метод Ember для вычисления subTotal

You want to calculate sum of something that is three levels deep into async relationships, that's not going to be easy. First of I would suggest putting totalPrice computed property into basket model itself. Computed properties are lazily evaluated so there is no performance degradation and this is something that model should be able to provide.

Here is little snippet:

// basket.js
const {RSVP, computed} = Ember;

price: computed('boxes.@each.price', function() {
  const promise = this.get('boxes').then(boxes => {
    // Assuming box.get('price') is computed property like this
    // and returns promise because box must wait for cart lines to resolve.
    const prices = boxes.map(box => box.get('price'));

    return RSVP
      .all(prices)
      .then(prices => prices.reduce((carry, price) => carry + price, 0));
  });

  return PromiseObject.create({promise});
}),

You would need to write something like this for each level or give up some of the async relations. The problem with your computed property is that boxes.@each.cartLines wont listen on everything that can change overall price (for example change of price of product itself). So it won't reflect and update on all possible changes.

I would sagest to give up some async relations. For example request on /baskets/2 could sideload all of its boxes, cartLines and maybe even products. If your api doesn't support sideloading, you can fake it by loading everything in route (you would have to use second example - you are not allowed to access boxes before they are in the store in case of async: false). That would lead to much simpler computed properties to calculate total price and in case of sideloading also reduce stress on server and clients confections.

// basket.js
const {computed} = Ember;

boxes: DS.hasMany('box', {async: false}),

price: computed('boxes.@each.price', function() {
  return this.get('boxes').reduce(box => box.get('price'));
}),

Update and overall after thoughts

I don't think that doing all sums in one function is viable, doable or sane. You will end up in callback hell or some other kind of hell. Moreover this is not going to be performance bottleneck.

I made jsfiddle it is basicaly more fleshed out version of snippet above. Note that it will properly wait and propagate price which is two promises deep and also should update when something changes (also I didn't test that).


0

The solution to your question is nicely explained in How to return a promise composed of nested models in EmberJS with EmberData? by @Kingpin2k.

What you want to do is just load a basket and its associated models(box, cat-line and product) rather than loading all boxes, cartLines and products. Also to compute the subTotal, we would need all those dependency promises resolved beforehand. Following the solution given in the post mentioned earlier, your solution would look like:

MODEL: app/models/cart-line.js

export default DS.Model.extend({
  qty: DS.attr('number'),
  box: DS.belongsTo('box'),
  product: DS.belongsTo('product', { async: true })//assuming you are not side-loading these
});

ROUTE: app/routes/basket.js

export default Ember.Route.extend({
    model(params) {
        return this.store.findRecord('basket', params.basket_id).then((basket)=> {
            return basket.get('boxes').then((boxes)=> {
                let cartLinesPromises = boxes.map(function (box) {
                    return box.get('cartLines');
                });
                return Ember.RSVP.allSettled(cartLinesPromises).then((array)=> {
                    let productPromises = array.map(function (item) {
                        return (item.value).get('product');
                    });
                    return Ember.RSVP.allSettled(productPromises);
                });
            });
        });
    }
});

CONTROLLER: app/controllers/basket.js

subTotal: computed('model.boxes.@each.cartLines', function () {
    //you dont need to use DS.PromiseArray because the promises all have been already resolved in the route's model hook
    let total = 0;
    this.get('model.boxes').forEach((box)=> {
        box.get('cartLines').forEach((cartLine)=> {
            total += cartLine.get('product.price');
        });
    });
    return total;
})

Lastly, for the issue you were having here:

subTotal: computed('boxes.@each.cartLines', function() {
  return DS.PromiseArray.create({
    //"this" here is DS.PromiseArray object and not your controller instance
    promise: this.get('boxes').then(boxes => {
      return boxes.filter(i => i.get('cart-line'));
    })
  });
})

you would not use the computed construct if following the solution given above, but just wanted to point out solution in similar conditions.

subTotal: computed('boxes.@each.cartLines', function() {
  let controllerInstance = this;
  return DS.PromiseArray.create({
    promise: controllerInstance.get('boxes').then(boxes => {
      return boxes.filter(i => i.get('cart-line'));
    })
  });
})
javascript,ember.js,ember-data,relationship,ember-controllers,
Похожие вопросы