---
title: Decorating Arrays in Ember
teaser: 'Leverage Ember.ObjectProxy to introduce decorators into your Ember codebase.

  '
tags: web,ember,javascript
author: Josh Clayton
published_on: 2015-10-06
---

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:

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

```javascript
// 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:

```hbs
{{!-- 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:

```javascript
// 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:

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

```hbs
{{!-- 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.

```javascript
// 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`:

```javascript
// 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:

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

This pattern ensures `Category` doesn't become bloated, and the additional
information from `TransactionCache` is isolated to the decorator itself.

[Mint]: https://www.mint.com
[God Class]: http://c2.com/cgi/wiki?GodClass
[`ObjectProxy`]: http://emberjs.com/api/classes/Ember.ObjectProxy.html
