3
votes

I have an Ember Data model and I'm trying to do a computed property based on properties of an async hasMany relationship. For some reason, it never seems to recompute. How can I do this properly?

The code:

export default DS.Model.extend({
    splits: DS.hasMany('split', { async: true }),
    amount: Ember.reduceComputed('[email protected]', {
        initialValue: 0,
        addedItem: function(accValue, split) { return accValue + split.get('amount'); },
        removedItem: function(accValue, split) { return accValue - split.get('amount'); }
    })
    /* Neither of these work either.
    amount: Ember.computed.sum('[email protected]') // This doesn't work
    amount: Ember.computed('[email protected]', function() {
        return this.get('splits').reduce(function(pValue, split) {
            return pValue + split.get('amount');
        }, 0);
    })
    */
});

The failing test (Expected 1350, Got 0):

import { test, moduleForModel } from 'ember-qunit';
import Transaction from 'my-app/models/transaction';

moduleForModel('transaction', 'Unit - Transaction Model', {
    needs: ['model:split']
});

test('amount', function() {
    var transaction = this.subject();
    var store = this.store();
    transaction.get('splits').addObjects([
        store.createRecord('split', { amount: 250 }),
        store.createRecord('split', { amount: 1000 })
    ]);
    equal(transaction.get('amount'), 1250);
});
3

3 Answers

5
votes

Your hasMany property is async, therefore it is a promise and its value must be accesible with the then method.

transaction.get('splits').then(function(splits) {

  split = store.createRecord('split', { amount: 250 }),
  splits.pushObject(split);

  split = store.createRecord('split', { amount: 1000 })
  splits.pushObject(split);

});
3
votes

Hehe, I typed up a fairly long answer, only to realize what you were doing wrong, and then got a good laugh at myself for not realizing it. :) It's been a long time since I've used Ember-Data, but here's what I've got.

First of all, this has nothing to do with promises. Ember-Data hasMany relationships return PromiseArray objects. PromiseArray extends from ArrayProxy. And ArrayProxy is a way to populate and array asynchronously, while still being able to bind to it immediately. ArrayProxy does not specify how you should populate the array, therefore, the fact that PromiseArray uses a promise is irrelevant. Bind to an ArrayProxy like you would any other array. When the content changes (for whatever reason), your bindings will update.

Now that we have that out of the way, what you're doing wrong is using the Ember computed helpers wrong. For instance, Ember.comptued.sum is supposed to add an array of numbers. Something like this:

numbers: [1,2,3,4,5],
sum: Ember.computed.sum('numbers')

Ember takes care of the @each and all of that nonsense for you. But you don't have straight numbers, you have objects. So you need to use Ember.reduceComputed (or something similar). You were using that, but you were using the wrong key. You tacked on the [email protected], which isn't what Ember was expecting. So Ember was silently failing because behind the scenes, it was watching the [email protected].@each property. If you wanted to use reduceComputed, you would do something like this:

amount: Ember.reduceComputed('splits', { ... })

But I've never used reduceComputed, and I like to keep it simple, so try this:

amount: function() {
    return this.get('splits').reduce(function(sum, split) {
        return sum + split.get('amount');
    }, 0);
}.property('[email protected]')

Sorry for the long answer, but I wanted to explain my solution rather than just giving it. It's early, so if I screwed up some syntax, let me know. But I think you should get the main idea. :)

EDIT: I put together a small JSBin to kind of simulate what you're doing. You can find it here. You'll see that the objects property is an unresolved PromiseArray, just like you would get from an Ember-Data hasMany relationship. As soon as you click the 'resolve' button, the promise resolves, the ArrayProxy updates, and your computed total updates as well.

0
votes

Try this way

DS.Model.extend({
    splits: DS.hasMany('split', { async: true }),
    splitAmountAry:Ember.computed.mapBy('amount'),
    amount:Ember.computed.sum('splitAmountAry')
})