Ember 2, отфильтруйте модели отношений (hasMany, ownTo) и вычислите вычисленное свойство на основе отношений

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

модели

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

export default DS.Model.extend({
  name: DS.attr('string'),
  house: DS.belongsTo('house', { async: true }),
  boxes: DS.hasMany('box', { async: true })
});

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

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

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

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

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

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

Маршруты

приложение / маршруты /basket.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);
  })
});

Вопросы:

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

1) Какой лучший способ фильтрации отношений Ember при первом входе в маршрут? Например, теперь я загружаю каждую коробку в своем приложении с boxes: this.store.findAll('box'), Мне нужен способ не загружать всю коробку в моем веб-приложении, только одну в корзине. Мне нужен "запрос с фильтром" прямо из бэкэнда?

ОБНОВЛЕННЫЙ ВОПРОС2) Какой метод Ember является лучшим для расчета подытога? Теперь, с кодом ниже, Ember дает мне итоговую сумму, но только в console.log(tot) и после обещаний! Почему это? Как я могу ждать обещания? Я не понимаю, что делать:

subTotal: Ember.computed('basket.boxes.@each.cartLines', function () {
  let count = 0;
  console.log('subTotal called: ', count);
  // It should be 0 ever
  count = count + 1;

  return this.get('basket.boxes').then(boxes => {
    boxes.forEach(box => {
      box.get('cartLines').then(cartLines => {
        cartLines.reduce(function (tot, value) {
          console.log('tot:', tot + value.get('product.price'));
          return tot + value.get('product.price');
        }, 0);
      });
    });
  });
});

Это дает мне в шаблоне [объект объекта], потому что я также использую в HBS {{log subTotal}} и в консоли это дает мне это:

subTotal called:  0
ember.debug.js:10095 Class {__ember1476746185015: "ember802", __ember_meta__: Meta}
subTotal called:  0
ember.debug.js:10095 Class {__ember1476746185015: "ember934", __ember_meta__: Meta}
ember.debug.js:10095 Class {isFulfilled: true, __ember1476746185015: "ember934", __ember_meta__: Meta}
subTotal called:  0
ember.debug.js:10095 Class {__ember1476746185015: "ember1011", __ember_meta__: Meta}
ember.debug.js:10095 Class {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

Почему это показывает три раза subTotal called: 0независимо от того, есть ли ноль или один или тысяча продуктов. Он всегда звонит три раза subTotal called: 0зачем?

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

3) Прав ли я с инкапсуляцией этих отношений?

ОБНОВЛЕНИЕ ВОПРОСА 2:

Сейчас я использую этот код, но безуспешно:

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 иметь null содержание.

Где я не прав?

Любой неверный return?

Является ли этот код "многообещающим" правильным?

2 ответа

Решение

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

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

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

Самое простое, что нужно сделать, это просто спросить об этой модели.

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

Учитывая вашу модель вы можете сделать:

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);
  });
}

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

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

Редактировать:you need to send ids with basket Это означает, что basket в вашей полезной нагрузке придется предоставить идентификацию для своих ящиков. В случае отдыха api: {basket: {id: 1, boxes: [1,2,3], ...}, Затем он проверит, какие идентификаторы еще не загружены в хранилище, и спросит здесь api (при условии, что поле с идентификатором 2 уже загружено): /boxes?ids[]=1&ids[]=3,

Спроси себя

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}),
  });
},
  • С другой стороны, этот подход будет отправлять запрос на корзину только в том случае, если корзина еще не в магазине (такая же, как раньше), но всегда запрашивать ящики (если вам это не нравится, вам придется использовать peekAll и фильтр, чтобы проверить, есть ли у вас все они или что-то в этом роде).
  • Хорошо, что запросы будут параллельными, а не последовательными, поэтому это может ускорить процесс.
  • Корзина также не должна отправлять идентификаторы своих ящиков.
  • Вы можете выполнить фильтрацию на стороне сервера, изменив 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 для расчета подытога

Вы хотите вычислить сумму чего-то, что на трех уровнях глубоко в асинхронных отношениях, это будет непросто. Прежде всего я бы предложил поместить вычисляемое свойство totalPrice в саму модель корзины. Вычисленные свойства лениво оцениваются, поэтому нет снижения производительности, и эта модель должна быть в состоянии обеспечить.

Вот небольшой фрагмент:

// 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});
}),

Вам нужно будет написать что-то подобное для каждого уровня или отказаться от некоторых асинхронных отношений. Проблема с вашим вычисленным свойством заключается в том, что boxes.@each.cartLines не будет слушать все, что может изменить общую цену (например, изменение цены самого продукта). Так что он не будет отражать и обновлять все возможные изменения.

Я хотел бы отказаться от некоторых асинхронных отношений. Например запрос на /baskets/2 может загрузить все свои коробки, cartLines и, возможно, даже продукты. Если ваш API-интерфейс не поддерживает боковую загрузку, вы можете подделать его, загрузив все в маршруте (вам придется использовать второй пример - вам не разрешено получать доступ к ящикам, прежде чем они будут в магазине в случае async: false). Это привело бы к гораздо более простым вычисляемым свойствам для расчета общей цены, а в случае дополнительной загрузки также уменьшило бы нагрузку на серверные и клиентские устройства.

// 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'));
}),

Обновление и общее после мыслей

Я не думаю, что выполнение всех сумм в одной функции является жизнеспособным, выполнимым или вменяемым. Вы окажетесь в аду обратного вызова или в каком-то другом аду. Более того, это не будет узким местом в производительности.

Я сделал jsfiddle в основном более детализированную версию сниппета выше. Обратите внимание, что он будет правильно ждать и распространять цену, которая составляет два обещания, и также должна обновляться, когда что-то меняется (также я не проверял это).

Решение вашего вопроса хорошо объясняется в разделе Как вернуть обещание, состоящее из вложенных моделей в EmberJS, с EmberData? @Kingpin2k.

Вам нужно просто загрузить корзину и связанные с ней модели (box, cat-line и product), а не загружать все коробки, cartLines и продукты. Также для вычисления подытога нам нужно было бы разрешить все эти обещания зависимости заранее. Следуя решению, приведенному в посте, упомянутом ранее, ваше решение будет выглядеть следующим образом:

МОДЕЛЬ: 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
});

МАРШРУТ: app /ways/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);
                });
            });
        });
    }
});

КОНТРОЛЛЕР: 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;
})

Наконец, по проблеме, которую вы имели здесь:

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'));
    })
  });
})

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

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'));
    })
  });
})
Другие вопросы по тегам