diff --git a/addons/api/addon/models/target.js b/addons/api/addon/models/target.js index 66cd0fdfa..0dad6ba7a 100644 --- a/addons/api/addon/models/target.js +++ b/addons/api/addon/models/target.js @@ -1,12 +1,32 @@ import GeneratedTargetModel from '../generated/models/target'; import { fragmentArray } from 'ember-data-model-fragments/attributes'; +import { computed } from '@ember/object'; export default class TargetModel extends GeneratedTargetModel { // =attributes + /** + * @type {[FragmentHostSetModel]} + */ @fragmentArray('fragment-host-set', {readOnly: true}) host_sets; + /** + * An array of resolved host set and host catalog instances. Model instances + * must already be loaded into the store (this method will not load unloaded + * instances). Unresolvable instances are excluded from the array. + * @type {[{model: HostSetModel, hostCatalog: HostCatalogModel}]} + */ + @computed('host_sets.[]') + get hostSets() { + return this.host_sets + .map(({ host_set_id, host_catalog_id }) => ({ + model: this.store.peekRecord('host-set', host_set_id), + hostCatalog: this.store.peekRecord('host-catalog', host_catalog_id) + })) + .filter(hostSetRef => hostSetRef.model !== null); + } + // =methods /** @@ -17,20 +37,17 @@ export default class TargetModel extends GeneratedTargetModel { * @return {Promise} */ saveHostSets(options={ adapterOptions: {} }) { - const defaultOptions = { - adapterOptions: { - method: 'set-host-sets', - serializeHostSets: true - } + const defaultAdapterOptions = { + method: 'set-host-sets', + serializeHostSets: true }; // There is no "deep merge" in ES. // All of this nonsense is here to ensure we get // a decent merge of `adapterOptions`. return this.save({ - ...defaultOptions, ...options, adapterOptions: { - ...defaultOptions.adapterOptions, + ...defaultAdapterOptions, ...options.adapterOptions } }); diff --git a/addons/api/tests/unit/models/target-test.js b/addons/api/tests/unit/models/target-test.js index a9ae5ca15..04baf1bab 100644 --- a/addons/api/tests/unit/models/target-test.js +++ b/addons/api/tests/unit/models/target-test.js @@ -6,10 +6,55 @@ module('Unit | Model | target', function (hooks) { setupTest(hooks); setupMirage(hooks); - test('it exists', function(assert) { - let store = this.owner.lookup('service:store'); - let model = store.createRecord('target', {}); - assert.ok(model); + test('it has a `hostSets` array of resolved model instances (if those instances are already in the store)', function(assert) { + assert.expect(6); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'target', + attributes: { + host_sets: [ + { host_set_id: '1', host_catalog_id: '2' }, + { host_set_id: '3', host_catalog_id: '2' } + ] + } + } + }); + const target = store.peekRecord('target', '123abc'); + assert.equal(target.host_sets.length, 2, 'Target has two entires in host_sets'); + assert.equal(target.hostSets.length, 0, 'Target has no resolved hostSets because they are not loaded yet'); + store.push({ + data: { + id: '1', + type: 'host-set', + attributes: {} + } + }); + store.push({ + data: { + id: '3', + type: 'host-set', + attributes: {} + } + }); + // Since `hostSets` is computed on `host_sets`, not the store itself, + // it's necessary to do this assignment to kick-off the computed update. + // eslint-disable-next-line no-self-assign + target.host_sets = target.host_sets; + assert.equal(target.host_sets.length, 2, 'Target has two entires in host_sets'); + assert.equal(target.hostSets.length, 2, 'Target has two resolved hostSets'); + assert.notOk(target.hostSets[0].hostCatalog, 'Host catalog was not resolved because it is not loaded yet'); + store.push({ + data: { + id: '2', + type: 'host-catalog', + attributes: {} + } + }); + // eslint-disable-next-line no-self-assign + target.host_sets = target.host_sets; + assert.ok(target.hostSets[0].hostCatalog, 'Host catalog is resolved'); }); test('it has a `saveHostSets` method that targets a specific POST API endpoint and serialization', async function (assert) {