From e142e99fdbbb95a3fbb8be577940017e82d9a344 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 7 Aug 2020 18:19:58 -0400 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20model=20grants=20as?= =?UTF-8?q?=20a=20fragment-array=20for=20statefulness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/generated/models/role.js | 6 +++ addons/api/addon/models/fragment-string.js | 16 ++++++++ addons/api/addon/models/role.js | 4 +- addons/api/addon/serializers/role.js | 21 +++++++++- addons/api/app/models/fragment-string.js | 1 + .../tests/unit/models/fragment-string-test.js | 13 ++++++ addons/api/tests/unit/models/role-test.js | 6 ++- .../api/tests/unit/serializers/role-test.js | 41 +++++++++++++++++-- 8 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 addons/api/addon/models/fragment-string.js create mode 100644 addons/api/app/models/fragment-string.js create mode 100644 addons/api/tests/unit/models/fragment-string-test.js diff --git a/addons/api/addon/generated/models/role.js b/addons/api/addon/generated/models/role.js index 254005ed5..0177972a0 100644 --- a/addons/api/addon/generated/models/role.js +++ b/addons/api/addon/generated/models/role.js @@ -33,4 +33,10 @@ export default class GeneratedRoleModel extends BaseModel { description: 'Whether the resource is disabled', }) disabled; + + @attr('number', { + description: 'Current version number of this resource.', + }) + version; + } diff --git a/addons/api/addon/models/fragment-string.js b/addons/api/addon/models/fragment-string.js new file mode 100644 index 000000000..78ddb80b5 --- /dev/null +++ b/addons/api/addon/models/fragment-string.js @@ -0,0 +1,16 @@ +import Fragment from 'ember-data-model-fragments/fragment'; +import { attr } from '@ember-data/model'; + +/** + * Represents an item in an array of strings. + */ +export default class FragmentStringModel extends Fragment { + + // =attributes + + @attr('string', { + description: 'The value of the string.' + }) + value; + +} diff --git a/addons/api/addon/models/role.js b/addons/api/addon/models/role.js index 09ff5d256..ccd4d1048 100644 --- a/addons/api/addon/models/role.js +++ b/addons/api/addon/models/role.js @@ -1,5 +1,5 @@ import GeneratedRoleModel from '../generated/models/role'; -import { array } from 'ember-data-model-fragments/attributes'; +import { fragmentArray } from 'ember-data-model-fragments/attributes'; export default class RoleModel extends GeneratedRoleModel { @@ -9,7 +9,7 @@ export default class RoleModel extends GeneratedRoleModel { * Grants is read-only _most_ under normal circumstances. But grants can * be persisted via a dedicated call to `saveGrants()`. */ - @array({readOnly: true}) grants; + @fragmentArray('fragment-string', {readOnly: true}) grants; // =methods diff --git a/addons/api/addon/serializers/role.js b/addons/api/addon/serializers/role.js index d22f01ef0..c4fb5ebb2 100644 --- a/addons/api/addon/serializers/role.js +++ b/addons/api/addon/serializers/role.js @@ -7,7 +7,7 @@ export default class RoleSerializer extends ApplicationSerializer { /** * If `adapterOptions.serializeGrants` is true, the serialization should - * include **only grants** and nothing else. Normally, grants are not + * include **only grants** and the version. Normally, grants are not * serialized. * @override * @param {Snapshot} snapshot @@ -27,8 +27,25 @@ export default class RoleSerializer extends ApplicationSerializer { */ serializeWithGrants(snapshot) { return { - grants: snapshot.attr('grants') + version: snapshot.attr('version'), + grants: snapshot.attr('grants').map(grant => grant.attr('value')) }; } + /** + * Converts string arrays to object arrays for use with `fragment-string`. + * E.g. `"foo"` -> `{value: "foo"}` + * @override + * @param {Model} typeClass + * @param {Object} hash + * @return {Object} + */ + normalize(modelClass, resourceHash) { + // TODO: can fragment string normalization be handled generically? + if (resourceHash.grants) { + resourceHash.grants = resourceHash.grants.map(value => ({ value })); + } + return super.normalize(modelClass, resourceHash); + } + } diff --git a/addons/api/app/models/fragment-string.js b/addons/api/app/models/fragment-string.js new file mode 100644 index 000000000..90c1321af --- /dev/null +++ b/addons/api/app/models/fragment-string.js @@ -0,0 +1 @@ +export { default } from 'api/models/fragment-string'; diff --git a/addons/api/tests/unit/models/fragment-string-test.js b/addons/api/tests/unit/models/fragment-string-test.js new file mode 100644 index 000000000..8bfd2c018 --- /dev/null +++ b/addons/api/tests/unit/models/fragment-string-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | fragment string', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('fragment-string', {}); + assert.ok(model); + }); +}); diff --git a/addons/api/tests/unit/models/role-test.js b/addons/api/tests/unit/models/role-test.js index 68160dec1..7cf7ba8fa 100644 --- a/addons/api/tests/unit/models/role-test.js +++ b/addons/api/tests/unit/models/role-test.js @@ -17,7 +17,8 @@ module('Unit | Model | role', function (hooks) { this.server.post('/v1/scopes/o_1/roles/123abc:set-grants', (schema, request) => { const body = JSON.parse(request.requestBody); assert.deepEqual(body, { - grants: ['grant1', 'grant2'] + grants: ['grant1', 'grant2'], + version: 1 }); }); const store = this.owner.lookup('service:store'); @@ -28,7 +29,8 @@ module('Unit | Model | role', function (hooks) { attributes: { name: 'Role', description: 'Description', - grants: ['grant1', 'grant2'], + grants: [{value: 'grant1'}, {value: 'grant2'}], + version: 1, scope: { scope_id: 'o_1', type: 'scope' diff --git a/addons/api/tests/unit/serializers/role-test.js b/addons/api/tests/unit/serializers/role-test.js index 7c97e2db5..5724cfb89 100644 --- a/addons/api/tests/unit/serializers/role-test.js +++ b/addons/api/tests/unit/serializers/role-test.js @@ -11,14 +11,16 @@ module('Unit | Serializer | role', function(hooks) { const record = store.createRecord('role', { name: 'User', description: 'Description', - grants: ['foo', 'bar'] + grants: [{value: 'foo'}, {value: 'bar'}], + version: 1 }); const snapshot = record._createSnapshot(); snapshot.adapterOptions = {}; const serializedRecord = serializer.serialize(snapshot); assert.deepEqual(serializedRecord, { name: 'User', - description: 'Description' + description: 'Description', + version: 1 }); }); @@ -29,7 +31,8 @@ module('Unit | Serializer | role', function(hooks) { const record = store.createRecord('role', { name: 'User', description: 'Description', - grants: ['foo', 'bar'] + grants: [{value: 'foo'}, {value: 'bar'}], + version: 1 }); const snapshot = record._createSnapshot(); snapshot.adapterOptions = { @@ -37,7 +40,37 @@ module('Unit | Serializer | role', function(hooks) { }; const serializedRecord = serializer.serialize(snapshot); assert.deepEqual(serializedRecord, { - grants: ['foo', 'bar'] + grants: ['foo', 'bar'], + version: 1 + }); + }); + + test('it normalizes records with array fields', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('role'); + const roleModelClass = store.createRecord('role').constructor; + const payload = { + id: '1', + name: 'Role 1', + grants: ['*', '*'] + }; + const normalized = serializer.normalizeSingleResponse( + store, + roleModelClass, + payload + ); + assert.deepEqual(normalized, { + included: [], + data: { + id: '1', + type: 'role', + attributes: { + name: 'Role 1', + grants: [{value: '*'}, {value: '*'}], + }, + relationships: {}, + }, }); });