Resolving keypaths (or chained relationships) in route

68 Views Asked by At

I am processing a bunch of records and producing an array of hashes for a third party library. For the life of me I can't figure out why this doesn't work.

export default Route.extend({
  model: function(params) {
   let qp = {viewName: 'byDay'};

  return this.store.query('job-receipt', qp).then(
   (receipts)=>
    all(
      receipts.map(
        (receipt)=>
          hash({
            stockCode: receipt.get('job')
                                   .then(job => job.get('stockCode'))
                                   .then(stockCode => stockCode.get('stockCode')),
            productClass: receipt.get('job')
                                  .then(job => job.get('stockCode'))
                                  .then(stockCode => stockCode.get('productClass'))
                                  .then(productClass => productClass.get('descr')),
            qtyRecvd: receipt.get('qtyRecvd')
          })
        )
      )
);

If I keep reentering the route, eventually the promises resolve. If I check, the productClass promise just straight up gets called with a null value. Why isn't it waiting for the stockCode.get('productClass') to resolve? I know there are real values in there because it does eventually resolve.

I'm missing something super basic. I've tried Ember.get(thing, keypath) etc. Don't these all return promises? Isn't the RSVP.hash supposed to wait for all the promises to resolve before proceeding? Like I said, I know the data is good because eventually it does resolve (as opposed to me just not handling a rejection).

EDIT:

I changed the productClass promise to this:

productClass: receipt.get('job')
        .then(job => job.get('stockCode'))
        .then(stockCode => stockCode.get('productClass'))
        .then(productClass => {if (!productClass) {return 'foo';} return productClass.get('descr');})

Now the report renders correctly every time albeit with nonsense. If I navigate to another route, and then back to this route, it renders perfectly. So, that makes it hard for me to believe I have some kind of data error. And even some of the stock codes return the right product class - not 'foo' - on the first run through. I'm not even sure how to debug this further.

edit

Just saw this. May be a bug after all.

[3.2.0+] Snapshot’s related data has become null #5565

2

There are 2 best solutions below

0
John Larson On BEST ANSWER

It turns out it is a bug. The bug for belongs-to.js causes the model to not wait for internalModel to complete loading before resolving the promise. The fix linked below resolves the issue

[BUGFIX] use internalModel promise if already loading #5562

3
Donald Wasserman On

I think the main issue is that "reciepts.job" is likely a DS.belongsTo relationship right? If you switch it to load job: DS.belongsTo('job', {async: false}), that will force ember-data to load that property synchronously (and will save a lot of headaches). But that requires the data to be available either in the json response.

If that doesn't work, you should investigate ember concurrency. Use that to clean up your code to look a little more straightforward. You'll have to fill in some blanks or change things where I misunderstood your use case, but this is a likely a good starting point.

The idea is to continue to break all the asynchronous calls into seperate tasks. Each ember-concurrency task object returns a promise, so you can keep bundling them up until you get to model which you can return just like any other promise.

//model.js
import {task} from 'ember-concurrency';
.....

model() {
  return this.get('loadData').perform();
},
loadData: task(function*(){
  let reciepts = yield this.store.query('job-receipt', qp);
  let promises = reciepts.map(r => {
    return this.get('loadNestedData').perform(r);
  })
  return all(promises)
}),
loadNestedData: task(function*(reciept) {
  let job = yield receipt.get('job');
  return hash({
    stockCode: job.get('sockcode')
  });
})