Skip to content

Commit

Permalink
FEAT: Explicit Polymorphic Relationship Support [emberjs/rfcs#793] (#…
Browse files Browse the repository at this point in the history
…7955)

* feat: explicit relationship polymorphism

* all tests passing but a few more to write

* stash

* cleanup tests and features

* add all the tests

* fix lint

* fix lint
  • Loading branch information
runspired authored Sep 2, 2022
1 parent 026e5d0 commit a03a130
Show file tree
Hide file tree
Showing 55 changed files with 2,330 additions and 1,474 deletions.
7 changes: 7 additions & 0 deletions ember-data-types/q/record-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export interface ChangedAttributesHash {
[key: string]: [string, string];
}

export interface MergeOperation {
op: 'mergeIdentifiers';
record: StableRecordIdentifier; // existing
value: StableRecordIdentifier; // new
}

export interface RecordDataV1 {
version?: '1';

Expand Down Expand Up @@ -74,6 +80,7 @@ export interface RecordData {
commitWasRejected(identifier: StableRecordIdentifier, errors?: JsonApiValidationError[]): void;

unloadRecord(identifier: StableRecordIdentifier): void;
sync(op: MergeOperation): void;

// Attrs
// =====
Expand Down
7 changes: 7 additions & 0 deletions packages/-ember-data/ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ module.exports = function (defaults) {
includeExternalHelpers: true,
},
'ember-cli-terser': terserSettings,
'@embroider/macros': {
setConfig: {
'@ember-data/store': {
polyfillUUID: true,
},
},
},
});

/*
Expand Down
1 change: 1 addition & 0 deletions packages/-ember-data/node-tests/fixtures/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ module.exports = {
'(public) @ember-data/store RecordArray#save',
'(public) @ember-data/store RecordArray#type',
'(public) @ember-data/store RecordArray#update',
"(public) @ember-data/store RecordDataManager#sync",
"(public) @ember-data/store RecordDataManager#addToHasMany",
"(public) @ember-data/store RecordDataManager#changedAttributes",
"(public) @ember-data/store RecordDataManager#changedAttrs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ module('Deprecations', function (hooks) {

const StaticModelMethods = [
{ name: 'typeForRelationship', count: 3 },
{ name: 'inverseFor', count: 6 },
{ name: '_findInverseFor', count: 4 },
{ name: 'inverseFor', count: 5 },
{ name: '_findInverseFor', count: 3 },
{ name: 'eachRelationship', count: 3 },
{ name: 'eachRelatedType', count: 3 },
{ name: 'determineRelationshipType', count: 1 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,33 @@ module('integration/adapter/json-api-adapter - JSONAPIAdapter', function (hooks)
}

class Handle extends Model {
@belongsTo('user', { async: true, inverse: 'handles' }) user;
@belongsTo('user', { async: true, inverse: 'handles', as: 'handle' }) user;
}

class GithubHandle extends Handle {
class GithubHandle extends Model {
@attr('string') username;
@belongsTo('user', { async: true, inverse: 'handles', as: 'handle' }) user;
}

class TwitterHandle extends Handle {
class TwitterHandle extends Model {
@attr('string') nickname;
@belongsTo('user', { async: true, inverse: 'handles', as: 'handle' }) user;
}

class Company extends Model {
@attr('string') name;
@hasMany('user', { async: true, inverse: 'company' }) employees;
@hasMany('user', { async: true, inverse: 'company', as: 'company' }) employees;
}

class DevelopmentShop extends Company {
class DevelopmentShop extends Model {
@attr('boolean') coffee;
@attr('string') name;
@hasMany('user', { async: true, inverse: 'company', as: 'company' }) employees;
}

class DesignStudio extends Company {
class DesignStudio extends Model {
@attr('number') hipsters;
@attr('string') name;
@hasMany('user', { async: true, inverse: 'company', as: 'company' }) employees;
}

this.owner.register('adapter:application', class extends JSONAPIAdapter {});
Expand Down Expand Up @@ -768,6 +773,14 @@ module('integration/adapter/json-api-adapter - JSONAPIAdapter', function (hooks)
});

test('create record', async function (assert) {
this.owner.register(
'serializer:application',
class extends JSONAPISerializer {
shouldSerializeHasMany() {
return true;
}
}
);
assert.expect(3);

ajaxResponse([
Expand Down Expand Up @@ -798,6 +811,15 @@ module('integration/adapter/json-api-adapter - JSONAPIAdapter', function (hooks)
},
},
});
let twitterHandle = store.push({
data: {
type: 'twitter-handle',
id: '2',
attributes: {
nickname: 'wycats',
},
},
});

let user = store.createRecord('user', {
firstName: 'Yehuda',
Expand All @@ -808,6 +830,7 @@ module('integration/adapter/json-api-adapter - JSONAPIAdapter', function (hooks)
let handles = await user.handles;

handles.push(githubHandle);
handles.push(twitterHandle);

await user.save();

Expand All @@ -827,6 +850,12 @@ module('integration/adapter/json-api-adapter - JSONAPIAdapter', function (hooks)
company: {
data: { type: 'companies', id: '1' },
},
handles: {
data: [
{ type: 'github-handles', id: '2' },
{ type: 'twitter-handles', id: '2' },
],
},
},
},
},
Expand All @@ -836,6 +865,15 @@ module('integration/adapter/json-api-adapter - JSONAPIAdapter', function (hooks)
test('update record', async function (assert) {
assert.expect(3);

this.owner.register(
'serializer:application',
class extends JSONAPISerializer {
shouldSerializeHasMany() {
return true;
}
}
);

ajaxResponse([
{
data: {
Expand Down Expand Up @@ -901,6 +939,9 @@ module('integration/adapter/json-api-adapter - JSONAPIAdapter', function (hooks)
company: {
data: { type: 'companies', id: '2' },
},
handles: {
data: [{ type: 'github-handles', id: '3' }],
},
},
},
},
Expand Down
8 changes: 3 additions & 5 deletions packages/-ember-data/tests/integration/inverse-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function stringify(string) {
};
}

module('integration/inverse_test - inverseFor', function (hooks) {
module('integration/inverse-test - inverseFor', function (hooks) {
setupTest(hooks);
let store;

Expand Down Expand Up @@ -300,16 +300,14 @@ module('integration/inverse_test - inverseFor', function (hooks) {

//Maybe store is evaluated lazily, so we need this :(
assert.expectWarning(() => {
var reflexiveModel;
store.push({
const reflexiveModel = store.push({
data: {
type: 'reflexive-model',
id: '1',
},
});
reflexiveModel = store.peekRecord('reflexive-model', 1);
reflexiveModel.reflexiveProp;
}, /Detected a reflexive relationship by the name of 'reflexiveProp'/);
}, /Detected a reflexive relationship named 'reflexiveProp' on the schema for 'reflexive-model' without an inverse option/);
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Store, { recordIdentifierFor } from '@ember-data/store';
import { DSModel } from '@ember-data/types/q/ds-model';
import { CollectionResourceRelationship, SingleResourceRelationship } from '@ember-data/types/q/ember-data-json-api';
import type { NewRecordIdentifier, RecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
import type { ChangedAttributesHash, RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
import type { ChangedAttributesHash, MergeOperation, RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
import type { JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
import type { RecordDataStoreWrapper } from '@ember-data/types/q/record-data-store-wrapper';
import { Dict } from '@ember-data/types/q/utils';
Expand All @@ -26,6 +26,9 @@ if (!DEPRECATE_V1_RECORD_DATA) {
}

class TestRecordData implements RecordData {
sync(op: MergeOperation): void {
throw new Error('Method not implemented.');
}
update(operation: LocalRelationshipOperation): void {
throw new Error('Method not implemented.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import JSONAPISerializer from '@ember-data/serializer/json-api';
import Store, { recordIdentifierFor } from '@ember-data/store';
import { CollectionResourceRelationship, SingleResourceRelationship } from '@ember-data/types/q/ember-data-json-api';
import type { NewRecordIdentifier, RecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
import type { ChangedAttributesHash, RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
import type { ChangedAttributesHash, MergeOperation, RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
import type { JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
import { RecordDataStoreWrapper } from '@ember-data/types/q/record-data-store-wrapper';
import { Dict } from '@ember-data/types/q/utils';
Expand Down Expand Up @@ -115,6 +115,9 @@ class V1TestRecordData implements RecordDataV1 {
}
}
class V2TestRecordData implements RecordData {
sync(op: MergeOperation): void {
throw new Error('Method not implemented.');
}
update(operation: LocalRelationshipOperation): void {
throw new Error('Method not implemented.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
SingleResourceRelationship,
} from '@ember-data/types/q/ember-data-json-api';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
import type { ChangedAttributesHash, RecordData } from '@ember-data/types/q/record-data';
import type { ChangedAttributesHash, MergeOperation, RecordData } from '@ember-data/types/q/record-data';
import type { JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
import type { RecordDataStoreWrapper } from '@ember-data/types/q/record-data-store-wrapper';
import type { Dict } from '@ember-data/types/q/utils';
Expand Down Expand Up @@ -119,6 +119,9 @@ class V2TestRecordData implements RecordData {
this._storeWrapper = wrapper;
this._identifier = identifier;
}
sync(op: MergeOperation): void {
throw new Error('Method not implemented.');
}
pushData(
identifier: StableRecordIdentifier,
data: JsonApiResource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,20 @@ module('integration/references/belongs-to', function (hooks) {
}, "The 'person' type does not implement 'family' and thus cannot be assigned to the 'family' relationship in 'person'. Make it a descendant of 'family' or use a mixin of the same name.");
});

testInDebug('push(object) works with polymorphic modelClass', async function (assert) {
let store = this.owner.lookup('service:store');
let Family = store.modelFor('family');
testInDebug('push(object) works with polymorphic types', async function (assert) {
const Family = Model.extend({
persons: hasMany('person', { async: true, inverse: 'family', as: 'family' }),
name: attr(),
});

const Person = Model.extend({
family: belongsTo('family', { async: true, inverse: 'persons', polymorphic: true }),
});

this.owner.register('model:family', Family);
this.owner.register('model:person', Person);
this.owner.register('model:mafia-family', Family.extend());
let store = this.owner.lookup('service:store');

let person = store.push({
data: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { get } from '@ember/object';
import { module, test } from 'qunit';
import { defer, resolve } from 'rsvp';

import DS from 'ember-data';
import { setupTest } from 'ember-qunit';

import JSONAPIAdapter from '@ember-data/adapter/json-api';
import Model, { attr } from '@ember-data/model';
import JSONAPISerializer from '@ember-data/serializer/json-api';
import { recordIdentifierFor } from '@ember-data/store';

module('integration/references/record', function (hooks) {
setupTest(hooks);

hooks.beforeEach(function () {
const Person = DS.Model.extend({
name: DS.attr(),
const Person = Model.extend({
name: attr(),
});

this.owner.register('model:person', Person);
Expand Down
Loading

0 comments on commit a03a130

Please sign in to comment.