I’ve been working on an Ember pet project that aggregates data from Mint. It has a model named Category that has many TransactionCache s:

// app/models/category.js import DS from ' ember-data ' ; const { attr , hasMany } = DS ; export default DS . Model . extend ({ transactionCaches : hasMany ( ' transaction-cache ' , { async : true }), name : attr ( ' string ' ), });

// app/models/transaction-cache.js import DS from ' ember-data ' ; const { attr , belongsTo } = DS ; export default DS . Model . extend ({ category : belongsTo ( ' category ' , { async : true }), occurredOn : attr ( ' moment ' ), amount : attr ( ' number ' ), averageAmount : attr ( ' number ' ), });

A Category might have a name (“Groceries”), and a TransactionCache is an aggregate ( amount , 100.0) of all transactions for a given month ( occurredOn , October 1, 2013). Additionally, it has a rolling twelve-month average ( averageAmount , 125.0) calculated when importing the data.

I created a categories/category-list component which rendered the list of categories:

{{!-- app/templates/components/categories/category-list.hbs --}} {{# each categories as | category | }} {{ categories / category-list-item category = category }} {{/ each }}

However, the server returns categories in an unknown order; with over 100 categories in total, this list needs to be sorted in an order that’s meaningful. I started by ordering the list by the most recent average monthly spend, but the data was stored on each category’s associated TransactionCache s instead of on the category itself.

As a first pass, I introduced this data onto Category itself with a computed property:

// app/models/category.js import DS from ' ember-data ' ; import Ember from ' ember ' ; const { attr , hasMany } = DS ; const { computed } = Ember ; export default DS . Model . extend ({ transactionCaches : hasMany ( ' transaction-cache ' , { async : true }), name : attr ( ' string ' ), averageMonthlySpend : computed . alias ( ' _averageAmounts.firstObject ' ), _averageAmounts : computed . mapBy ( ' _sortedTransactionCaches ' , ' averageAmount ' ), _sortedTransactionCaches : computed . sort ( ' transactionCaches ' , ' _occurredOnSort ' ), _occurredOnSort : [ ' occurredOn:desc ' ], });

Next, I updated the component and template to sort categories by this data:

// app/components/categories/category-list.js import Ember from ' ember ' ; const { computed } = Ember ; export default Ember . Component . extend ({ sortedCategories : computed . sort ( ' categories ' , ' _categoriesSort ' ), _categoriesSort : [ ' averageMonthlySpend:desc ' ], });

{{!-- app/templates/components/categories/category-list.hbs --}} {{# each sortedCategories as | category | }} {{ categories / category-list-item category = category }} {{/ each }}

While the component and corresponding template seemed like they were in a good place, I didn’t feel great about Category ; the introduction of this data caused the file to almost double in size, and given the data model, I felt confident it would likely gravitate towards becoming a God Class.

Ember includes ObjectProxy , which allows us to introduce the decorator pattern. It expects one piece of data, content , and additional data can be provided. Any functions not defined on the decorator get proxied to the decorated object.

// app/decorators/monthly-spending.js import Ember from ' ember ' ; const { computed } = Ember ; export default Ember . ObjectProxy . extend ({ averageMonthlySpend : computed . alias ( ' _averageAmounts.firstObject ' ), _averageAmounts : computed . mapBy ( ' _sortedTransactionCaches ' , ' averageAmount ' ), _sortedTransactionCaches : computed . sort ( ' transactionCaches ' , ' _occurredOnSort ' ), _occurredOnSort : [ ' occurredOn:desc ' ], });

I removed the code I previously added to Category :

// app/models/category.js import DS from ' ember-data ' ; const { attr , hasMany } = DS ; export default DS . Model . extend ({ transactionCaches : hasMany ( ' transaction-cache ' , { async : true }), name : attr ( ' string ' ), });

And decorated the items in the collection at the component level when sorting:

// app/components/categories/category-list.js import Ember from ' ember ' ; import MonthlySpending from ' appname/decorators/monthly-spending ' ; const { computed } = Ember ; export default Ember . Component . extend ({ sortedCategories : computed . sort ( ' _decoratedCategories ' , ' _categoriesSort ' ), _decoratedCategories : computed . map ( ' categories ' , ( category ) => { return MonthlySpending . create ({ content : category }); }), _categoriesSort : [ ' averageMonthlySpend:desc ' ], });