Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion addon/serializers/maintenance-schedule.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
import { isBlank } from '@ember/utils';
import clearUnsetPolymorphicRelationships from '../utils/clear-unset-polymorphic-relationships';

export default class MaintenanceScheduleSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
/**
Expand All @@ -26,6 +27,7 @@ export default class MaintenanceScheduleSerializer extends ApplicationSerializer
const json = super.serialize(snapshot, options);
const readOnly = ['subject_name', 'default_assignee_name'];
readOnly.forEach((attr) => delete json[attr]);
clearUnsetPolymorphicRelationships(snapshot, json, ['subject', 'default_assignee']);
return json;
}

Expand All @@ -44,7 +46,9 @@ export default class MaintenanceScheduleSerializer extends ApplicationSerializer
let key = relationship.key;
let belongsTo = snapshot.belongsTo(key);

const isPolymorphicTypeBlank = isBlank(snapshot.attr(key + '_type'));
// Read the type through attributes(): these models declare no `<key>_type`
// attribute, and snapshot.attr() asserts on an undeclared one.
const isPolymorphicTypeBlank = isBlank(snapshot.attributes()[key + '_type']);
if (isPolymorphicTypeBlank) {
key = this.keyForAttribute ? this.keyForAttribute(key, 'serialize') : key;
if (!belongsTo) {
Expand Down
6 changes: 5 additions & 1 deletion addon/serializers/maintenance.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
import { isBlank } from '@ember/utils';
import clearUnsetPolymorphicRelationships from '../utils/clear-unset-polymorphic-relationships';

export default class MaintenanceSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
/**
Expand Down Expand Up @@ -28,6 +29,7 @@ export default class MaintenanceSerializer extends ApplicationSerializer.extend(
const json = super.serialize(snapshot, options);
const readOnly = ['maintainable_name', 'performed_by_name', 'work_order_subject', 'duration_hours', 'is_overdue', 'days_until_due', 'cost_breakdown'];
readOnly.forEach((attr) => delete json[attr]);
clearUnsetPolymorphicRelationships(snapshot, json, ['maintainable', 'performed_by']);
return json;
}

Expand All @@ -46,7 +48,9 @@ export default class MaintenanceSerializer extends ApplicationSerializer.extend(
let key = relationship.key;
let belongsTo = snapshot.belongsTo(key);

const isPolymorphicTypeBlank = isBlank(snapshot.attr(key + '_type'));
// Read the type through attributes(): these models declare no `<key>_type`
// attribute, and snapshot.attr() asserts on an undeclared one.
const isPolymorphicTypeBlank = isBlank(snapshot.attributes()[key + '_type']);
if (isPolymorphicTypeBlank) {
key = this.keyForAttribute ? this.keyForAttribute(key, 'serialize') : key;
if (!belongsTo) {
Expand Down
6 changes: 5 additions & 1 deletion addon/serializers/work-order.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
import { isBlank } from '@ember/utils';
import clearUnsetPolymorphicRelationships from '../utils/clear-unset-polymorphic-relationships';

export default class WorkOrderSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
/**
Expand All @@ -27,6 +28,7 @@ export default class WorkOrderSerializer extends ApplicationSerializer.extend(Em
const json = super.serialize(snapshot, options);
const readOnly = ['target_name', 'assignee_name', 'is_overdue', 'days_until_due', 'completion_percentage'];
readOnly.forEach((attr) => delete json[attr]);
clearUnsetPolymorphicRelationships(snapshot, json, ['target', 'assignee']);
return json;
}

Expand All @@ -45,7 +47,9 @@ export default class WorkOrderSerializer extends ApplicationSerializer.extend(Em
let key = relationship.key;
let belongsTo = snapshot.belongsTo(key);

const isPolymorphicTypeBlank = isBlank(snapshot.attr(key + '_type'));
// Read the type through attributes(): these models declare no `<key>_type`
// attribute, and snapshot.attr() asserts on an undeclared one.
const isPolymorphicTypeBlank = isBlank(snapshot.attributes()[key + '_type']);
if (isPolymorphicTypeBlank) {
key = this.keyForAttribute ? this.keyForAttribute(key, 'serialize') : key;
if (!belongsTo) {
Expand Down
23 changes: 23 additions & 0 deletions addon/utils/clear-unset-polymorphic-relationships.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Send each unset polymorphic relationship as cleared.
*
* Ember Data only calls `serializePolymorphicType` for a related record that is
* present, so a removed relationship otherwise reaches the server as `null`
* embedded data while its `<key>_uuid` and `<key>_type` columns keep pointing
* at the old record.
*
* @param {Snapshot} snapshot
* @param {Object} json the serialized payload, changed in place
* @param {Array<String>} keys the polymorphic relationship names
* @return {Object} json
*/
export default function clearUnsetPolymorphicRelationships(snapshot, json, keys) {
for (const key of keys) {
if (!snapshot.belongsTo(key)) {
json[`${key}_uuid`] = null;
json[`${key}_type`] = null;
}
}

return json;
}
1 change: 1 addition & 0 deletions app/utils/clear-unset-polymorphic-relationships.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/fleetops-data/utils/clear-unset-polymorphic-relationships';
3 changes: 2 additions & 1 deletion tests/helpers/polymorphic-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
* Build the smallest snapshot `serializePolymorphicType` actually reads.
*
* @param {Object} [options]
* @param {Object} [options.attrs] values `snapshot.attr(key)` should return
* @param {Object} [options.attrs] values `snapshot.attr(key)` and `snapshot.attributes()` should return
* @param {Object|null} [options.belongsTo] value `snapshot.belongsTo(key)` should return
* @return {Object}
*/
export function snapshotStub({ attrs = {}, belongsTo = null } = {}) {
return {
attr: (key) => attrs[key],
attributes: () => attrs,
belongsTo: () => belongsTo,
};
}
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/serializers/maintenance-schedule-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,14 @@ module('Unit | Serializer | maintenance schedule', function (hooks) {
assert.strictEqual(normalized.data.relationships.default_assignee.data.type, 'facilitator-driver');
assert.strictEqual(normalized.data.relationships.default_assignee.data.id, 'driver-1');
});

test('a record whose model declares no polymorphic type attributes serializes without asserting', function (assert) {
const record = this.store.createRecord('maintenance-schedule', { subject: this.store.createRecord('maintenance-subject-vehicle', { id: 'subject_1' }) });

const json = record.serialize();

assert.strictEqual(json.subject_type, 'fleet-ops:vehicle', 'the type is derived from the related record');
assert.strictEqual(json.default_assignee_uuid, null, 'an unset default_assignee clears its uuid');
assert.strictEqual(json.default_assignee_type, null, 'an unset default_assignee clears its type');
});
});
10 changes: 10 additions & 0 deletions tests/unit/serializers/maintenance-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,14 @@ module('Unit | Serializer | maintenance', function (hooks) {

assert.strictEqual(json['maintainable_type'], 'fleet-ops:vendor', 'the raw key still produces a usable type field');
});

test('a record whose model declares no polymorphic type attributes serializes without asserting', function (assert) {
const record = this.store.createRecord('maintenance', { maintainable: this.store.createRecord('maintenance-subject-vehicle', { id: 'subject_1' }) });

const json = record.serialize();

assert.strictEqual(json.maintainable_type, 'fleet-ops:vehicle', 'the type is derived from the related record');
assert.strictEqual(json.performed_by_uuid, null, 'an unset performed_by clears its uuid');
assert.strictEqual(json.performed_by_type, null, 'an unset performed_by clears its type');
});
});
10 changes: 10 additions & 0 deletions tests/unit/serializers/work-order-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,14 @@ module('Unit | Serializer | work order', function (hooks) {

assert.strictEqual(json['target_type'], 'fleet-ops:vendor', 'the raw key still produces a usable type field');
});

test('a record whose model declares no polymorphic type attributes serializes without asserting', function (assert) {
const record = this.store.createRecord('work-order', { target: this.store.createRecord('maintenance-subject-vehicle', { id: 'subject_1' }) });

const json = record.serialize();

assert.strictEqual(json.target_type, 'fleet-ops:vehicle', 'the type is derived from the related record');
assert.strictEqual(json.assignee_uuid, null, 'an unset assignee clears its uuid');
assert.strictEqual(json.assignee_type, null, 'an unset assignee clears its type');
});
});
30 changes: 30 additions & 0 deletions tests/unit/utils/clear-unset-polymorphic-relationships-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import clearUnsetPolymorphicRelationships from '@fleetbase/fleetops-data/utils/clear-unset-polymorphic-relationships';
import { module, test } from 'qunit';

/**
* A removed polymorphic relationship has to reach the server as cleared
* columns; a relationship that is still set must be left to the type hook.
*/
module('Unit | Utility | clear-unset-polymorphic-relationships', function () {
const snapshot = (related) => ({ belongsTo: (key) => related[key] ?? null });

test('an unset relationship is sent with both its uuid and type cleared', function (assert) {
const json = { target: null };

const result = clearUnsetPolymorphicRelationships(snapshot({}), json, ['target']);

assert.strictEqual(result, json, 'the payload is changed in place and returned');
assert.strictEqual(json.target_uuid, null);
assert.strictEqual(json.target_type, null);
});

test('a relationship that is set is left untouched', function (assert) {
const json = { assignee_uuid: 'vendor_1', assignee_type: 'fleet-ops:vendor' };

clearUnsetPolymorphicRelationships(snapshot({ assignee: { id: 'vendor_1' } }), json, ['assignee', 'target']);

assert.strictEqual(json.assignee_uuid, 'vendor_1');
assert.strictEqual(json.assignee_type, 'fleet-ops:vendor');
assert.strictEqual(json.target_uuid, null, 'the unset sibling is still cleared');
});
});
Loading