Skip to content
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,43 @@ export default Ember.Route.extend({
}
}
});
```

## Attachments

`Ember-Pouch` provides an `attachment` transform for your models, which makes working with attachments as simple as working with any other field.

Add a `DS.attr('attachment')` field to your model. Provide a default value for it to be an empty array.

```js
// myapp/models/photo-album.js
export default DS.Model.extend({
photos: DS.attr('attachment', {
defaultValue: function() {
return [];
}
});
});
```

Here, instances of `PhotoAlbum` have a `photos` field, which is an array of plain `Ember.Object`s, which have a `.name` and `.content_type`. Non-stubbed attachment also have a `.data` field; and stubbed attachments have a `.stub` instead.
```handlebars
<ul>
{{#each myalbum.photos as |photo|}}
<li>{{photo.name}}</li>
{{/each}}
</ul>
```

Attach new files by adding an `Ember.Object` with a `.name`, `.content_type` and `.data` to array of attachments.

```js
// somewhere in your controller/component:
myAlbum.get('photos').addObject(Ember.Object.create({
'name': 'kitten.jpg',
'content_type': 'image/jpg',
'data': btoa('hello world') // base64-encoded `String`, or a DOM `Blob`, or a `File`
}));
```

## Sample app
Expand Down
41 changes: 41 additions & 0 deletions addon/transforms/attachment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Ember from 'ember';
import DS from 'ember-data';

const { isNone } = Ember;
const keys = Object.keys || Ember.keys;

export default DS.Transform.extend({
deserialize: function(serialized) {
if (isNone(serialized)) { return []; }

return keys(serialized).map(function (attachmentName) {
return Ember.Object.create({
name: attachmentName,
content_type: serialized[attachmentName]['content_type'],
data: serialized[attachmentName]['data'],
stub: serialized[attachmentName]['stub'],
length: serialized[attachmentName]['length'],
digest: serialized[attachmentName]['digest']
});
});
},

serialize: function(deserialized) {
if (!Ember.isArray(deserialized)) { return null; }

return deserialized.reduce(function (acc, attachment) {
const serialized = {
content_type: attachment.get('content_type'),
};
if (attachment.get('stub')) {
serialized.stub = true;
serialized.length = attachment.get('length');
serialized.digest = attachment.get('digest');
} else {
serialized.data = attachment.get('data');
}
acc[attachment.get('name')] = serialized;
return acc;
}, {});
}
});
1 change: 1 addition & 0 deletions app/transforms/attachment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-pouch/transforms/attachment';
74 changes: 74 additions & 0 deletions tests/unit/transforms/attachment-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { moduleFor, test } from 'ember-qunit';

import Ember from 'ember';

let testSerializedData = {
'hello.txt': {
content_type: 'text/plain',
data: 'aGVsbG8gd29ybGQ=',
digest: "md5-7mkg+nM0HN26sZkLN8KVSA=="
// CouchDB doesn't add 'length'
},
'stub.txt': {
stub: true,
content_type: 'text/plain',
digest: "md5-7mkg+nM0HN26sZkLN8KVSA==",
length: 11
},
};

let testDeserializedData = [
Ember.Object.create({
name: 'hello.txt',
content_type: 'text/plain',
data: 'aGVsbG8gd29ybGQ=',
digest: 'md5-7mkg+nM0HN26sZkLN8KVSA=='
}),
Ember.Object.create({
name: 'stub.txt',
content_type: 'text/plain',
stub: true,
digest: 'md5-7mkg+nM0HN26sZkLN8KVSA==',
length: 11
})
];

moduleFor('transform:attachment', 'Unit | Transform | attachment', {});

test('it serializes an attachment', function(assert) {
let transform = this.subject();
assert.equal(transform.serialize(null), null);
assert.equal(transform.serialize(undefined), null);
assert.deepEqual(transform.serialize([]), {});

let serializedData = transform.serialize(testDeserializedData);

let hello = testDeserializedData[0].get('name');
assert.equal(hello, 'hello.txt');
assert.equal(serializedData[hello].content_type, testSerializedData[hello].content_type);
assert.equal(serializedData[hello].data, testSerializedData[hello].data);

let stub = testDeserializedData[1].get('name');
assert.equal(stub, 'stub.txt');
assert.equal(serializedData[stub].content_type, testSerializedData[stub].content_type);
assert.equal(serializedData[stub].stub, true);
});

test('it deserializes an attachment', function(assert) {
let transform = this.subject();
assert.deepEqual(transform.deserialize(null), []);
assert.deepEqual(transform.deserialize(undefined), []);

let deserializedData = transform.deserialize(testSerializedData);

assert.equal(deserializedData[0].get('name'), testDeserializedData[0].get('name'));
assert.equal(deserializedData[0].get('content_type'), testDeserializedData[0].get('content_type'));
assert.equal(deserializedData[0].get('data'), testDeserializedData[0].get('data'));
assert.equal(deserializedData[0].get('digest'), testDeserializedData[0].get('digest'));

assert.equal(deserializedData[1].get('name'), testDeserializedData[1].get('name'));
assert.equal(deserializedData[1].get('content_type'), testDeserializedData[1].get('content_type'));
assert.equal(deserializedData[1].get('stub'), true);
assert.equal(deserializedData[1].get('digest'), testDeserializedData[1].get('digest'));
assert.equal(deserializedData[1].get('length'), testDeserializedData[1].get('length'));
});