diff --git a/packages/-ember-data/tests/deprecations/deprecate-early-static-test.js b/packages/-ember-data/tests/deprecations/deprecate-early-static-test.js index 2fc8135dd64..661c9a57b1e 100644 --- a/packages/-ember-data/tests/deprecations/deprecate-early-static-test.js +++ b/packages/-ember-data/tests/deprecations/deprecate-early-static-test.js @@ -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 }, diff --git a/packages/-ember-data/tests/integration/adapter/json-api-adapter-test.js b/packages/-ember-data/tests/integration/adapter/json-api-adapter-test.js index a11318e6dbf..6ff354b1eb5 100644 --- a/packages/-ember-data/tests/integration/adapter/json-api-adapter-test.js +++ b/packages/-ember-data/tests/integration/adapter/json-api-adapter-test.js @@ -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 {}); @@ -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([ @@ -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', @@ -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(); @@ -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' }, + ], + }, }, }, }, @@ -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: { @@ -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' }], + }, }, }, }, diff --git a/packages/-ember-data/tests/integration/references/record-test.js b/packages/-ember-data/tests/integration/references/record-test.js index 339a39d46ef..2aa90c274ed 100644 --- a/packages/-ember-data/tests/integration/references/record-test.js +++ b/packages/-ember-data/tests/integration/references/record-test.js @@ -3,10 +3,10 @@ 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'; @@ -14,8 +14,8 @@ 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); diff --git a/packages/-ember-data/tests/integration/relationships/belongs-to-test.js b/packages/-ember-data/tests/integration/relationships/belongs-to-test.js index 8662c8713ad..8295f530856 100644 --- a/packages/-ember-data/tests/integration/relationships/belongs-to-test.js +++ b/packages/-ember-data/tests/integration/relationships/belongs-to-test.js @@ -277,16 +277,20 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function } class Message extends Model { - @belongsTo('user', { inverse: 'messages', async: false }) user; + @belongsTo('user', { inverse: 'messages', async: false, as: 'message' }) user; @attr('date') created_at; } - class Comment extends Message { + class Comment extends Model { @attr('string') body; + @attr('date') created_at; + @belongsTo('user', { inverse: 'messages', async: false, as: 'message' }) user; @belongsTo('message', { polymorphic: true, async: false, inverse: null }) message; } - class Post extends Message { + class Post extends Model { @attr('string') title; + @attr('date') created_at; + @belongsTo('user', { inverse: 'messages', async: false, as: 'message' }) user; @hasMany('comment', { async: false, inverse: null }) comments; } @@ -296,23 +300,23 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function @hasMany('chapter', { async: false, inverse: 'book' }) chapters; } - const Book1 = Model.extend({ - name: attr('string'), - }); + class Book1 extends Model { + @attr name; + } class Chapter extends Model { @attr title; @belongsTo('book', { async: false, inverse: 'chapters' }) book; } - const Author = Model.extend({ - name: attr('string'), - books: hasMany('book', { async: false, inverse: 'author' }), - }); + class Author extends Model { + @attr name; + @hasMany('book', { async: false, inverse: 'author' }) books; + } - const Section = Model.extend({ - name: attr('string'), - }); + class Section extends Model { + @attr name; + } this.owner.register('model:user', User); this.owner.register('model:post', Post); @@ -428,16 +432,12 @@ module('integration/relationship/belongs_to Belongs-To Relationships', function test('The store can materialize a non loaded monomorphic belongsTo association', function (assert) { assert.expect(1); - class Message extends Model { - @belongsTo('user', { inverse: 'messages', async: false }) user; - @attr('date') created_at; - } - class Post extends Message { + + class Post extends Model { @attr('string') title; @hasMany('comment', { async: false, inverse: null }) comments; - @belongsTo('user', { async: true, inverse: 'messages' }) user; + @belongsTo('user', { async: true, inverse: 'messages', as: 'message' }) user; } - this.owner.register('model:message', Message); this.owner.register('model:post', Post); let store = this.owner.lookup('service:store'); diff --git a/packages/-ember-data/tests/integration/relationships/explicit-polymorphism-test.js b/packages/-ember-data/tests/integration/relationships/explicit-polymorphism-test.js new file mode 100644 index 00000000000..2616041c416 --- /dev/null +++ b/packages/-ember-data/tests/integration/relationships/explicit-polymorphism-test.js @@ -0,0 +1,192 @@ +import { module, test } from 'qunit'; + +import { setupTest } from 'ember-qunit'; + +import Model, { attr, belongsTo } from '@ember-data/model'; +import { recordIdentifierFor } from '@ember-data/store'; + +class FrameworkClass { + constructor(args) { + Object.assign(this, args); + } + + static create(args) { + return new this(args); + } +} + +module('Integration | Relationships | Explicit Polymorphism', function (hooks) { + setupTest(hooks); + + test('We can fetch a polymorphic belongsTo relationship with a null inverse', async function (assert) { + const { owner } = this; + const store = owner.lookup('service:store'); + + owner.register( + 'model:tag', + class extends Model { + @attr name; + @belongsTo('taggable', { async: false, inverse: null, polymorphic: true }) tagged; + } + ); + owner.register( + 'model:comment', + class extends Model { + @attr name; + } + ); + owner.register( + 'model:post', + class extends Model { + @attr name; + } + ); + + owner.register( + 'adapter:application', + class extends FrameworkClass { + findRecord(store, schema, id, snapshot) { + return { + data: { + type: 'comment', + id, + attributes: { + name: 'My Comment', + }, + }, + }; + } + } + ); + owner.register( + 'serializer:application', + class extends FrameworkClass { + normalizeResponse(_, __, data) { + return data; + } + } + ); + + const tag = store.push({ + data: { + type: 'tag', + id: '1', + attributes: { name: 'My Tag' }, + relationships: { + tagged: { + // we expect the store to not error on push for this unknown model name + data: { type: 'comment', id: '1' }, + }, + }, + }, + }); + + assert.strictEqual(tag.name, 'My Tag', 'We pushed the Tag'); + assert.strictEqual(tag.belongsTo('tagged').id(), '1', 'we have the data for the relationship'); + + await store.findRecord('comment', '1'); + + assert.strictEqual(tag.tagged.id, '1', 'we have the loaded comment'); + assert.strictEqual(tag.tagged.name, 'My Comment', 'Comment is correct'); + assert.strictEqual(tag.tagged.constructor.modelName, 'comment', 'model name is correct'); + const identifier = recordIdentifierFor(tag.tagged); + assert.strictEqual(identifier.type, 'comment', 'identifier type is correct'); + + // update the value + const post = store.push({ + data: { + type: 'post', + id: '1', + attributes: { name: 'My Post' }, + }, + }); + tag.tagged = post; + assert.strictEqual(tag.tagged.id, '1', 'we have the loaded post'); + assert.strictEqual(tag.tagged.name, 'My Post', 'Post is correct'); + assert.strictEqual(tag.tagged.constructor.modelName, 'post', 'model name is correct'); + const identifier2 = recordIdentifierFor(tag.tagged); + assert.strictEqual(identifier2.type, 'post', 'identifier type is correct'); + }); + + test('We can fetch a polymorphic belongsTo relationship with a specified inverse', async function (assert) { + const { owner } = this; + const store = owner.lookup('service:store'); + + owner.register( + 'model:tag', + class extends Model { + @attr name; + @belongsTo('taggable', { async: false, inverse: 'tag', polymorphic: true }) tagged; + } + ); + owner.register( + 'model:comment', + class extends Model { + @attr name; + @belongsTo('tag', { async: false, inverse: 'tagged', as: 'taggable' }) tag; + } + ); + owner.register( + 'model:post', + class extends Model { + @attr name; + @belongsTo('tag', { async: false, inverse: 'tagged', as: 'taggable' }) tag; + } + ); + + owner.register( + 'adapter:application', + class extends FrameworkClass { + findRecord(store, schema, id, snapshot) { + return { + data: { + type: 'comment', + id, + attributes: { + name: 'My Comment', + }, + }, + }; + } + } + ); + owner.register( + 'serializer:application', + class extends FrameworkClass { + normalizeResponse(_, __, data) { + return data; + } + } + ); + + const post = store.push({ + data: { + type: 'post', + id: '1', + attributes: { name: 'My Post' }, + relationships: { + tag: { + // we expect the store to not error on push for this unknown model name + data: { type: 'tag', id: '1' }, + }, + }, + }, + included: [ + { + type: 'tag', + id: '1', + attributes: { name: 'My Tag' }, + relationships: { + tagged: { + // we expect the store to not error on push for this unknown model name + data: { type: 'post', id: '1' }, + }, + }, + }, + ], + }); + const tag = store.peekRecord('tag', '1'); + + assert.strictEqual(post.tag, tag, 'we have a tag'); + }); +}); diff --git a/packages/-ember-data/tests/integration/relationships/has-many-test.js b/packages/-ember-data/tests/integration/relationships/has-many-test.js index 8158ec2d12f..6352d1fc1e0 100644 --- a/packages/-ember-data/tests/integration/relationships/has-many-test.js +++ b/packages/-ember-data/tests/integration/relationships/has-many-test.js @@ -34,34 +34,39 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( @belongsTo('user', { async: false, inverse: null }) user; } - class Email extends Contact { + class Email extends Model { + @belongsTo('user', { async: false, inverse: null }) user; @attr email; } - class Phone extends Contact { + class Phone extends Model { + @belongsTo('user', { async: false, inverse: null }) user; @attr number; } class Message extends Model { @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; + @belongsTo('user', { async: false, inverse: 'messages', as: 'message' }) user; } - class Post extends Message { + class Post extends Model { + @attr('date') created_at; + @belongsTo('user', { async: false, inverse: 'messages', as: 'message' }) user; @attr title; - @hasMany('comment', { async: false, inverse: 'message' }) comments; + @hasMany('comment', { async: false, inverse: 'message', as: 'post' }) comments; } - class Comment extends Message { + class Comment extends Model { + @attr('date') created_at; + @belongsTo('user', { async: false, inverse: 'messages', as: 'message' }) user; @attr body; @belongsTo('post', { polymorphic: true, async: true, inverse: 'comments' }) message; } - const Book = Model.extend({ - title: attr(), - chapters: hasMany('chapter', { async: true, inverse: null }), - toString: () => 'Book', - }); + class Book extends Model { + @attr title; + @hasMany('chapter', { async: true, inverse: null }) chapters; + } class Chapter extends Model { @attr title; @@ -474,23 +479,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( // common use case of this is to provide a URL to a collection that // is loaded later. test("A serializer can materialize a hasMany as an opaque token that can be lazily fetched via the adapter's findHasMany hook", function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { polymorphic: true, async: true, inverse: 'comments' }) message; + @belongsTo('post', { async: true, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -549,23 +548,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Accessing a hasMany backed by a link multiple times triggers only one request', async function (assert) { assert.expect(2); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: true, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -624,23 +617,18 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('A hasMany backed by a link remains a promise after a record has been added to it', function (assert) { assert.expect(1); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: true, inverse: 'comments' }) message; } + this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -694,23 +682,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('A hasMany updated link should not remove new children', function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: true, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -750,23 +732,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('A hasMany updated link should not remove new children when the parent record has children already', function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: true, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -808,23 +784,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test("A hasMany relationship doesn't contain duplicate children, after the canonical state of the relationship is updated via store#push", function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: true, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -881,23 +851,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('A hasMany relationship can be reloaded if it was fetched via a link', async function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1025,23 +989,18 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('A hasMany relationship can be reloaded if it was fetched via ids', function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } + this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1104,23 +1063,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('A hasMany relationship can be reloaded even if it failed at the first time', async function (assert) { assert.expect(7); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1186,23 +1139,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('A hasMany relationship can be directly reloaded if it was fetched via links', function (assert) { assert.expect(6); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1247,23 +1194,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Has many via links - Calling reload multiple times does not send a new request if the first one is not settled', function (assert) { assert.expect(1); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let done = assert.async(); @@ -1311,23 +1252,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('A hasMany relationship can be directly reloaded if it was fetched via ids', function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1374,23 +1309,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Has many via ids - Calling reload multiple times does not send a new request if the first one is not settled', function (assert) { assert.expect(1); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let done = assert.async(); @@ -1446,23 +1375,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 1 }, function (assert) { assert.expect(4); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1509,23 +1432,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('An updated `links` value should invalidate a relationship cache', async function (assert) { assert.expect(8); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -1887,20 +1804,18 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Polymorphic relationships with a hasMany is set up correctly on both sides', function (assert) { assert.expect(2); class Contact extends Model { - @belongsTo('user', { async: false, inverse: null }) user; - @hasMany('post', { async: false, inverse: 'contact' }) posts; + @hasMany('post', { async: false, inverse: 'contact', as: 'contact' }) posts; } - class Email extends Contact { + class Email extends Model { @attr email; + @hasMany('post', { async: false, inverse: 'contact', as: 'contact' }) posts; } - class Phone extends Contact { + + class Phone extends Model { @attr number; + @hasMany('post', { async: false, inverse: 'contact', as: 'contact' }) posts; } - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: false, inverse: 'message' }) comments; @belongsTo('contact', { async: false, polymorphic: true, inverse: 'posts' }) contact; @@ -1908,7 +1823,6 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( this.owner.register('model:contact', Contact); this.owner.register('model:email', Email); this.owner.register('model:phone', Phone); - this.owner.register('model:message', Message); this.owner.register('model:post', Post); let store = this.owner.lookup('service:store'); @@ -1919,7 +1833,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); assert.strictEqual(post.contact, email, 'The polymorphic belongsTo is set up correctly'); - assert.strictEqual(get(email, 'posts.length'), 1, 'The inverse has many is set up correctly on the email side.'); + assert.strictEqual(email.posts.length, 1, 'The inverse has many is set up correctly on the email side.'); }); testInDebug('Only records of the same type can be added to a monomorphic hasMany relationship', function (assert) { @@ -2093,23 +2007,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('When a record is created on the client, its async hasMany arrays should be in a loaded state', function (assert) { assert.expect(4); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let post = store.createRecord('post'); @@ -2158,23 +2066,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('We can set records ASYNC HM relationship', function (assert) { assert.expect(1); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let post = store.createRecord('post'); @@ -2228,23 +2130,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('dual non-async HM <-> BT', async function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: false, inverse: 'post' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: false, inverse: 'comments' }) post; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -2294,23 +2190,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('When an unloaded record is added to the hasMany, it gets fetched once the hasMany is accessed even if the hasMany has been already fetched', async function (assert) { assert.expect(6); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -2415,12 +2305,18 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( assert.ok(false, 'no request should be made'); }; - const Post = Model.extend({ - title: attr('string'), - comments: hasMany('comment', { async: true, inverse: 'message' }), - toString: () => 'Post', - }); + class Post extends Model { + @attr title; + @hasMany('comment', { async: true, inverse: 'message' }) comments; + } + + class Comment extends Model { + @attr body; + @belongsTo('post', { async: false, inverse: 'comments' }) message; + } + this.owner.register('model:post', Post); + this.owner.register('model:comment', Comment); let comment = store.createRecord('comment'); let post = store.push({ @@ -2787,23 +2683,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('Relationship.clear removes all records correctly', async function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: false, inverse: 'post' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: false, inverse: 'comments' }) post; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); @@ -2876,23 +2766,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('unloading a record with associated records does not prevent the store from tearing down', function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: false, inverse: 'post' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: false, inverse: 'comments' }) post; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); @@ -3417,23 +3301,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Related link should be fetched when no relationship data is present', function (assert) { assert.expect(3); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'post' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: false, inverse: 'comments' }) post; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -3487,23 +3365,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Related link should take precedence over relationship data when local record data is missing', function (assert) { assert.expect(3); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'post' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: false, inverse: 'comments' }) post; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -3558,23 +3430,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Local relationship data should take precedence over related link when local record data is available', function (assert) { assert.expect(1); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'post' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: false, inverse: 'comments' }) post; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -3626,23 +3492,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Related link should take precedence over local record data when relationship data is not initially available', function (assert) { assert.expect(3); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'post' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: false, inverse: 'comments' }) post; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -3713,23 +3573,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('Updated related link should take precedence over relationship data and local record data', function (assert) { assert.expect(3); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -3787,23 +3641,16 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( { id: 'ember-data:deprecate-promise-many-array-behaviors', until: '5.0', count: 1 }, function (assert) { assert.expect(1); - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - - class Comment extends Message { + class Comment extends Model { @attr body; - @belongsTo('post', { async: false, polymorphic: true, inverse: 'comments' }) message; + @belongsTo('post', { async: false, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); @@ -3844,22 +3691,16 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( test('deleteRecord + unloadRecord', async function (assert) { class User extends Model { @attr name; - @hasMany('message', { polymorphic: true, async: false, inverse: 'user' }) messages; @hasMany('user', { inverse: null, async: false }) contacts; @hasMany('post', { async: true, inverse: null }) posts; } - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: false, inverse: 'message' }) comments; @belongsTo('user', { inverse: null, async: false }) user; } this.owner.register('model:user', User); - this.owner.register('model:message', Message); this.owner.register('model:post', Post); let store = this.owner.lookup('service:store'); @@ -4096,23 +3937,17 @@ module('integration/relationships/has_many - Has-Many Relationships', function ( }); test('A hasMany relationship with a link will trigger the link request even if a inverse related object is pushed to the store', function (assert) { - class Message extends Model { - @attr('date') created_at; - @belongsTo('user', { async: false, inverse: 'messages' }) user; - } - - class Post extends Message { + class Post extends Model { @attr title; @hasMany('comment', { async: true, inverse: 'message' }) comments; } - class Comment extends Message { + class Comment extends Model { @attr body; @belongsTo('post', { async: true, inverse: 'comments' }) message; } this.owner.register('model:post', Post); this.owner.register('model:comment', Comment); - this.owner.register('model:message', Message); let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); diff --git a/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js b/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js index 5a2a3853c3e..9fedf156d20 100644 --- a/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js +++ b/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js @@ -312,7 +312,7 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' @belongsTo('user', { async: false }) twoUser; - @belongsTo('user', { async: false }) + @belongsTo('user', { async: false, as: 'message' }) redUser; @belongsTo('user', { async: false }) @@ -361,7 +361,7 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' } class Message extends Model { - @belongsTo('user', { inverse: 'youMessages', async: false }) + @belongsTo('user', { inverse: 'youMessages', async: false, as: 'message' }) user; } @@ -396,7 +396,7 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' @hasMany('comment', { inverse: null, async: false }) meMessages; - @hasMany('comment', { inverse: 'message', async: false }) + @hasMany('comment', { inverse: 'message', async: false, as: 'message' }) youMessages; @hasMany('comment', { inverse: null, async: false }) @@ -455,7 +455,7 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' assert.expectAssertion(function () { post = store.createRecord('post'); post.comments; - }, /We found no inverse relationships by the name of 'testPost' on the 'comment' model/); + }, /We found no field named 'testPost' on the schema for 'comment' to be the inverse of the 'comments' relationship on 'post'. This is most likely due to a missing field on your model definition./); }); testInDebug("Inverse relationships that don't exist throw a nice error for a belongsTo", async function (assert) { @@ -478,7 +478,7 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' assert.expectAssertion(function () { post = store.createRecord('post'); post.user; - }, /We found no inverse relationships by the name of 'testPost' on the 'user' model/); + }, /We found no field named 'testPost' on the schema for 'user' to be the inverse of the 'user' relationship on 'post'. This is most likely due to a missing field on your model definition./); }); test('inverseFor is only called when inverse is not null', async function (assert) { diff --git a/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-belongs-to-test.js b/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-belongs-to-test.js index b0670313eaf..4cb66d37f27 100644 --- a/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-belongs-to-test.js +++ b/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-belongs-to-test.js @@ -1,5 +1,4 @@ import Mixin from '@ember/object/mixin'; -import { run } from '@ember/runloop'; import { module, test } from 'qunit'; @@ -16,18 +15,23 @@ module( setupTest(hooks); hooks.beforeEach(function () { - const User = Model.extend({ - name: attr('string'), - bestMessage: belongsTo('message', { async: true, inverse: 'user', polymorphic: true }), - }); + class User extends Model { + @attr name; + @belongsTo('message', { async: true, inverse: 'user', polymorphic: true }) bestMessage; + } const Message = Mixin.create({ title: attr('string'), - user: belongsTo('user', { async: true, inverse: 'bestMessage' }), + user: belongsTo('user', { async: true, inverse: 'bestMessage', as: 'message' }), }); - const NotMessage = Model.extend({ video: attr() }); - const Video = Model.extend(Message, { video: attr() }); + class NotMessage extends Model { + @attr video; + } + + class Video extends Model.extend(Message) { + @attr video; + } this.owner.register('model:user', User); this.owner.register('model:video', Video); @@ -43,133 +47,77 @@ module( Server loading tests */ - test('Relationship is available from the belongsTo side even if only loaded from the inverse side - async', function (assert) { - let store = this.owner.lookup('service:store'); + test('Relationship is available from the belongsTo side even if only loaded from the inverse side - async', async function (assert) { + const store = this.owner.lookup('service:store'); - var user, video; - run(function () { - store.push({ - data: [ - { - type: 'user', - id: '1', - attributes: { - name: 'Stanley', - }, - relationships: { - bestMessage: { - data: { type: 'video', id: '2' }, - }, - }, + const [user, video] = store.push({ + data: [ + { + type: 'user', + id: '1', + attributes: { + name: 'Stanley', }, - { - type: 'video', - id: '2', - attributes: { - video: 'Here comes Youtube', + relationships: { + bestMessage: { + data: { type: 'video', id: '2' }, }, }, - ], - }); - user = store.peekRecord('user', 1); - video = store.peekRecord('video', 2); - }); - run(function () { - user.bestMessage.then(function (message) { - assert.strictEqual(message, video, 'The message was loaded correctly'); - message.user.then(function (fetchedUser) { - assert.strictEqual(fetchedUser, user, 'The inverse was setup correctly'); - }); - }); + }, + { + type: 'video', + id: '2', + attributes: { + video: 'Here comes Youtube', + }, + }, + ], }); + + const message = await user.bestMessage; + assert.strictEqual(message, video, 'The message was loaded correctly'); + const fetchedUser = await message.user; + assert.strictEqual(fetchedUser, user, 'The inverse was setup correctly'); }); /* Local edits */ - test('Setting the polymorphic belongsTo gets propagated to the inverse side - async', function (assert) { - let store = this.owner.lookup('service:store'); - - var user, video; - run(function () { - store.push({ - data: [ - { - type: 'user', - id: '1', - attributes: { - name: 'Stanley', - }, + test('Setting the polymorphic belongsTo gets propagated to the inverse side - async', async function (assert) { + const store = this.owner.lookup('service:store'); + + const [user, video] = store.push({ + data: [ + { + type: 'user', + id: '1', + attributes: { + name: 'Stanley', }, - { - type: 'video', - id: '2', - attributes: { - video: 'Here comes Youtube', - }, + }, + { + type: 'video', + id: '2', + attributes: { + video: 'Here comes Youtube', }, - ], - }); - user = store.peekRecord('user', 1); - video = store.peekRecord('video', 2); + }, + ], }); - run(function () { - user.set('bestMessage', video); - video.user.then(function (fetchedUser) { - assert.strictEqual(fetchedUser, user, 'user got set correctly'); - }); - user.bestMessage.then(function (message) { - assert.strictEqual(message, video, 'The message was set correctly'); - }); - }); + user.bestMessage = video; + const fetchedUser = await video.user; + assert.strictEqual(fetchedUser, user, 'user got set correctly'); + const message = await user.bestMessage; + assert.strictEqual(message, video, 'The message was set correctly'); }); testInDebug( 'Setting the polymorphic belongsTo with an object that does not implement the mixin errors out', function (assert) { - let store = this.owner.lookup('service:store'); - - var user, video; - run(function () { - store.push({ - data: [ - { - type: 'user', - id: '1', - attributes: { - name: 'Stanley', - }, - }, - { - type: 'not-message', - id: '2', - attributes: { - video: 'Here comes Youtube', - }, - }, - ], - }); - user = store.peekRecord('user', 1); - video = store.peekRecord('not-message', 2); - }); - - run(function () { - assert.expectAssertion(function () { - user.set('bestMessage', video); - }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'bestMessage' relationship in 'user'. Make it a descendant of 'message'/); - }); - } - ); + const store = this.owner.lookup('service:store'); - test('Setting the polymorphic belongsTo gets propagated to the inverse side - model injections true', function (assert) { - assert.expect(2); - - let store = this.owner.lookup('service:store'); - - var user, video; - run(function () { - store.push({ + const [user, video] = store.push({ data: [ { type: 'user', @@ -179,7 +127,7 @@ module( }, }, { - type: 'video', + type: 'not-message', id: '2', attributes: { video: 'Here comes Youtube', @@ -187,55 +135,10 @@ module( }, ], }); - user = store.peekRecord('user', 1); - video = store.peekRecord('video', 2); - }); - - run(function () { - user.set('bestMessage', video); - video.user.then(function (fetchedUser) { - assert.strictEqual(fetchedUser, user, 'user got set correctly'); - }); - user.bestMessage.then(function (message) { - assert.strictEqual(message, video, 'The message was set correctly'); - }); - }); - }); - testInDebug( - 'Setting the polymorphic belongsTo with an object that does not implement the mixin errors out - model injections true', - function (assert) { - let store = this.owner.lookup('service:store'); - - var user, video; - run(function () { - store.push({ - data: [ - { - type: 'user', - id: '1', - attributes: { - name: 'Stanley', - }, - }, - { - type: 'not-message', - id: '2', - attributes: { - video: 'Here comes Youtube', - }, - }, - ], - }); - user = store.peekRecord('user', 1); - video = store.peekRecord('not-message', 2); - }); - - run(function () { - assert.expectAssertion(function () { - user.set('bestMessage', video); - }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'bestMessage' relationship in 'user'. Make it a descendant of 'message'/); - }); + assert.expectAssertion(function () { + user.bestMessage = video; + }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'bestMessage' relationship in 'user'. Make it a descendant of 'message'/); } ); } diff --git a/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-has-many-test.js b/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-has-many-test.js index 5da55712e1f..d7ec6134078 100644 --- a/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-has-many-test.js +++ b/packages/-ember-data/tests/integration/relationships/polymorphic-mixins-has-many-test.js @@ -1,5 +1,4 @@ import Mixin from '@ember/object/mixin'; -import { run } from '@ember/runloop'; import { module, test } from 'qunit'; @@ -14,23 +13,24 @@ module( 'integration/relationships/polymorphic_mixins_has_many_test - Polymorphic hasMany relationships with mixins', function (hooks) { setupTest(hooks); + class User extends Model { + @attr name; + @hasMany('message', { async: true, inverse: 'user', polymorphic: true }) messages; + } - let Message; - - hooks.beforeEach(function () { - const User = Model.extend({ - name: attr('string'), - messages: hasMany('message', { async: true, inverse: 'user', polymorphic: true }), - }); - - Message = Mixin.create({ - title: attr('string'), - user: belongsTo('user', { async: true, inverse: 'messages' }), - }); + const Message = Mixin.create({ + title: attr('string'), + user: belongsTo('user', { async: true, inverse: 'messages', as: 'message' }), + }); - const Video = Model.extend(Message, { video: attr() }); - const NotMessage = Model.extend({ video: attr() }); + class Video extends Model.extend(Message) { + @attr video; + } + class NotMessage extends Model { + @attr video; + } + hooks.beforeEach(function () { this.owner.register('model:user', User); this.owner.register('model:video', Video); this.owner.register('model:not-message', NotMessage); @@ -45,102 +45,121 @@ module( Server loading tests */ - test('Relationship is available from the belongsTo side even if only loaded from the hasMany side - async', function (assert) { - let store = this.owner.lookup('service:store'); + test('Relationship is available from the belongsTo side even if only loaded from the hasMany side - async', async function (assert) { + const store = this.owner.lookup('service:store'); - var user, video; - run(function () { - store.push({ - data: [ - { - type: 'user', - id: '1', - attributes: { - name: 'Stanley', - }, - relationships: { - messages: { - data: [{ type: 'video', id: '2' }], - }, - }, + const [user, video] = store.push({ + data: [ + { + type: 'user', + id: '1', + attributes: { + name: 'Stanley', }, - { - type: 'video', - id: '2', - attributes: { - video: 'Here comes Youtube', + relationships: { + messages: { + data: [{ type: 'video', id: '2' }], }, }, - ], - }); - user = store.peekRecord('user', 1); - video = store.peekRecord('video', 2); - }); - run(function () { - user.messages.then(function (messages) { - assert.strictEqual(messages.at(0), video, 'The hasMany has loaded correctly'); - messages.at(0).user.then(function (fetchedUser) { - assert.strictEqual(fetchedUser, user, 'The inverse was setup correctly'); - }); - }); + }, + { + type: 'video', + id: '2', + attributes: { + video: 'Here comes Youtube', + }, + }, + ], }); + + const messages = await user.messages; + assert.strictEqual(messages.at(0), video, 'The hasMany has loaded correctly'); + const fetchedUser = await messages.at(0).user; + assert.strictEqual(fetchedUser, user, 'The inverse was setup correctly'); }); /* Local edits */ - test('Pushing to the hasMany reflects the change on the belongsTo side - async', function (assert) { - let store = this.owner.lookup('service:store'); + test('Pushing to the hasMany reflects the change on the belongsTo side - async', async function (assert) { + const store = this.owner.lookup('service:store'); - var user, video; - run(function () { - store.push({ - data: [ - { - type: 'user', - id: '1', - attributes: { - name: 'Stanley', - }, - relationships: { - messages: { - data: [], - }, - }, + const [user, video] = store.push({ + data: [ + { + type: 'user', + id: '1', + attributes: { + name: 'Stanley', }, - { - type: 'video', - id: '2', - attributes: { - video: 'Here comes Youtube', + relationships: { + messages: { + data: [], }, }, - ], - }); - user = store.peekRecord('user', 1); - video = store.peekRecord('video', 2); + }, + { + type: 'video', + id: '2', + attributes: { + video: 'Here comes Youtube', + }, + }, + ], }); - run(function () { - user.messages.then(function (fetchedMessages) { - fetchedMessages.push(video); - video.user.then(function (fetchedUser) { - assert.strictEqual(fetchedUser, user, 'user got set correctly'); - }); - }); - }); + const fetchedMessages = await user.messages; + fetchedMessages.push(video); + const fetchedUser = await video.user; + assert.strictEqual(fetchedUser, user, 'user got set correctly'); }); - test('NATIVE CLASSES: Pushing to the hasMany reflects the change on the belongsTo side - async', function (assert) { + test('NATIVE CLASSES: Pushing to the hasMany reflects the change on the belongsTo side - async', async function (assert) { class Video extends Model.extend(Message) {} this.owner.register('model:video', Video); - let store = this.owner.lookup('service:store'); + const store = this.owner.lookup('service:store'); - var user, video; - run(function () { - store.push({ + const [user, video] = store.push({ + data: [ + { + type: 'user', + id: '1', + attributes: { + name: 'Stanley', + }, + relationships: { + messages: { + data: [], + }, + }, + }, + { + type: 'video', + id: '2', + attributes: { + video: 'Here comes Youtube', + }, + }, + ], + }); + + const fetchedMessages = await user.messages; + fetchedMessages.push(video); + const fetchedUser = await video.user; + assert.strictEqual(fetchedUser, user, 'user got set correctly'); + }); + + /* + Local edits + */ + testInDebug( + 'Pushing a an object that does not implement the mixin to the mixin accepting array errors out', + async function (assert) { + const store = this.owner.lookup('service:store'); + + const [user, notMessage] = store.push({ data: [ { type: 'user', @@ -155,7 +174,7 @@ module( }, }, { - type: 'video', + type: 'not-message', id: '2', attributes: { video: 'Here comes Youtube', @@ -163,73 +182,56 @@ module( }, ], }); - user = store.peekRecord('user', 1); - video = store.peekRecord('video', 2); - }); - run(function () { - user.messages.then(function (fetchedMessages) { - fetchedMessages.push(video); - video.user.then(function (fetchedUser) { - assert.strictEqual(fetchedUser, user, 'user got set correctly'); - }); - }); + const fetchedMessages = await user.messages; + assert.expectAssertion(function () { + fetchedMessages.push(notMessage); + }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'messages' relationship in 'user'. Make it a descendant of 'message/); + } + ); + + test('Pushing to the hasMany reflects the change on the belongsTo side - model injections true', async function (assert) { + const store = this.owner.lookup('service:store'); + + const [user, video] = store.push({ + data: [ + { + type: 'user', + id: '1', + attributes: { + name: 'Stanley', + }, + relationships: { + messages: { + data: [], + }, + }, + }, + { + type: 'video', + id: '2', + attributes: { + video: 'Here comes Youtube', + }, + }, + ], }); + + const fetchedMessages = await user.messages; + fetchedMessages.push(video); + const fetchedUser = await video.user; + assert.strictEqual(fetchedUser, user, 'user got set correctly'); }); /* Local edits */ testInDebug( - 'Pushing a an object that does not implement the mixin to the mixin accepting array errors out', - function (assert) { - let store = this.owner.lookup('service:store'); - - var user, notMessage; - run(function () { - store.push({ - data: [ - { - type: 'user', - id: '1', - attributes: { - name: 'Stanley', - }, - relationships: { - messages: { - data: [], - }, - }, - }, - { - type: 'not-message', - id: '2', - attributes: { - video: 'Here comes Youtube', - }, - }, - ], - }); - user = store.peekRecord('user', 1); - notMessage = store.peekRecord('not-message', 2); - }); - - run(function () { - user.messages.then(function (fetchedMessages) { - assert.expectAssertion(function () { - fetchedMessages.push(notMessage); - }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'messages' relationship in 'user'. Make it a descendant of 'message/); - }); - }); - } - ); - - test('Pushing to the hasMany reflects the change on the belongsTo side - model injections true', function (assert) { - let store = this.owner.lookup('service:store'); + 'Pushing a an object that does not implement the mixin to the mixin accepting array errors out - model injections true', + async function (assert) { + const store = this.owner.lookup('service:store'); - var user, video; - run(function () { - store.push({ + const [user, notMessage] = store.push({ data: [ { type: 'user', @@ -244,7 +246,7 @@ module( }, }, { - type: 'video', + type: 'not-message', id: '2', attributes: { video: 'Here comes Youtube', @@ -252,64 +254,11 @@ module( }, ], }); - user = store.peekRecord('user', 1); - video = store.peekRecord('video', 2); - }); - - run(function () { - user.messages.then(function (fetchedMessages) { - fetchedMessages.push(video); - video.user.then(function (fetchedUser) { - assert.strictEqual(fetchedUser, user, 'user got set correctly'); - }); - }); - }); - }); - - /* - Local edits - */ - testInDebug( - 'Pushing a an object that does not implement the mixin to the mixin accepting array errors out - model injections true', - function (assert) { - let store = this.owner.lookup('service:store'); - - var user, notMessage; - run(function () { - store.push({ - data: [ - { - type: 'user', - id: '1', - attributes: { - name: 'Stanley', - }, - relationships: { - messages: { - data: [], - }, - }, - }, - { - type: 'not-message', - id: '2', - attributes: { - video: 'Here comes Youtube', - }, - }, - ], - }); - user = store.peekRecord('user', 1); - notMessage = store.peekRecord('not-message', 2); - }); - run(function () { - user.messages.then(function (fetchedMessages) { - assert.expectAssertion(function () { - fetchedMessages.push(notMessage); - }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'messages' relationship in 'user'. Make it a descendant of 'message'/); - }); - }); + const fetchedMessages = await user.messages; + assert.expectAssertion(function () { + fetchedMessages.push(notMessage); + }, /The 'not-message' type does not implement 'message' and thus cannot be assigned to the 'messages' relationship in 'user'. Make it a descendant of 'message'/); } ); } diff --git a/packages/-ember-data/tests/integration/serializers/json-api-serializer-test.js b/packages/-ember-data/tests/integration/serializers/json-api-serializer-test.js index f97796e0e41..90ae696e2c1 100644 --- a/packages/-ember-data/tests/integration/serializers/json-api-serializer-test.js +++ b/packages/-ember-data/tests/integration/serializers/json-api-serializer-test.js @@ -3,47 +3,51 @@ import { run } from '@ember/runloop'; import { module, test } from 'qunit'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; import JSONAPIAdapter from '@ember-data/adapter/json-api'; +import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; +import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest'; +import Transform from '@ember-data/serializer/transform'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; module('integration/serializers/json-api-serializer - JSONAPISerializer', function (hooks) { setupTest(hooks); - hooks.beforeEach(function () { - const User = DS.Model.extend({ - firstName: DS.attr('string'), - lastName: DS.attr('string'), - title: DS.attr('string'), - handles: DS.hasMany('handle', { async: true, polymorphic: true, inverse: 'user' }), - company: DS.belongsTo('company', { async: true, inverse: 'employees' }), - reportsTo: DS.belongsTo('user', { async: true, inverse: null }), - }); - - const Handle = DS.Model.extend({ - user: DS.belongsTo('user', { async: true, inverse: 'handles' }), - }); - - const GithubHandle = Handle.extend({ - username: DS.attr('string'), - }); - - const TwitterHandle = Handle.extend({ - nickname: DS.attr('string'), - }); - - const Company = DS.Model.extend({ - name: DS.attr('string'), - employees: DS.hasMany('user', { async: true, inverse: 'company' }), - }); - - const Project = DS.Model.extend({ - 'company-name': DS.attr('string'), - }); + class User extends Model { + @attr('string') firstName; + @attr('string') lastName; + @attr('string') title; + @hasMany('handle', { async: true, polymorphic: true, inverse: 'user' }) handles; + @belongsTo('company', { async: true, inverse: 'employees' }) company; + @belongsTo('user', { async: true, inverse: null }) reportsTo; + } + + class Handle extends Model { + @belongsTo('user', { async: true, inverse: 'handles', as: 'handle' }) user; + } + + class GithubHandle extends Model { + @attr('string') username; + @belongsTo('user', { async: true, inverse: 'handles', as: 'handle' }) user; + } + + 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; + } + + class Project extends Model { + @attr 'company-name'; + } + hooks.beforeEach(function () { this.owner.register('model:user', User); this.owner.register('model:handle', Handle); this.owner.register('model:github-handle', GithubHandle); @@ -386,14 +390,14 @@ module('integration/serializers/json-api-serializer - JSONAPISerializer', functi test('options are passed to transform for serialization', function (assert) { assert.expect(1); - const User = DS.Model.extend({ - firstName: DS.attr('string'), - lastName: DS.attr('string'), - title: DS.attr('string'), - handles: DS.hasMany('handle', { async: true, polymorphic: true, inverse: 'user' }), - company: DS.belongsTo('company', { async: true, inverse: 'employees' }), - reportsTo: DS.belongsTo('user', { async: true, inverse: null }), - myCustomField: DS.attr('custom', { + const User = Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + title: attr('string'), + handles: hasMany('handle', { async: true, polymorphic: true, inverse: 'user' }), + company: belongsTo('company', { async: true, inverse: 'employees' }), + reportsTo: belongsTo('user', { async: true, inverse: null }), + myCustomField: attr('custom', { custom: 'config', }), }); @@ -405,7 +409,7 @@ module('integration/serializers/json-api-serializer - JSONAPISerializer', functi this.owner.register( 'transform:custom', - DS.Transform.extend({ + Transform.extend({ serialize: function (deserialized, options) { assert.deepEqual(options, { custom: 'config' }, 'we have the right options'); }, @@ -723,13 +727,13 @@ module('integration/serializers/json-api-serializer - JSONAPISerializer', functi testInDebug('Asserts when combined with EmbeddedRecordsMixin', function (assert) { assert.expectAssertion(function () { - JSONAPISerializer.extend(DS.EmbeddedRecordsMixin).create(); + JSONAPISerializer.extend(EmbeddedRecordsMixin).create(); }, /You've used the EmbeddedRecordsMixin in/); }); testInDebug('Allows EmbeddedRecordsMixin if isEmbeddedRecordsMixinCompatible is true', function (assert) { assert.expectNoAssertion(function () { - JSONAPISerializer.extend(DS.EmbeddedRecordsMixin, { + JSONAPISerializer.extend(EmbeddedRecordsMixin, { isEmbeddedRecordsMixinCompatible: true, }).create(); }); diff --git a/packages/-ember-data/tests/integration/serializers/rest-serializer-test.js b/packages/-ember-data/tests/integration/serializers/rest-serializer-test.js index 21549468a1a..331173425d1 100644 --- a/packages/-ember-data/tests/integration/serializers/rest-serializer-test.js +++ b/packages/-ember-data/tests/integration/serializers/rest-serializer-test.js @@ -3,11 +3,12 @@ import { camelize, dasherize, decamelize } from '@ember/string'; import { module, test } from 'qunit'; -import DS from 'ember-data'; import Inflector, { singularize } from 'ember-inflector'; import { setupTest } from 'ember-qunit'; +import Adapter from '@ember-data/adapter'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; +import JSONSerializer from '@ember-data/serializer/json'; import RESTSerializer from '@ember-data/serializer/rest'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; @@ -62,7 +63,7 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { this.owner.register('model:basket', Basket); this.owner.register('model:container', Container); - this.owner.register('adapter:application', DS.Adapter.extend()); + this.owner.register('adapter:application', Adapter.extend()); this.owner.register('serializer:application', RESTSerializer.extend()); let store = this.owner.lookup('service:store'); @@ -93,7 +94,7 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { test('normalizeResponse should extract meta using extractMeta', function (assert) { this.owner.register( 'serializer:home-planet', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ extractMeta(store, modelClass, payload) { let meta = this._super(...arguments); meta.authors.push('Tomhuda'); @@ -125,8 +126,8 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { return singularize(camelized); }; - this.owner.register('serializer:home-planet', DS.JSONSerializer.extend()); - this.owner.register('serializer:super-villain', DS.JSONSerializer.extend()); + this.owner.register('serializer:home-planet', JSONSerializer.extend()); + this.owner.register('serializer:super-villain', JSONSerializer.extend()); var jsonHash = { home_planets: [ @@ -196,7 +197,7 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { this.owner.register( 'serializer:home-planet', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ normalize() { homePlanetNormalizeCount++; return this._super.apply(this, arguments); @@ -235,8 +236,8 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { var homePlanet; var oldModelNameFromPayloadKey = serializer.modelNameFromPayloadKey; - this.owner.register('serializer:super-villain', DS.JSONSerializer.extend()); - this.owner.register('serializer:home-planet', DS.JSONSerializer.extend()); + this.owner.register('serializer:super-villain', JSONSerializer.extend()); + this.owner.register('serializer:home-planet', JSONSerializer.extend()); serializer.modelNameFromPayloadKey = function (root) { //return some garbage that won"t resolve in the container @@ -277,8 +278,8 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { let serializer = store.serializerFor('application'); var homePlanets; - this.owner.register('serializer:super-villain', DS.JSONSerializer); - this.owner.register('serializer:home-planet', DS.JSONSerializer); + this.owner.register('serializer:super-villain', JSONSerializer); + this.owner.register('serializer:home-planet', JSONSerializer); serializer.modelNameFromPayloadKey = function (root) { //return some garbage that won"t resolve in the container return 'garbage'; @@ -350,10 +351,10 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { test('normalizeResponse loads secondary records with correct serializer', function (assert) { var superVillainNormalizeCount = 0; - this.owner.register('serializer:evil-minion', DS.JSONSerializer); + this.owner.register('serializer:evil-minion', JSONSerializer); this.owner.register( 'serializer:super-villain', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ normalize() { superVillainNormalizeCount++; return this._super.apply(this, arguments); @@ -397,10 +398,10 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { test('normalizeResponse loads secondary records with correct serializer', function (assert) { var superVillainNormalizeCount = 0; - this.owner.register('serializer:evil-minion', DS.JSONSerializer); + this.owner.register('serializer:evil-minion', JSONSerializer); this.owner.register( 'serializer:super-villain', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ normalize() { superVillainNormalizeCount++; return this._super.apply(this, arguments); @@ -424,8 +425,8 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { }); test('normalizeResponse can handle large included arrays', function (assert) { - this.owner.register('serializer:super-villain', DS.RESTSerializer); - this.owner.register('serializer:evil-minion', DS.RESTSerializer); + this.owner.register('serializer:super-villain', RESTSerializer); + this.owner.register('serializer:evil-minion', RESTSerializer); let evilMinions = []; // The actual stack size seems to vary based on browser and potenetially hardware and @@ -457,7 +458,7 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { test('normalize should allow for different levels of normalization', function (assert) { this.owner.register( 'serializer:evil-minion', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ attrs: { superVillain: 'is_super_villain', }, @@ -485,7 +486,7 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { test('normalize should allow for different levels of normalization - attributes', function (assert) { this.owner.register( 'serializer:evil-minion', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ attrs: { name: 'full_name', }, @@ -633,7 +634,7 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { this.owner.register( 'serializer:home-planet', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ payloadKeyFromModelName(modelName) { return dasherize(modelName); }, @@ -809,7 +810,7 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { ], }; var array; - this.owner.register('serializer:comment', DS.JSONSerializer); + this.owner.register('serializer:comment', JSONSerializer); let store = this.owner.lookup('service:store'); let serializer = store.serializerFor('application'); @@ -935,7 +936,7 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { test('Serializer should respect the attrs hash in links', function (assert) { this.owner.register( 'serializer:super-villain', - DS.RESTSerializer.extend({ + RESTSerializer.extend({ attrs: { evilMinions: { key: 'my_minions' }, }, @@ -963,8 +964,8 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { // https://github.com/emberjs/data/issues/3805 test('normalizes sideloaded single record so that it sideloads correctly - belongsTo - GH-3805', function (assert) { - this.owner.register('serializer:evil-minion', DS.JSONSerializer); - this.owner.register('serializer:doomsday-device', DS.RESTSerializer.extend()); + this.owner.register('serializer:evil-minion', JSONSerializer); + this.owner.register('serializer:doomsday-device', RESTSerializer.extend()); let payload = { doomsdayDevice: { @@ -999,8 +1000,8 @@ module('integration/serializer/rest - RESTSerializer', function (hooks) { // https://github.com/emberjs/data/issues/3805 test('normalizes sideloaded single record so that it sideloads correctly - hasMany - GH-3805', function (assert) { - this.owner.register('serializer:super-villain', DS.JSONSerializer); - this.owner.register('serializer:home-planet', DS.RESTSerializer.extend()); + this.owner.register('serializer:super-villain', JSONSerializer); + this.owner.register('serializer:home-planet', RESTSerializer.extend()); let payload = { homePlanet: { diff --git a/packages/-ember-data/tests/integration/store/json-api-validation-test.js b/packages/-ember-data/tests/integration/store/json-api-validation-test.js index 6355be31845..77e291eec40 100644 --- a/packages/-ember-data/tests/integration/store/json-api-validation-test.js +++ b/packages/-ember-data/tests/integration/store/json-api-validation-test.js @@ -3,15 +3,17 @@ import { run } from '@ember/runloop'; import QUnit, { module } from 'qunit'; import { resolve } from 'rsvp'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; +import Adapter from '@ember-data/adapter'; +import Model, { attr } from '@ember-data/model'; +import Serializer from '@ember-data/serializer'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; function payloadError(owner, payload, expectedError, assert) { owner.register( 'serializer:person', - DS.Serializer.extend({ + Serializer.extend({ normalizeResponse(store, type, pld) { return pld; }, @@ -19,7 +21,7 @@ function payloadError(owner, payload, expectedError, assert) { ); owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ findRecord() { return resolve(payload); }, @@ -44,11 +46,11 @@ module('integration/store/json-validation', function (hooks) { hooks.beforeEach(function () { QUnit.assert.payloadError = payloadError.bind(QUnit.assert); - const Person = DS.Model.extend({ - updatedAt: DS.attr('string'), - name: DS.attr('string'), - firstName: DS.attr('string'), - lastName: DS.attr('string'), + const Person = Model.extend({ + updatedAt: attr('string'), + name: attr('string'), + firstName: attr('string'), + lastName: attr('string'), }); this.owner.register('model:person', Person); @@ -61,14 +63,14 @@ module('integration/store/json-validation', function (hooks) { testInDebug("when normalizeResponse returns undefined (or doesn't return), throws an error", function (assert) { this.owner.register( 'serializer:person', - DS.Serializer.extend({ + Serializer.extend({ normalizeResponse() {}, }) ); this.owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ findRecord() { return resolve({ data: {} }); }, @@ -87,7 +89,7 @@ module('integration/store/json-validation', function (hooks) { testInDebug('when normalizeResponse returns null, throws an error', function (assert) { this.owner.register( 'serializer:person', - DS.Serializer.extend({ + Serializer.extend({ normalizeResponse() { return null; }, @@ -96,7 +98,7 @@ module('integration/store/json-validation', function (hooks) { this.owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ findRecord() { return resolve({ data: {} }); }, @@ -115,7 +117,7 @@ module('integration/store/json-validation', function (hooks) { testInDebug('when normalizeResponse returns an empty object, throws an error', function (assert) { this.owner.register( 'serializer:person', - DS.Serializer.extend({ + Serializer.extend({ normalizeResponse() { return {}; }, @@ -124,7 +126,7 @@ module('integration/store/json-validation', function (hooks) { this.owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ findRecord() { return resolve({ data: {} }); }, @@ -145,7 +147,7 @@ module('integration/store/json-validation', function (hooks) { function (assert) { this.owner.register( 'serializer:person', - DS.Serializer.extend({ + Serializer.extend({ normalizeResponse() { return { data: [], @@ -157,7 +159,7 @@ module('integration/store/json-validation', function (hooks) { this.owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ findRecord() { return resolve({ data: {} }); }, diff --git a/packages/-ember-data/tests/integration/store/query-record-test.js b/packages/-ember-data/tests/integration/store/query-record-test.js index 8f7d72d3236..02e7b38225d 100644 --- a/packages/-ember-data/tests/integration/store/query-record-test.js +++ b/packages/-ember-data/tests/integration/store/query-record-test.js @@ -3,9 +3,10 @@ import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import { reject, resolve } from 'rsvp'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; +import Adapter from '@ember-data/adapter'; +import Model, { attr } from '@ember-data/model'; import JSONAPISerializer from '@ember-data/serializer/json-api'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; @@ -13,8 +14,8 @@ module('integration/store/query-record - Query one record with a query hash', fu setupTest(hooks); hooks.beforeEach(function () { - const Person = DS.Model.extend({ - name: DS.attr('string'), + const Person = Model.extend({ + name: attr('string'), }); this.owner.register('model:person', Person); @@ -45,7 +46,7 @@ module('integration/store/query-record - Query one record with a query hash', fu this.owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ queryRecord(store, type, query) { assert.strictEqual(type, Person, 'the query method is called with the correct type'); return resolve({ @@ -65,7 +66,7 @@ module('integration/store/query-record - Query one record with a query hash', fu test('When a record is requested, and the promise is rejected, .queryRecord() is rejected.', function (assert) { this.owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ queryRecord(store, type, query) { return reject(); }, @@ -100,7 +101,7 @@ module('integration/store/query-record - Query one record with a query hash', fu this.owner.register( 'adapter:person', - DS.Adapter.extend({ + Adapter.extend({ queryRecord(store, type, query) { return resolve({ data: { diff --git a/packages/-ember-data/tests/unit/adapter-errors-test.js b/packages/-ember-data/tests/unit/adapter-errors-test.js index e5632b97b79..8990915cc31 100644 --- a/packages/-ember-data/tests/unit/adapter-errors-test.js +++ b/packages/-ember-data/tests/unit/adapter-errors-test.js @@ -2,14 +2,24 @@ import EmberError from '@ember/error'; import { module, test } from 'qunit'; -import DS from 'ember-data'; - +import AdapterError, { + AbortError, + ConflictError, + errorsArrayToHash, + errorsHashToArray, + ForbiddenError, + InvalidError, + NotFoundError, + ServerError, + TimeoutError, + UnauthorizedError, +} from '@ember-data/adapter/error'; import { DEPRECATE_HELPERS } from '@ember-data/private-build-infra/deprecations'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; -module('unit/adapter-errors - DS.AdapterError', function () { - test('DS.AdapterError', function (assert) { - let error = new DS.AdapterError(); +module('unit/adapter-errors - AdapterError', function () { + test('AdapterError', function (assert) { + let error = new AdapterError(); assert.ok(error instanceof Error); assert.ok(error instanceof EmberError); @@ -17,90 +27,90 @@ module('unit/adapter-errors - DS.AdapterError', function () { assert.strictEqual(error.message, 'Adapter operation failed'); }); - test('DS.InvalidError', function (assert) { - let error = new DS.InvalidError(); + test('InvalidError', function (assert) { + let error = new InvalidError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'The adapter rejected the commit because it was invalid'); }); - test('DS.TimeoutError', function (assert) { - let error = new DS.TimeoutError(); + test('TimeoutError', function (assert) { + let error = new TimeoutError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'The adapter operation timed out'); }); - test('DS.AbortError', function (assert) { - let error = new DS.AbortError(); + test('AbortError', function (assert) { + let error = new AbortError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'The adapter operation was aborted'); }); - test('DS.UnauthorizedError', function (assert) { - let error = new DS.UnauthorizedError(); + test('UnauthorizedError', function (assert) { + let error = new UnauthorizedError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'The adapter operation is unauthorized'); }); - test('DS.ForbiddenError', function (assert) { - let error = new DS.ForbiddenError(); + test('ForbiddenError', function (assert) { + let error = new ForbiddenError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'The adapter operation is forbidden'); }); - test('DS.NotFoundError', function (assert) { - let error = new DS.NotFoundError(); + test('NotFoundError', function (assert) { + let error = new NotFoundError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'The adapter could not find the resource'); }); - test('DS.ConflictError', function (assert) { - let error = new DS.ConflictError(); + test('ConflictError', function (assert) { + let error = new ConflictError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'The adapter operation failed due to a conflict'); }); - test('DS.ServerError', function (assert) { - let error = new DS.ServerError(); + test('ServerError', function (assert) { + let error = new ServerError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'The adapter operation failed due to a server error'); }); test('CustomAdapterError', function (assert) { - let CustomAdapterError = DS.AdapterError.extend(); + let CustomAdapterError = AdapterError.extend(); let error = new CustomAdapterError(); assert.ok(error instanceof Error); - assert.ok(error instanceof DS.AdapterError); + assert.ok(error instanceof AdapterError); assert.ok(error.isAdapterError); assert.strictEqual(error.message, 'Adapter operation failed'); }); test('CustomAdapterError with default message', function (assert) { - let CustomAdapterError = DS.AdapterError.extend({ message: 'custom error!' }); + let CustomAdapterError = AdapterError.extend({ message: 'custom error!' }); let error = new CustomAdapterError(); assert.strictEqual(error.message, 'custom error!'); @@ -148,25 +158,25 @@ module('unit/adapter-errors - DS.AdapterError', function () { ]; test('errorsHashToArray', function (assert) { - let result = DS.errorsHashToArray(errorsHash); + let result = errorsHashToArray(errorsHash); assert.deepEqual(result, errorsArray); assert.expectDeprecation({ id: 'ember-data:deprecate-errors-hash-to-array-helper', count: 1 }); }); test('errorsHashToArray for primary data object', function (assert) { - let result = DS.errorsHashToArray(errorsPrimaryHash); + let result = errorsHashToArray(errorsPrimaryHash); assert.deepEqual(result, errorsPrimaryArray); assert.expectDeprecation({ id: 'ember-data:deprecate-errors-hash-to-array-helper', count: 1 }); }); test('errorsArrayToHash', function (assert) { - let result = DS.errorsArrayToHash(errorsArray); + let result = errorsArrayToHash(errorsArray); assert.deepEqual(result, errorsHash); assert.expectDeprecation({ id: 'ember-data:deprecate-errors-array-to-hash-helper', count: 1 }); }); test('errorsArrayToHash without trailing slash', function (assert) { - let result = DS.errorsArrayToHash([ + let result = errorsArrayToHash([ { detail: 'error message', source: { pointer: 'data/attributes/name' }, @@ -177,15 +187,15 @@ module('unit/adapter-errors - DS.AdapterError', function () { }); test('errorsArrayToHash for primary data object', function (assert) { - let result = DS.errorsArrayToHash(errorsPrimaryArray); + let result = errorsArrayToHash(errorsPrimaryArray); assert.deepEqual(result, errorsPrimaryHash); assert.expectDeprecation({ id: 'ember-data:deprecate-errors-array-to-hash-helper', count: 1 }); }); } - testInDebug('DS.InvalidError will normalize errors hash will assert', function (assert) { + testInDebug('InvalidError will normalize errors hash will assert', function (assert) { assert.expectAssertion(function () { - new DS.InvalidError({ name: ['is invalid'] }); + new InvalidError({ name: ['is invalid'] }); }, /expects json-api formatted errors/); }); }); diff --git a/packages/-ember-data/tests/unit/adapters/build-url-mixin/build-url-test.js b/packages/-ember-data/tests/unit/adapters/build-url-mixin/build-url-test.js index 6dd91401396..0ad714ff4c0 100644 --- a/packages/-ember-data/tests/unit/adapters/build-url-mixin/build-url-test.js +++ b/packages/-ember-data/tests/unit/adapters/build-url-mixin/build-url-test.js @@ -1,9 +1,10 @@ import { module, test } from 'qunit'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; -module('unit/adapters/build-url-mixin/build-url - DS.BuildURLMixin#buildURL', function (hooks) { +import Adapter, { BuildURLMixin } from '@ember-data/adapter'; + +module('unit/adapters/build-url-mixin/build-url - BuildURLMixin#buildURL', function (hooks) { let adapter; setupTest(hooks); hooks.beforeEach(function () { @@ -16,9 +17,9 @@ module('unit/adapters/build-url-mixin/build-url - DS.BuildURLMixin#buildURL', fu }, }; - const Adapter = DS.Adapter.extend(DS.BuildURLMixin, customPathForType); + const AppAdapter = Adapter.extend(BuildURLMixin, customPathForType); - adapter = Adapter.create(); + adapter = AppAdapter.create(); }); test('buildURL - works with empty paths', function (assert) { diff --git a/packages/-ember-data/tests/unit/adapters/rest-adapter/build-query-test.js b/packages/-ember-data/tests/unit/adapters/rest-adapter/build-query-test.js index 1064ed988de..1efb3ee7272 100644 --- a/packages/-ember-data/tests/unit/adapters/rest-adapter/build-query-test.js +++ b/packages/-ember-data/tests/unit/adapters/rest-adapter/build-query-test.js @@ -1,10 +1,10 @@ import { module, test } from 'qunit'; -import DS from 'ember-data'; +import RESTAdapter from '@ember-data/adapter/rest'; module('unit/adapters/rest-adapter/build-query - building queries', function () { test('buildQuery() returns an empty query when snapshot has no query params', function (assert) { - const adapter = DS.RESTAdapter.create(); + const adapter = RESTAdapter.create(); const snapshotStub = {}; const query = adapter.buildQuery(snapshotStub); @@ -13,14 +13,14 @@ module('unit/adapters/rest-adapter/build-query - building queries', function () }); test(`buildQuery - doesn't fail without a snapshot`, function (assert) { - const adapter = DS.RESTAdapter.create(); + const adapter = RESTAdapter.create(); const query = adapter.buildQuery(); assert.deepEqual(query, {}, 'returns an empty query'); }); test('buildQuery() returns query with `include` from snapshot', function (assert) { - const adapter = DS.RESTAdapter.create(); + const adapter = RESTAdapter.create(); const snapshotStub = { include: 'comments' }; const query = adapter.buildQuery(snapshotStub); diff --git a/packages/-ember-data/tests/unit/model/errors-test.js b/packages/-ember-data/tests/unit/model/errors-test.js index 66eb35a1af2..8f559425d87 100644 --- a/packages/-ember-data/tests/unit/model/errors-test.js +++ b/packages/-ember-data/tests/unit/model/errors-test.js @@ -1,7 +1,6 @@ import QUnit, { module } from 'qunit'; -import DS from 'ember-data'; - +import { Errors } from '@ember-data/model/-private'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; const AssertPrototype = QUnit.assert; @@ -10,7 +9,7 @@ let errors; module('unit/model/errors', function (hooks) { hooks.beforeEach(function () { - errors = DS.Errors.create({ + errors = Errors.create({ __record: { currentState: { notify() {}, diff --git a/packages/-ember-data/tests/unit/model/rollback-attributes-test.js b/packages/-ember-data/tests/unit/model/rollback-attributes-test.js index 2d3549768e6..769e8e9cd86 100644 --- a/packages/-ember-data/tests/unit/model/rollback-attributes-test.js +++ b/packages/-ember-data/tests/unit/model/rollback-attributes-test.js @@ -7,7 +7,6 @@ import { module, test } from 'qunit'; import { Promise as EmberPromise, reject } from 'rsvp'; import { gte } from 'ember-compatibility-helpers'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; import Adapter from '@ember-data/adapter'; @@ -22,9 +21,9 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h module('rolledBack hook', function (hooks) { hooks.beforeEach(function () { - const Person = DS.Model.extend({ - firstName: DS.attr(), - lastName: DS.attr(), + const Person = Model.extend({ + firstName: attr(), + lastName: attr(), }); this.owner.register('model:person', Person); @@ -235,14 +234,14 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h }); test(`invalid new record's attributes can be rollbacked`, async function (assert) { - let error = new DS.InvalidError([ + let error = new InvalidError([ { detail: 'is invalid', source: { pointer: 'data/attributes/name' }, }, ]); - let adapter = DS.RESTAdapter.extend({ + let adapter = RESTAdapter.extend({ ajax(url, type, hash) { return reject(error); }, @@ -272,9 +271,9 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h }); test(`invalid record's attributes can be rollbacked after multiple failed calls - #3677`, function (assert) { - let adapter = DS.RESTAdapter.extend({ + let adapter = RESTAdapter.extend({ ajax(url, type, hash) { - let error = new DS.InvalidError(); + let error = new InvalidError(); return reject(error); }, }); @@ -491,18 +490,18 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function (h }); test(`when destroying a record setup the record state to invalid, the record's attributes can be rollbacked`, async function (assert) { - const Dog = DS.Model.extend({ - name: DS.attr(), + const Dog = Model.extend({ + name: attr(), }); - let error = new DS.InvalidError([ + let error = new InvalidError([ { detail: 'is invalid', source: { pointer: 'data/attributes/name' }, }, ]); - let adapter = DS.RESTAdapter.extend({ + let adapter = RESTAdapter.extend({ ajax(url, type, hash) { return reject(error); }, diff --git a/packages/-ember-data/tests/unit/promise-proxies-test.js b/packages/-ember-data/tests/unit/promise-proxies-test.js index 6f4a30f42d2..e7a423b54f2 100644 --- a/packages/-ember-data/tests/unit/promise-proxies-test.js +++ b/packages/-ember-data/tests/unit/promise-proxies-test.js @@ -3,11 +3,11 @@ import { A } from '@ember/array'; import { module, test } from 'qunit'; import { Promise as EmberPromise } from 'rsvp'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; import Adapter from '@ember-data/adapter'; import Model, { belongsTo } from '@ember-data/model'; +import { PromiseManyArray } from '@ember-data/model/-private'; import JSONAPISerializer from '@ember-data/serializer/json-api'; module('PromiseManyArray', function () { @@ -18,7 +18,7 @@ module('PromiseManyArray', function () { content.reload = () => EmberPromise.resolve(content); - let array = DS.PromiseManyArray.create({ + let array = PromiseManyArray.create({ content, }); @@ -40,7 +40,7 @@ module('PromiseManyArray', function () { }; let promise = EmberPromise.resolve(content); - array = DS.PromiseManyArray.create({ + array = PromiseManyArray.create({ promise, }); @@ -62,7 +62,7 @@ module('PromiseManyArray', function () { assert.false(array.isSettled, 'should NOT be settled'); assert.false(array.isFulfilled, 'should NOT be fulfilled'); - assert.ok(reloaded instanceof DS.PromiseManyArray); + assert.ok(reloaded instanceof PromiseManyArray); assert.strictEqual(reloaded, array); let value = await reloaded; @@ -81,7 +81,7 @@ module('PromiseManyArray', function () { let promise = EmberPromise.resolve(content); - let array = DS.PromiseManyArray.create({ + let array = PromiseManyArray.create({ promise, }); @@ -103,7 +103,7 @@ module('PromiseManyArray', function () { assert.false(array.isSettled, 'should NOT be settled'); assert.false(array.isFulfilled, 'should NOT be fulfilled'); - assert.ok(array instanceof DS.PromiseManyArray); + assert.ok(array instanceof PromiseManyArray); let value = await array; assert.false(array.isRejected, 'should NOT be rejected'); diff --git a/packages/-ember-data/tests/unit/store/peek-record-test.js b/packages/-ember-data/tests/unit/store/peek-record-test.js index 4fb804c0d50..bd784922b52 100644 --- a/packages/-ember-data/tests/unit/store/peek-record-test.js +++ b/packages/-ember-data/tests/unit/store/peek-record-test.js @@ -2,9 +2,9 @@ import EmberObject from '@ember/object'; import { module, test } from 'qunit'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; +import Model from '@ember-data/model'; import { recordIdentifierFor } from '@ember-data/store'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; @@ -12,7 +12,7 @@ module('unit/store/peekRecord - Store peekRecord', function (hooks) { setupTest(hooks); hooks.beforeEach(function () { - this.owner.register('model:person', DS.Model.extend()); + this.owner.register('model:person', Model.extend()); }); test('peekRecord should return the record if it is in the store', function (assert) { diff --git a/packages/-ember-data/tests/unit/store/serializer-for-test.js b/packages/-ember-data/tests/unit/store/serializer-for-test.js index 4ed34d0b1fd..8ce56163706 100644 --- a/packages/-ember-data/tests/unit/store/serializer-for-test.js +++ b/packages/-ember-data/tests/unit/store/serializer-for-test.js @@ -1,17 +1,18 @@ import { module, test } from 'qunit'; -import DS from 'ember-data'; import { setupTest } from 'ember-qunit'; +import Model from '@ember-data/model'; +import JSONSerializer from '@ember-data/serializer/json'; import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; let store, Person; -module('unit/store/serializer_for - DS.Store#serializerFor', function (hooks) { +module('unit/store/serializer_for - Store#serializerFor', function (hooks) { setupTest(hooks); hooks.beforeEach(function () { - Person = DS.Model.extend({}); + Person = Model.extend({}); this.owner.register('model:person', Person); @@ -19,7 +20,7 @@ module('unit/store/serializer_for - DS.Store#serializerFor', function (hooks) { }); test('Calling serializerFor looks up `serializer:` from the container', function (assert) { - const PersonSerializer = DS.JSONSerializer.extend(); + const PersonSerializer = JSONSerializer.extend(); this.owner.register('serializer:person', PersonSerializer); @@ -30,7 +31,7 @@ module('unit/store/serializer_for - DS.Store#serializerFor', function (hooks) { }); test('Calling serializerFor with a type that has not been registered looks up the default ApplicationSerializer', function (assert) { - const ApplicationSerializer = DS.JSONSerializer.extend(); + const ApplicationSerializer = JSONSerializer.extend(); this.owner.register('serializer:application', ApplicationSerializer); diff --git a/packages/adapter/addon/error.js b/packages/adapter/addon/error.js index 8f88239ad9f..498fd2775e5 100644 --- a/packages/adapter/addon/error.js +++ b/packages/adapter/addon/error.js @@ -356,9 +356,7 @@ const PRIMARY_ATTRIBUTE_KEY = 'base'; /** Convert an hash of errors into an array with errors in JSON-API format. ```javascript - import DS from 'ember-data'; - - const { errorsHashToArray } = DS; + import { errorsHashToArray } from '@ember-data/adapter/error'; let errors = { base: 'Invalid attributes on saving this record', @@ -437,9 +435,7 @@ export function errorsHashToArray(errors) { Convert an array of errors in JSON-API format into an object. ```javascript - import DS from 'ember-data'; - - const { errorsArrayToHash } = DS; + import { errorsArrayToHash } from '@ember-data/adapter/error'; let errorsArray = [ { diff --git a/packages/model/addon/-private/model.js b/packages/model/addon/-private/model.js index 37a83ad6762..01f337dab80 100644 --- a/packages/model/addon/-private/model.js +++ b/packages/model/addon/-private/model.js @@ -7,7 +7,6 @@ import EmberError from '@ember/error'; import EmberObject from '@ember/object'; import { dependentKeyCompat } from '@ember/object/compat'; import { run } from '@ember/runloop'; -import { isNone } from '@ember/utils'; import { DEBUG } from '@glimmer/env'; import { tracked } from '@glimmer/tracking'; import Ember from 'ember'; @@ -18,6 +17,7 @@ import { HAS_DEBUG_PACKAGE } from '@ember-data/private-build-infra'; import { DEPRECATE_EARLY_STATIC, DEPRECATE_MODEL_REOPEN, + DEPRECATE_NON_EXPLICIT_POLYMORPHISM, DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE, DEPRECATE_SAVE_PROMISE_ACCESS, } from '@ember-data/private-build-infra/deprecations'; @@ -58,7 +58,7 @@ function findPossibleInverses(type, inverseType, name, relationshipsSoFar) { let relationshipsForType = relationshipMap.get(type.modelName); let relationships = Array.isArray(relationshipsForType) ? relationshipsForType.filter((relationship) => { - let optionsForRelationship = inverseType.metaForProperty(relationship.name).options; + let optionsForRelationship = relationship.options; if (!optionsForRelationship.inverse && optionsForRelationship.inverse !== null) { return true; @@ -1394,42 +1394,46 @@ class Model extends EmberObject { this.modelName ); } - let inverseType = this.typeForRelationship(name, store); - if (!inverseType) { - return null; - } - let propertyMeta = this.metaForProperty(name); + const relationship = this.relationshipsByName.get(name); + const { options } = relationship; + const isPolymorphic = options.polymorphic; + //If inverse is manually specified to be null, like `comments: hasMany('message', { inverse: null })` - let options = propertyMeta.options; - if (options.inverse === null) { + const isExplicitInverseNull = options.inverse === null; + const isAbstractType = + !isExplicitInverseNull && isPolymorphic && !store.getSchemaDefinitionService().doesTypeExist(relationship.type); + + if (isExplicitInverseNull || isAbstractType) { + assert( + `No schema for the abstract type '${relationship.type}' for the polymorphic relationship '${name}' on '${this.modelName}' was provided by the SchemaDefinitionService.`, + !isPolymorphic || isExplicitInverseNull + ); return null; } - let inverseName, inverseKind, inverse, inverseOptions; + let fieldOnInverse, inverseKind, inverseRelationship, inverseOptions; + let inverseSchema = this.typeForRelationship(name, store); + // if the type does not exist and we are not polymorphic //If inverse is specified manually, return the inverse - if (options.inverse) { - inverseName = options.inverse; - inverse = inverseType.relationshipsByName.get(inverseName); + if (options.inverse !== undefined) { + fieldOnInverse = options.inverse; + inverseRelationship = inverseSchema && inverseSchema.relationshipsByName.get(fieldOnInverse); assert( - "We found no inverse relationships by the name of '" + - inverseName + - "' on the '" + - inverseType.modelName + - "' model. This is most likely due to a missing attribute on your model definition.", - !isNone(inverse) + `We found no field named '${fieldOnInverse}' on the schema for '${inverseSchema.modelName}' to be the inverse of the '${name}' relationship on '${this.modelName}'. This is most likely due to a missing field on your model definition.`, + inverseRelationship ); // TODO probably just return the whole inverse here - inverseKind = inverse.kind; - inverseOptions = inverse.options; + inverseKind = inverseRelationship.kind; + inverseOptions = inverseRelationship.options; } else { //No inverse was specified manually, we need to use a heuristic to guess one - if (propertyMeta.type === propertyMeta.parentModelName) { + if (relationship.type === relationship.parentModelName) { warn( - `Detected a reflexive relationship by the name of '${name}' without an inverse option. Look at https://guides.emberjs.com/current/models/relationships/#toc_reflexive-relations for how to explicitly specify inverses.`, + `Detected a reflexive relationship named '${name}' on the schema for ${relationship.type} without an inverse option. Look at https://guides.emberjs.com/current/models/relationships/#toc_reflexive-relations for how to explicitly specify inverses.`, false, { id: 'ds.model.reflexive-relationship-without-inverse', @@ -1437,30 +1441,33 @@ class Model extends EmberObject { ); } - let possibleRelationships = findPossibleInverses(this, inverseType, name); + let possibleRelationships = findPossibleInverses(this, inverseSchema, name); if (possibleRelationships.length === 0) { return null; } - let filteredRelationships = possibleRelationships.filter((possibleRelationship) => { - let optionsForRelationship = inverseType.metaForProperty(possibleRelationship.name).options; - return name === optionsForRelationship.inverse; - }); + if (DEBUG) { + let filteredRelationships = possibleRelationships.filter((possibleRelationship) => { + let optionsForRelationship = possibleRelationship.options; + return name === optionsForRelationship.inverse; + }); - assert( - "You defined the '" + - name + - "' relationship on " + - this + - ', but you defined the inverse relationships of type ' + - inverseType.toString() + - ' multiple times. Look at https://guides.emberjs.com/current/models/relationships/#toc_explicit-inverses for how to explicitly specify inverses', - filteredRelationships.length < 2 - ); + assert( + "You defined the '" + + name + + "' relationship on " + + this + + ', but you defined the inverse relationships of type ' + + inverseSchema.toString() + + ' multiple times. Look at https://guides.emberjs.com/current/models/relationships/#toc_explicit-inverses for how to explicitly specify inverses', + filteredRelationships.length < 2 + ); + } - if (filteredRelationships.length === 1) { - possibleRelationships = filteredRelationships; + let explicitRelationship = possibleRelationships.find((relationship) => relationship.options.inverse === name); + if (explicitRelationship) { + possibleRelationships = [explicitRelationship]; } assert( @@ -1471,24 +1478,78 @@ class Model extends EmberObject { ', but multiple possible inverse relationships of type ' + this + ' were found on ' + - inverseType + + inverseSchema + '. Look at https://guides.emberjs.com/current/models/relationships/#toc_explicit-inverses for how to explicitly specify inverses', possibleRelationships.length === 1 ); - inverseName = possibleRelationships[0].name; + fieldOnInverse = possibleRelationships[0].name; inverseKind = possibleRelationships[0].kind; inverseOptions = possibleRelationships[0].options; } + // ensure inverse is properly configured + if (DEBUG && isPolymorphic) { + if (DEPRECATE_NON_EXPLICIT_POLYMORPHISM) { + if (!inverseOptions.as) { + deprecate( + `Relationships that satisfy polymorphic relationships MUST define which abstract-type they are satisfying using 'as'. The field '${fieldOnInverse}' on type '${inverseSchema.modelName}' is misconfigured.`, + false, + { + id: 'ember-data:non-explicit-relationships', + since: { enabled: '4.8', available: '4.8' }, + until: '5.0', + for: 'ember-data', + } + ); + } + } else { + assert( + `Relationships that satisfy polymorphic relationships MUST define which abstract-type they are satisfying using 'as'. The field '${fieldOnInverse}' on type '${inverseSchema.modelName}' is misconfigured.`, + inverseOptions.as + ); + assert( + `options.as should match the expected type of the polymorphic relationship. Expected field '${fieldOnInverse}' on type '${inverseSchema.modelName}' to specify '${relationship.type}' but found '${inverseOptions.as}'`, + !!inverseOptions.as && relationship.type === inverseOptions.as + ); + } + } + + // ensure we are properly configured + if (DEBUG && inverseOptions.polymorphic) { + if (DEPRECATE_NON_EXPLICIT_POLYMORPHISM) { + if (!options.as) { + deprecate( + `Relationships that satisfy polymorphic relationships MUST define which abstract-type they are satisfying using 'as'. The field '${name}' on type '${this.modelName}' is misconfigured.`, + false, + { + id: 'ember-data:non-explicit-relationships', + since: { enabled: '4.8', available: '4.8' }, + until: '5.0', + for: 'ember-data', + } + ); + } + } else { + assert( + `Relationships that satisfy polymorphic relationships MUST define which abstract-type they are satisfying using 'as'. The field '${name}' on type '${this.modelName}' is misconfigured.`, + options.as + ); + assert( + `options.as should match the expected type of the polymorphic relationship. Expected field '${name}' on type '${this.modelName}' to specify '${inverseRelationship.type}' but found '${options.as}'`, + !!options.as && inverseRelationship.type === options.as + ); + } + } + assert( - `The ${inverseType.modelName}:${inverseName} relationship declares 'inverse: null', but it was resolved as the inverse for ${this.modelName}:${name}.`, - !inverseOptions || inverseOptions.inverse !== null + `The ${inverseSchema.modelName}:${fieldOnInverse} relationship declares 'inverse: null', but it was resolved as the inverse for ${this.modelName}:${name}.`, + inverseOptions.inverse !== null ); return { - type: inverseType, - name: inverseName, + type: inverseSchema, + name: fieldOnInverse, kind: inverseKind, options: inverseOptions, }; diff --git a/packages/model/addon/-private/relationship-meta.ts b/packages/model/addon/-private/relationship-meta.ts index 3baeea16f65..b7eac638c53 100644 --- a/packages/model/addon/-private/relationship-meta.ts +++ b/packages/model/addon/-private/relationship-meta.ts @@ -75,7 +75,9 @@ class RelationshipDefinition implements RelationshipSchema { if (shouldFindInverse(this.meta)) { inverse = modelClass.inverseFor(this.key, store); - } else if (DEBUG) { + } + // TODO make this error again for the non-polymorphic case + if (DEBUG && !this.options.polymorphic) { modelClass.typeForRelationship(this.key, store); } diff --git a/packages/private-build-infra/addon/current-deprecations.ts b/packages/private-build-infra/addon/current-deprecations.ts index 85f68d0d5c0..ccdfd62b877 100644 --- a/packages/private-build-infra/addon/current-deprecations.ts +++ b/packages/private-build-infra/addon/current-deprecations.ts @@ -60,4 +60,5 @@ export default { DEPRECATE_PROMISE_PROXIES: '4.8', DEPRECATE_ARRAY_LIKE: '4.8', DEPRECATE_COMPUTED_CHAINS: '4.8', + DEPRECATE_NON_EXPLICIT_POLYMORPHISM: '4.8', }; diff --git a/packages/private-build-infra/addon/deprecations.ts b/packages/private-build-infra/addon/deprecations.ts index 28581f7ac65..8e528641fef 100644 --- a/packages/private-build-infra/addon/deprecations.ts +++ b/packages/private-build-infra/addon/deprecations.ts @@ -29,3 +29,4 @@ export const DEPRECATE_A_USAGE = deprecationState('DEPRECATE_A_USAGE'); export const DEPRECATE_PROMISE_PROXIES = deprecationState('DEPRECATE_PROMISE_PROXIES'); export const DEPRECATE_ARRAY_LIKE = deprecationState('DEPRECATE_ARRAY_LIKE'); export const DEPRECATE_COMPUTED_CHAINS = deprecationState('DEPRECATE_COMPUTED_CHAINS'); +export const DEPRECATE_NON_EXPLICIT_POLYMORPHISM = deprecationState('DEPRECATE_NON_EXPLICIT_POLYMORPHISM'); diff --git a/packages/record-data/addon/-private/graph/-edge-definition.ts b/packages/record-data/addon/-private/graph/-edge-definition.ts index cf70beb8040..131357be09d 100644 --- a/packages/record-data/addon/-private/graph/-edge-definition.ts +++ b/packages/record-data/addon/-private/graph/-edge-definition.ts @@ -175,13 +175,30 @@ export function upgradeDefinition( // CASE: Inverse is explicitly null if (definition.inverseKey === null) { + // TODO probably dont need this assertion if polymorphic assert(`Expected the inverse model to exist`, getStore(storeWrapper).modelFor(inverseType)); inverseDefinition = null; } else { inverseKey = inverseForRelationship(getStore(storeWrapper), identifier, propertyName); - // CASE: Inverse resolves to null - if (!inverseKey) { + // CASE: If we are polymorphic, and we declared an inverse that is non-null + // we must assume that the lack of inverseKey means that there is no + // concrete type as the baseType, so we must construct and artificial + // placeholder + if (!inverseKey && definition.isPolymorphic && definition.inverseKey) { + inverseDefinition = { + kind: 'belongsTo', // this must be updated when we find the first belongsTo or hasMany definition that matches + key: definition.inverseKey, + type: type, + isAsync: false, // this must be updated when we find the first belongsTo or hasMany definition that matches + isImplicit: false, + isCollection: false, // this must be updated when we find the first belongsTo or hasMany definition that matches + isPolymorphic: false, + isInitialized: false, // tracks whether we have seen the other side at least once + }; + + // CASE: Inverse resolves to null + } else if (!inverseKey) { inverseDefinition = null; } else { // CASE: We have an explicit inverse or were able to resolve one diff --git a/packages/record-data/addon/-private/graph/operations/replace-related-record.ts b/packages/record-data/addon/-private/graph/operations/replace-related-record.ts index 1417c786ac4..582dad0b799 100644 --- a/packages/record-data/addon/-private/graph/operations/replace-related-record.ts +++ b/packages/record-data/addon/-private/graph/operations/replace-related-record.ts @@ -103,7 +103,17 @@ export default function replaceRelatedRecord(graph: Graph, op: ReplaceRelatedRec if (op.value) { if (definition.type !== op.value.type) { + assert( + `Record of type '${op.value.type}' does not satisfy the relationships ${op.field} on ${definition.type} as the relationship is not polymorphic.`, + definition.isPolymorphic + ); + + // TODO this should now handle the deprecation warning if isPolymorphic is not set + // but the record does turn out to be polymorphic + // this should still assert if the user is relying on legacy inheritance/mixins to + // provide polymorphic behavior and has not yet added the polymorphic flags assertPolymorphicType(relationship.identifier, definition, op.value, graph.store); + graph.registerPolymorphicType(definition.type, op.value.type); } addToInverse(graph, op.value, definition.inverseKey, op.record, isRemote); diff --git a/packages/store/addon/-debug/index.js b/packages/store/addon/-debug/index.js index c0e32c40e09..7ae5a45dae4 100644 --- a/packages/store/addon/-debug/index.js +++ b/packages/store/addon/-debug/index.js @@ -1,6 +1,8 @@ import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; +import { DEPRECATE_NON_EXPLICIT_POLYMORPHISM } from '@ember-data/private-build-infra/deprecations'; + /* Assert that `addedRecord` has a valid type so it can be added to the relationship of the `record`. @@ -29,18 +31,38 @@ if (DEBUG) { }; assertPolymorphicType = function assertPolymorphicType(parentIdentifier, parentDefinition, addedIdentifier, store) { - store = store._store ? store._store : store; // allow usage with storeWrapper - let addedModelName = addedIdentifier.type; - let parentModelName = parentIdentifier.type; - let key = parentDefinition.key; - let relationshipModelName = parentDefinition.type; - let relationshipClass = store.modelFor(relationshipModelName); - let addedClass = store.modelFor(addedModelName); - - let assertionMessage = `The '${addedModelName}' type does not implement '${relationshipModelName}' and thus cannot be assigned to the '${key}' relationship in '${parentModelName}'. Make it a descendant of '${relationshipModelName}' or use a mixin of the same name.`; - let isPolymorphic = checkPolymorphic(relationshipClass, addedClass); - - assert(assertionMessage, isPolymorphic); + let asserted = false; + + if (parentDefinition.inverseIsImplicit) { + return; + } + if (parentDefinition.isPolymorphic) { + let meta = store.getSchemaDefinitionService().relationshipsDefinitionFor(addedIdentifier)[ + parentDefinition.inverseKey + ]; + if (meta?.options?.as) { + asserted = true; + assert( + `The schema for the relationship '${parentDefinition.inverseKey}' on '${addedIdentifier.type}' type does not implement '${parentDefinition.type}' and thus cannot be assigned to the '${parentDefinition.key}' relationship in '${parentIdentifier.type}'. The definition should specify 'as: "${parentDefinition.type}"' in options.`, + meta.options.as === parentDefinition.type + ); + } + } + + if (DEPRECATE_NON_EXPLICIT_POLYMORPHISM && !asserted) { + store = store._store ? store._store : store; // allow usage with storeWrapper + let addedModelName = addedIdentifier.type; + let parentModelName = parentIdentifier.type; + let key = parentDefinition.key; + let relationshipModelName = parentDefinition.type; + let relationshipClass = store.modelFor(relationshipModelName); + let addedClass = store.modelFor(addedModelName); + + let assertionMessage = `The '${addedModelName}' type does not implement '${relationshipModelName}' and thus cannot be assigned to the '${key}' relationship in '${parentModelName}'. Make it a descendant of '${relationshipModelName}' or use a mixin of the same name.`; + let isPolymorphic = checkPolymorphic(relationshipClass, addedClass); + + assert(assertionMessage, isPolymorphic); + } }; } diff --git a/packages/unpublished-relationship-performance-test-app/app/models/car.js b/packages/unpublished-relationship-performance-test-app/app/models/car.js index ad55ea27b46..592471a5916 100644 --- a/packages/unpublished-relationship-performance-test-app/app/models/car.js +++ b/packages/unpublished-relationship-performance-test-app/app/models/car.js @@ -1,6 +1,4 @@ -import DS from 'ember-data'; - -const { Model, belongsTo, hasMany } = DS; +import Model, { belongsTo, hasMany } from '@ember-data/model'; export default Model.extend({ make: belongsTo('make', { async: false, inverse: 'cars' }), diff --git a/packages/unpublished-relationship-performance-test-app/app/models/child.js b/packages/unpublished-relationship-performance-test-app/app/models/child.js index ac3b442da51..c5a94496fdc 100644 --- a/packages/unpublished-relationship-performance-test-app/app/models/child.js +++ b/packages/unpublished-relationship-performance-test-app/app/models/child.js @@ -1,6 +1,4 @@ -import DS from 'ember-data'; - -const { Model, attr, belongsTo, hasMany } = DS; +import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; export default Model.extend({ childName: attr('string'), diff --git a/packages/unpublished-relationship-performance-test-app/app/models/color.js b/packages/unpublished-relationship-performance-test-app/app/models/color.js index 99eaadfc486..955154d2197 100644 --- a/packages/unpublished-relationship-performance-test-app/app/models/color.js +++ b/packages/unpublished-relationship-performance-test-app/app/models/color.js @@ -1,6 +1,4 @@ -import DS from 'ember-data'; - -const { Model, attr, hasMany } = DS; +import Model, { attr, hasMany } from '@ember-data/model'; export default Model.extend({ name: attr('string'), diff --git a/packages/unpublished-relationship-performance-test-app/app/models/make.js b/packages/unpublished-relationship-performance-test-app/app/models/make.js index 007940b5ee6..9b073362775 100644 --- a/packages/unpublished-relationship-performance-test-app/app/models/make.js +++ b/packages/unpublished-relationship-performance-test-app/app/models/make.js @@ -1,6 +1,4 @@ -import DS from 'ember-data'; - -const { Model, attr, hasMany } = DS; +import Model, { attr, hasMany } from '@ember-data/model'; export default Model.extend({ name: attr('string'), diff --git a/packages/unpublished-relationship-performance-test-app/app/models/parent.js b/packages/unpublished-relationship-performance-test-app/app/models/parent.js index c94b54e6f96..e3d030bf6da 100644 --- a/packages/unpublished-relationship-performance-test-app/app/models/parent.js +++ b/packages/unpublished-relationship-performance-test-app/app/models/parent.js @@ -1,6 +1,4 @@ -import DS from 'ember-data'; - -const { Model, attr, hasMany } = DS; +import Model, { attr, hasMany } from '@ember-data/model'; export default Model.extend({ parentName: attr('string'), diff --git a/packages/unpublished-relationship-performance-test-app/app/models/size.js b/packages/unpublished-relationship-performance-test-app/app/models/size.js index 7293e621d4c..692e0d192c8 100644 --- a/packages/unpublished-relationship-performance-test-app/app/models/size.js +++ b/packages/unpublished-relationship-performance-test-app/app/models/size.js @@ -1,6 +1,4 @@ -import DS from 'ember-data'; - -const { Model, attr, hasMany } = DS; +import Model, { attr, hasMany } from '@ember-data/model'; export default Model.extend({ name: attr('string'),