From 6d75a93204b41ae02d720156eb1fe6051e253e2c Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Mon, 14 Dec 2015 16:54:19 +0600 Subject: [PATCH 01/10] `Attachment` transform Unit test courtesy of Matt Marcum @mattmarcum --- addon/transforms/attachment.js | 37 ++++++++++++++++++++ app/transforms/attachment.js | 1 + tests/unit/transforms/attachment-test.js | 44 ++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 addon/transforms/attachment.js create mode 100644 app/transforms/attachment.js create mode 100644 tests/unit/transforms/attachment-test.js diff --git a/addon/transforms/attachment.js b/addon/transforms/attachment.js new file mode 100644 index 0000000..7cbbea4 --- /dev/null +++ b/addon/transforms/attachment.js @@ -0,0 +1,37 @@ +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 null; } + + 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'], + }); + }); + }, + + 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; + } else { + serialized.data = attachment.get('data'); + } + acc[attachment.get('name')] = serialized; + return acc; + }, {}); + } +}); diff --git a/app/transforms/attachment.js b/app/transforms/attachment.js new file mode 100644 index 0000000..bf67f61 --- /dev/null +++ b/app/transforms/attachment.js @@ -0,0 +1 @@ +export { default } from 'ember-pouch/transforms/attachment'; diff --git a/tests/unit/transforms/attachment-test.js b/tests/unit/transforms/attachment-test.js new file mode 100644 index 0000000..c134015 --- /dev/null +++ b/tests/unit/transforms/attachment-test.js @@ -0,0 +1,44 @@ +import { moduleFor, test } from 'ember-qunit'; + +import Ember from 'ember'; + +let testSerializedData = { + 'test.txt': { + content_type: 'text/plain', + data: 'hello world!' + } +}; + +let testDeserializedData = [ + Ember.Object.create({ + name: 'test.txt', + content_type: 'text/plain', + data: 'hello world!' + }) +]; + +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); + + let serializedData = transform.serialize(testDeserializedData); + let name = testDeserializedData[0].name; + + assert.equal(serializedData[name].content_type, testSerializedData[name].content_type); + assert.equal(serializedData[name].data, testSerializedData[name].data); +}); + +test('it deserializes an attachment', function(assert) { + let transform = this.subject(); + assert.equal(transform.deserialize(null), null); + assert.equal(transform.deserialize(undefined), null); + + 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')); +}); From 9cd4a0eee26675ca169d87ad68f6ec1137b85662 Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Mon, 14 Dec 2015 18:07:37 +0600 Subject: [PATCH 02/10] Describe usage of `Attachment` transform --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 95e31a8..6004d10 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,39 @@ export default Ember.Route.extend({ } } }); +``` + +## Attachments + +`Ember-Pouch` provides an `attachment` transform for your models, which makes working with attachments is as simple as working with any other field. + +Add a `DS.attr('attachment')` field to your model: +```js +// myapp/models/photo-album.js +export default DS.Model.extend({ + photos: DS.attr('attachment'); +}); +``` + +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 + +``` + +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': data // ... can be a DOM File, Blob, or plain old String +})); ``` ## Sample app From 218e76ba8608bca332d66acd170835ac63281853 Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Mon, 14 Dec 2015 18:17:14 +0600 Subject: [PATCH 03/10] Test stubbed attachments --- tests/unit/transforms/attachment-test.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/unit/transforms/attachment-test.js b/tests/unit/transforms/attachment-test.js index c134015..345ce08 100644 --- a/tests/unit/transforms/attachment-test.js +++ b/tests/unit/transforms/attachment-test.js @@ -6,6 +6,10 @@ let testSerializedData = { 'test.txt': { content_type: 'text/plain', data: 'hello world!' + }, + 'stub.json': { + stub: true, + content_type: 'application/json' } }; @@ -14,6 +18,11 @@ let testDeserializedData = [ name: 'test.txt', content_type: 'text/plain', data: 'hello world!' + }), + Ember.Object.create({ + name: 'stub.json', + content_type: 'application/json', + stub: true }) ]; @@ -25,10 +34,15 @@ test('it serializes an attachment', function(assert) { assert.equal(transform.serialize(undefined), null); let serializedData = transform.serialize(testDeserializedData); - let name = testDeserializedData[0].name; + let name = testDeserializedData[0].get('name'); assert.equal(serializedData[name].content_type, testSerializedData[name].content_type); assert.equal(serializedData[name].data, testSerializedData[name].data); + + let stub = testDeserializedData[1].get('name'); + + assert.equal(serializedData[stub].content_type, testSerializedData[stub].content_type); + assert.equal(serializedData[stub].stub, true); }); test('it deserializes an attachment', function(assert) { @@ -41,4 +55,8 @@ test('it deserializes an attachment', function(assert) { 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[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); }); From 172fccc4611cd8fc3f3a647da305e1ce35bd9e1e Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Wed, 23 Dec 2015 11:28:02 +0600 Subject: [PATCH 04/10] Clarify the use of base-64 encoded String attachments --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6004d10..ea46667 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ Attach new files by adding an `Ember.Object` with a `.name`, `.content_type` and myAlbum.get('photos').addObject(Ember.Object.create({ 'name': 'kitten.jpg', 'content_type': 'image/jpg', - 'data': data // ... can be a DOM File, Blob, or plain old String + 'data': btoa('hello world') // base64-encoded `String`, or a DOM `Blob`, or a `File` })); ``` From 38c4ebea6ef6f6e4b6ed92ea13ec4f888d5aed1e Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Wed, 17 Aug 2016 14:26:06 +0700 Subject: [PATCH 05/10] Keep the `length` and `digest` properties of a stub attachment --- addon/transforms/attachment.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addon/transforms/attachment.js b/addon/transforms/attachment.js index 7cbbea4..5881ca4 100644 --- a/addon/transforms/attachment.js +++ b/addon/transforms/attachment.js @@ -14,6 +14,8 @@ export default DS.Transform.extend({ content_type: serialized[attachmentName]['content_type'], data: serialized[attachmentName]['data'], stub: serialized[attachmentName]['stub'], + length: serialized[attachmentName]['length'], + digest: serialized[attachmentName]['digest'] }); }); }, @@ -27,6 +29,8 @@ export default DS.Transform.extend({ }; if (attachment.get('stub')) { serialized.stub = true; + serialized.length = attachment.get('length'); + serialized.digest = attachment.get('digest'); } else { serialized.data = attachment.get('data'); } From 4e168647e113297e34f99c1ec463d257c6b47ab8 Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Wed, 17 Aug 2016 15:04:02 +0700 Subject: [PATCH 06/10] Test that `digest` and `length` properties of a stub attachment are preserved --- tests/unit/transforms/attachment-test.js | 38 +++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/tests/unit/transforms/attachment-test.js b/tests/unit/transforms/attachment-test.js index 345ce08..cce726b 100644 --- a/tests/unit/transforms/attachment-test.js +++ b/tests/unit/transforms/attachment-test.js @@ -3,26 +3,32 @@ import { moduleFor, test } from 'ember-qunit'; import Ember from 'ember'; let testSerializedData = { - 'test.txt': { + 'hello.txt': { content_type: 'text/plain', - data: 'hello world!' + data: 'aGVsbG8gd29ybGQ=', + digest: "md5-7mkg+nM0HN26sZkLN8KVSA==" + // CouchDB doesn't add 'length' }, - 'stub.json': { + 'stub.txt': { stub: true, - content_type: 'application/json' - } + content_type: 'text/plain', + digest: "md5-7mkg+nM0HN26sZkLN8KVSA==", + length: 11 + }, }; let testDeserializedData = [ Ember.Object.create({ - name: 'test.txt', + name: 'hello.txt', content_type: 'text/plain', - data: 'hello world!' + data: 'aGVsbG8gd29ybGQ=', }), Ember.Object.create({ - name: 'stub.json', - content_type: 'application/json', - stub: true + name: 'stub.txt', + content_type: 'text/plain', + stub: true, + digest: 'md5-7mkg+nM0HN26sZkLN8KVSA==', + length: 11 }) ]; @@ -34,13 +40,14 @@ test('it serializes an attachment', function(assert) { assert.equal(transform.serialize(undefined), null); let serializedData = transform.serialize(testDeserializedData); - let name = testDeserializedData[0].get('name'); - assert.equal(serializedData[name].content_type, testSerializedData[name].content_type); - assert.equal(serializedData[name].data, testSerializedData[name].data); + 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); }); @@ -55,8 +62,11 @@ test('it deserializes an attachment', function(assert) { 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')); }); From 62325c82e4209c9985df20e0351d74de9856264b Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Wed, 17 Aug 2016 15:29:59 +0700 Subject: [PATCH 07/10] Add missing property on a test object --- tests/unit/transforms/attachment-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/transforms/attachment-test.js b/tests/unit/transforms/attachment-test.js index cce726b..614b398 100644 --- a/tests/unit/transforms/attachment-test.js +++ b/tests/unit/transforms/attachment-test.js @@ -22,6 +22,7 @@ let testDeserializedData = [ name: 'hello.txt', content_type: 'text/plain', data: 'aGVsbG8gd29ybGQ=', + digest: 'md5-7mkg+nM0HN26sZkLN8KVSA==' }), Ember.Object.create({ name: 'stub.txt', From 9a5305f4eeb3382779dc54b7d9aeb07cebc3718b Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Wed, 17 Aug 2016 17:32:36 +0700 Subject: [PATCH 08/10] Deserialize "no attachments" into an empty array --- addon/transforms/attachment.js | 2 +- tests/unit/transforms/attachment-test.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/addon/transforms/attachment.js b/addon/transforms/attachment.js index 5881ca4..9c7c875 100644 --- a/addon/transforms/attachment.js +++ b/addon/transforms/attachment.js @@ -6,7 +6,7 @@ const keys = Object.keys || Ember.keys; export default DS.Transform.extend({ deserialize: function(serialized) { - if (isNone(serialized)) { return null; } + if (isNone(serialized)) { return []; } return keys(serialized).map(function (attachmentName) { return Ember.Object.create({ diff --git a/tests/unit/transforms/attachment-test.js b/tests/unit/transforms/attachment-test.js index 614b398..5741eba 100644 --- a/tests/unit/transforms/attachment-test.js +++ b/tests/unit/transforms/attachment-test.js @@ -39,6 +39,7 @@ 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); @@ -55,8 +56,8 @@ test('it serializes an attachment', function(assert) { test('it deserializes an attachment', function(assert) { let transform = this.subject(); - assert.equal(transform.deserialize(null), null); - assert.equal(transform.deserialize(undefined), null); + assert.deepEqual(transform.deserialize(null), []); + assert.deepEqual(transform.deserialize(undefined), []); let deserializedData = transform.deserialize(testSerializedData); From a931b3ed4f63974a85ba8a7434e556713683766b Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Wed, 17 Aug 2016 18:07:15 +0700 Subject: [PATCH 09/10] Is fix a typo [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea46667..6b7d5b3 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ export default Ember.Route.extend({ ## Attachments -`Ember-Pouch` provides an `attachment` transform for your models, which makes working with attachments is as simple as working with any other field. +`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: From 4870636339a59b0c93584050248391950720df4f Mon Sep 17 00:00:00 2001 From: Stepan Stolyarov Date: Thu, 18 Aug 2016 13:16:49 +0700 Subject: [PATCH 10/10] Documentation for `defaultValue` of `attachment` --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6b7d5b3..b8aa5bc 100644 --- a/README.md +++ b/README.md @@ -186,12 +186,16 @@ export default Ember.Route.extend({ `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: +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'); + photos: DS.attr('attachment', { + defaultValue: function() { + return []; + } + }); }); ```