Ember 2,过滤关系模型(hasMany,belongsTo)并根据关系计算计算属性

时间:2016-10-13 23:41:46

标签: javascript ember.js ember-data relationship ember-controllers

这些是我的文件:

模型

应用程序/模型/ 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')的应用程序中加载每个框。我需要一种方法来不加载我的webapp中的所有框,只是在篮子中的那个。我需要直接从后端“使用过滤器查询”吗?

更新的问题 2)计算subTotal的最佳Ember方法是什么?现在,使用下面的代码,Ember给出了subTotal,但只是在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);
      });
    });
  });
});

它给了我模板[object Object],因为我也在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为什么

将计算属性与promises一起使用是否合适?

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

promisenull个内容。

我哪里错了?

有任何不正确的return

此代码“有前途”是否正确?

2 个答案:

答案 0 :(得分:1)

对技术不熟悉并不是什么坏事,特别是当你的问题格式化并经过思考时。

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

但这没有什么限制和优点

  • 你需要发送带篮子的ids
  • 您必须启用coalesceFindRequests才能让它变得清晰
  • 它只会加载不存储的框

修改 you need to send ids with basket这意味着您的有效负载中的basket必须为其框提供标识。如果是休息api:{basket: {id: 1, boxes: [1,2,3], ...}。然后它将检查哪些ID尚未加载到商店中并在此处询问api(假设已加载id为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和filter来检查你是否拥有所有这些或者像这样)。
  • 好的想法是请求将是并行的而不是串行的,因此可能会加快速度。
  • 篮子也不必发送其盒子的ID。
  • 您可以通过更改query param
  • 来执行服务器端过滤

修改 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)哪个是计算subTotal

的最佳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不支持sideloading,您可以通过加载路由中的所有内容来伪造它(您必须使用第二个示例 - 如果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这是基本上更加充实的上述代码段版本。请注意,它将正确地等待并传播价格,这是两个承诺的深度,并且当事情发生变化时也应该更新(我也没有测试过)。

答案 1 :(得分:0)

@ {Kingpin2k在How to return a promise composed of nested models in EmberJS with EmberData?中很好地解释了您的问题的解决方案。

你想要做的只是加载一个篮子及其相关的模型(盒子,猫线和产品)而不是加载所有盒子,cartLines和产品。另外,要计算subTotal,我们需要预先解决所有这些依赖性承诺。按照前面提到的帖子中给出的解决方案,您的解决方案将如下所示:

MODEL:app / models / cart-line.js

$(function() {
  var limit = 10;
  var current = 0;
  $("button").on("click", function() {
    if (++current < limit) {
      $(".form-group").last().clone()
      .find("label")
      .text(function(_, text) {
        return text.replace(/\d+/, current + 1)
      })
      .end()
      .insertAfter(".form-group:last");
    } else {
      $(this).off("click").prop("disabled", "disabled");
    }
  });
})

ROUTE:app / routes / basket.js

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Add File</button>
<div class="form-group row">
  <label for="file1_recipient" class="col-xs-2 col-form-label">Upload Photo (1)</label>
  <div class="col-xs-10">
    <input type="file" class="file">
  </div>
</div>

CONTROLLER:app / controllers / basket.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
});

最后,对于你在这里遇到的问题:

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

如果遵循上面给出的解决方案,你不会使用计算结构,但只想指出类似条件下的解决方案。

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