From df2d16e13ba4544740089fce5ff5b65f660f6fd3 Mon Sep 17 00:00:00 2001 From: Pete Date: Wed, 3 Jul 2019 14:08:06 -0700 Subject: [PATCH] [CHORE] RFC-0329 - adding deprecation about using evented in ember-data (#6059) --- .../tests/helpers/deprecated-test.js | 42 +++++++ .../tests/integration/lifecycle-hooks-test.js | 91 ++++++++------- .../relationships/has-many-test.js | 106 ++++++++++-------- packages/-ember-data/tests/unit/model-test.js | 96 ++++++++++------ .../tests/unit/model/merge-test.js | 23 +++- .../-private/system/deprecated-evented.js | 79 +++++++++++++ .../store/addon/-private/system/many-array.js | 7 +- .../addon/-private/system/model/errors.js | 56 +++++---- .../-private/system/model/internal-model.ts | 66 ++++++++--- .../addon/-private/system/model/model.js | 24 ++-- .../adapter-populated-record-array.js | 13 ++- .../system/record-arrays/record-array.js | 10 +- 12 files changed, 427 insertions(+), 186 deletions(-) create mode 100644 packages/-ember-data/tests/helpers/deprecated-test.js create mode 100644 packages/store/addon/-private/system/deprecated-evented.js diff --git a/packages/-ember-data/tests/helpers/deprecated-test.js b/packages/-ember-data/tests/helpers/deprecated-test.js new file mode 100644 index 00000000000..5d83041195d --- /dev/null +++ b/packages/-ember-data/tests/helpers/deprecated-test.js @@ -0,0 +1,42 @@ +import { test } from 'qunit'; +import VERSION from 'ember-data/version'; + +// small comparison function for major and minor semver values +function gte(EDVersion, DeprecationVersion) { + let _edv = EDVersion.split('.'); + let _depv = DeprecationVersion.split('.'); + // compare major + let major = +_edv[0] >= +_depv[0]; + // compare minor + let minor = +_edv[1] >= +_depv[1]; + return major || minor; +} + +export function deprecatedTest(testName, deprecation, testCallback) { + // '4.0' + if (typeof deprecation.until !== 'string' || deprecation.until.length < 3) { + throw new Error(`deprecatedTest expects { until } to be a version.`); + } + // 'ds.' + if (typeof deprecation.id !== 'string' || deprecation.id.length < 8) { + throw new Error(`deprecatedTest expects { id } to be a meaningful string`); + } + + if (gte(VERSION, deprecation.until)) { + test(`DEPRECATION ${deprecation.id} until ${deprecation.until} | ${testName}`, testCallback); + } else { + test(`DEPRECATION ${deprecation.id} until ${deprecation.until} | ${testName}`, function(assert) { + if (deprecation.refactor === true) { + assert.ok( + false, + 'This test includes use of a deprecated feature that should now be refactored.' + ); + } else { + assert.ok( + false, + 'This test is for a deprecated feature whose time has come and should be removed' + ); + } + }); + } +} diff --git a/packages/-ember-data/tests/integration/lifecycle-hooks-test.js b/packages/-ember-data/tests/integration/lifecycle-hooks-test.js index f14b6f453ef..e4f3dae82e2 100644 --- a/packages/-ember-data/tests/integration/lifecycle-hooks-test.js +++ b/packages/-ember-data/tests/integration/lifecycle-hooks-test.js @@ -1,8 +1,9 @@ import { resolve } from 'rsvp'; import { run } from '@ember/runloop'; +import { deprecatedTest } from 'dummy/tests/helpers/deprecated-test'; import setupStore from 'dummy/tests/helpers/store'; -import { module, test } from 'qunit'; +import { module } from 'qunit'; import DS from 'ember-data'; @@ -24,41 +25,55 @@ module('integration/lifecycle_hooks - Lifecycle Hooks', function(hooks) { run(env.container, 'destroy'); }); - test('When the adapter acknowledges that a record has been created, a `didCreate` event is triggered.', function(assert) { - let done = assert.async(); - assert.expect(3); - - env.adapter.createRecord = function(store, type, snapshot) { - return resolve({ data: { id: 99, type: 'person', attributes: { name: 'Yehuda Katz' } } }); - }; - - let person = env.store.createRecord('person', { name: 'Yehuda Katz' }); - - person.on('didCreate', function() { - assert.equal(this, person, 'this is bound to the record'); - assert.equal(this.get('id'), '99', 'the ID has been assigned'); - assert.equal(this.get('name'), 'Yehuda Katz', 'the attribute has been assigned'); - done(); - }); - - run(person, 'save'); - }); - - test('When the adapter acknowledges that a record has been created without a new data payload, a `didCreate` event is triggered.', function(assert) { - assert.expect(3); - - env.adapter.createRecord = function(store, type, snapshot) { - return resolve(); - }; - - let person = env.store.createRecord('person', { id: 99, name: 'Yehuda Katz' }); - - person.on('didCreate', function() { - assert.equal(this, person, 'this is bound to the record'); - assert.equal(this.get('id'), '99', 'the ID has been assigned'); - assert.equal(this.get('name'), 'Yehuda Katz', 'the attribute has been assigned'); - }); - - run(person, 'save'); - }); + deprecatedTest( + 'When the adapter acknowledges that a record has been created, a `didCreate` event is triggered.', + { + id: 'ember-data:evented-api-usage', + until: '4.0', + }, + function(assert) { + let done = assert.async(); + assert.expect(3); + + env.adapter.createRecord = function(store, type, snapshot) { + return resolve({ data: { id: 99, type: 'person', attributes: { name: 'Yehuda Katz' } } }); + }; + + let person = env.store.createRecord('person', { name: 'Yehuda Katz' }); + + person.on('didCreate', function() { + assert.equal(this, person, 'this is bound to the record'); + assert.equal(this.get('id'), '99', 'the ID has been assigned'); + assert.equal(this.get('name'), 'Yehuda Katz', 'the attribute has been assigned'); + done(); + }); + + run(person, 'save'); + } + ); + + deprecatedTest( + 'When the adapter acknowledges that a record has been created without a new data payload, a `didCreate` event is triggered.', + { + id: 'ember-data:evented-api-usage', + until: '4.0', + }, + function(assert) { + assert.expect(3); + + env.adapter.createRecord = function(store, type, snapshot) { + return resolve(); + }; + + let person = env.store.createRecord('person', { id: 99, name: 'Yehuda Katz' }); + + person.on('didCreate', function() { + assert.equal(this, person, 'this is bound to the record'); + assert.equal(this.get('id'), '99', 'the ID has been assigned'); + assert.equal(this.get('name'), 'Yehuda Katz', 'the attribute has been assigned'); + }); + + run(person, 'save'); + } + ); }); 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 0ffe1b07c62..37f2e31ecf4 100644 --- a/packages/-ember-data/tests/integration/relationships/has-many-test.js +++ b/packages/-ember-data/tests/integration/relationships/has-many-test.js @@ -6,6 +6,7 @@ import { get } from '@ember/object'; import { run } from '@ember/runloop'; import setupStore from 'dummy/tests/helpers/store'; import testInDebug from 'dummy/tests/helpers/test-in-debug'; +import { deprecatedTest } from 'dummy/tests/helpers/deprecated-test'; import { module, test } from 'qunit'; import { relationshipStateFor, relationshipsFor } from 'ember-data/-private'; import DS from 'ember-data'; @@ -1275,71 +1276,78 @@ module('integration/relationships/has_many - Has-Many Relationships', function(h }); }); - test('PromiseArray proxies evented methods to its ManyArray', function(assert) { - assert.expect(6); - - Post.reopen({ - comments: DS.hasMany('comment', { async: true }), - }); + deprecatedTest( + 'PromiseArray proxies evented methods to its ManyArray', + { + id: 'ember-data:evented-api-usage', + until: '4.0', + }, + function(assert) { + assert.expect(6); - env.adapter.findHasMany = function(store, snapshot, link, relationship) { - return resolve({ - data: [ - { id: 1, type: 'comment', attributes: { body: 'First' } }, - { id: 2, type: 'comment', attributes: { body: 'Second' } }, - ], + Post.reopen({ + comments: DS.hasMany('comment', { async: true }), }); - }; - let post, comments; - run(function() { - env.store.push({ - data: { - type: 'post', - id: '1', - relationships: { - comments: { - links: { - related: 'someLink', + env.adapter.findHasMany = function(store, snapshot, link, relationship) { + return resolve({ + data: [ + { id: 1, type: 'comment', attributes: { body: 'First' } }, + { id: 2, type: 'comment', attributes: { body: 'Second' } }, + ], + }); + }; + let post, comments; + + run(function() { + env.store.push({ + data: { + type: 'post', + id: '1', + relationships: { + comments: { + links: { + related: 'someLink', + }, }, }, }, - }, + }); + post = env.store.peekRecord('post', 1); + comments = post.get('comments'); }); - post = env.store.peekRecord('post', 1); - comments = post.get('comments'); - }); - comments.on('on-event', function() { - assert.ok(true); - }); + comments.on('on-event', function() { + assert.ok(true); + }); - run(function() { - comments.trigger('on-event'); - }); + run(function() { + comments.trigger('on-event'); + }); - assert.equal(comments.has('on-event'), true); - const cb = function() { - assert.ok(false, 'We should not trigger this event'); - }; + assert.equal(comments.has('on-event'), true); + const cb = function() { + assert.ok(false, 'We should not trigger this event'); + }; - comments.on('off-event', cb); - comments.off('off-event', cb); + comments.on('off-event', cb); + comments.off('off-event', cb); - assert.equal(comments.has('off-event'), false); + assert.equal(comments.has('off-event'), false); - comments.one('one-event', function() { - assert.ok(true); - }); + comments.one('one-event', function() { + assert.ok(true); + }); - assert.equal(comments.has('one-event'), true); + assert.equal(comments.has('one-event'), true); - run(function() { - comments.trigger('one-event'); - }); + run(function() { + comments.trigger('one-event'); + }); - assert.equal(comments.has('one-event'), false); - }); + assert.equal(comments.has('one-event'), false); + } + ); test('An updated `links` value should invalidate a relationship cache', function(assert) { assert.expect(8); diff --git a/packages/-ember-data/tests/unit/model-test.js b/packages/-ember-data/tests/unit/model-test.js index cd64c6ab9fe..63bf4cace9a 100644 --- a/packages/-ember-data/tests/unit/model-test.js +++ b/packages/-ember-data/tests/unit/model-test.js @@ -3,6 +3,7 @@ import { resolve, reject } from 'rsvp'; import { set, get, observer, computed } from '@ember/object'; import testInDebug from 'dummy/tests/helpers/test-in-debug'; import { module, test } from 'qunit'; +import { deprecatedTest } from 'dummy/tests/helpers/deprecated-test'; import { settled } from '@ember/test-helpers'; import { setupTest } from 'ember-qunit'; import Model from '@ember-data/model'; @@ -607,58 +608,79 @@ module('unit/model - Model', function(hooks) { }); module('Evented', function() { - test('an event listener can be added to a record', async function(assert) { - let count = 0; - let F = function() { - count++; - }; + deprecatedTest( + 'an event listener can be added to a record', + { + id: 'ember-data:evented-api-usage', + until: '4.0', + }, + async function(assert) { + let count = 0; + let F = function() { + count++; + }; - let record = store.createRecord('person'); + let record = store.createRecord('person'); - record.on('event!', F); - record.trigger('event!'); + record.on('event!', F); + record.trigger('event!'); - await settled(); + await settled(); - assert.equal(count, 1, 'the event was triggered'); - record.trigger('event!'); + assert.equal(count, 1, 'the event was triggered'); + record.trigger('event!'); - await settled(); + await settled(); - assert.equal(count, 2, 'the event was triggered'); - }); + assert.equal(count, 2, 'the event was triggered'); + } + ); - test('when an event is triggered on a record the method with the same name is invoked with arguments', async function(assert) { - let count = 0; - let F = function() { - count++; - }; - let record = store.createRecord('person'); + deprecatedTest( + 'when an event is triggered on a record the method with the same name is invoked with arguments', + { + id: 'ember-data:evented-api-usage', + until: '4.0', + }, + async function(assert) { + let count = 0; + let F = function() { + count++; + }; + let record = store.createRecord('person'); - record.eventNamedMethod = F; + record.eventNamedMethod = F; - record.trigger('eventNamedMethod'); + record.trigger('eventNamedMethod'); - await settled(); + await settled(); - assert.equal(count, 1, 'the corresponding method was called'); - }); + assert.equal(count, 1, 'the corresponding method was called'); + } + ); - test('when a method is invoked from an event with the same name the arguments are passed through', async function(assert) { - let eventMethodArgs = null; - let F = function() { - eventMethodArgs = arguments; - }; - let record = store.createRecord('person'); + deprecatedTest( + 'when a method is invoked from an event with the same name the arguments are passed through', + { + id: 'ember-data:evented-api-usage', + until: '4.0', + }, + async function(assert) { + let eventMethodArgs = null; + let F = function() { + eventMethodArgs = arguments; + }; + let record = store.createRecord('person'); - record.eventThatTriggersMethod = F; - record.trigger('eventThatTriggersMethod', 1, 2); + record.eventThatTriggersMethod = F; + record.trigger('eventThatTriggersMethod', 1, 2); - await settled(); + await settled(); - assert.equal(eventMethodArgs[0], 1); - assert.equal(eventMethodArgs[1], 2); - }); + assert.equal(eventMethodArgs[0], 1); + assert.equal(eventMethodArgs[1], 2); + } + ); }); module('Reserved Props', function() { diff --git a/packages/-ember-data/tests/unit/model/merge-test.js b/packages/-ember-data/tests/unit/model/merge-test.js index c5b4d89fc8e..cf814ed4c66 100644 --- a/packages/-ember-data/tests/unit/model/merge-test.js +++ b/packages/-ember-data/tests/unit/model/merge-test.js @@ -1,7 +1,7 @@ -import { resolve, Promise as EmberPromise } from 'rsvp'; +import { resolve, reject, Promise as EmberPromise } from 'rsvp'; import { run } from '@ember/runloop'; import { createStore } from 'dummy/tests/helpers/store'; - +import { InvalidError } from '@ember-data/adapter/error'; import { module, test } from 'qunit'; import DS from 'ember-data'; @@ -207,12 +207,17 @@ module('unit/model/merge - Merging', function(hooks) { ); }); - test('When a record is invalid, pushes are overridden by local changes', function(assert) { + test('When a record is invalid, pushes are overridden by local changes', async function(assert) { let store = createStore({ adapter: DS.Adapter, person: Person, }); let person; + let adapter = store.adapterFor('application'); + + adapter.updateRecord = () => { + return reject(new InvalidError()); + }; run(() => { person = store.push({ @@ -225,12 +230,18 @@ module('unit/model/merge - Merging', function(hooks) { }, }, }); - person.set('name', 'Brondan McLoughlin'); - person.send('becameInvalid'); }); - assert.equal(person.get('hasDirtyAttributes'), true, 'the person is currently dirty'); + person.set('name', 'Brondan McLoughlin'); + + try { + await person.save(); + assert.ok(false, 'We should throw during save'); + } catch (e) { + assert.ok(true, 'We rejected the save'); + } assert.equal(person.get('isValid'), false, 'the person is currently invalid'); + assert.equal(person.get('hasDirtyAttributes'), true, 'the person is currently dirty'); assert.equal(person.get('name'), 'Brondan McLoughlin', 'the update was effective'); assert.equal(person.get('city'), 'Boston', 'the original data applies'); diff --git a/packages/store/addon/-private/system/deprecated-evented.js b/packages/store/addon/-private/system/deprecated-evented.js new file mode 100644 index 00000000000..aed29dff36d --- /dev/null +++ b/packages/store/addon/-private/system/deprecated-evented.js @@ -0,0 +1,79 @@ +import Evented from '@ember/object/evented'; +import Mixin from '@ember/object/mixin'; +import { deprecate } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; + +let INSTANCE_DEPRECATIONS; +let lookupDeprecations; +let DeprecatedEvented; + +if (DEBUG) { + INSTANCE_DEPRECATIONS = new WeakMap(); + + lookupDeprecations = function lookupInstanceDrecations(instance) { + let deprecations = INSTANCE_DEPRECATIONS.get(instance); + + if (!deprecations) { + deprecations = {}; + INSTANCE_DEPRECATIONS.set(instance, deprecations); + } + + return deprecations; + }; + + DeprecatedEvented = Mixin.create(Evented, { + /** + * Provides a way to call Evented without logging deprecation warnings + * @param {String} name + */ + _has(name) { + return Evented.mixins[0].properties.has.call(this, name); + }, + + _on() { + return Evented.mixins[0].properties.on.call(this, ...arguments); + }, + + _deprecateEvented(eventName) { + let deprecations = lookupDeprecations(this); + const _deprecationData = this._getDeprecatedEventedInfo + ? `on ${this._getDeprecatedEventedInfo()}` + : ''; + const deprecationMessage = _deprecationData + ? `Called ${eventName} ${_deprecationData}` + : eventName; + deprecate(deprecationMessage, deprecations[eventName], { + id: 'ember-data:evented-api-usage', + until: '4.0', + }); + deprecations[eventName] = true; + }, + + has(name) { + this._deprecateEvented(name); + return this._super(...arguments); + }, + + off(name, target, method) { + this._deprecateEvented(name); + return this._super(...arguments); + }, + + on(name, target, method) { + this._deprecateEvented(name); + return this._super(...arguments); + }, + + one(name, target, method) { + this._deprecateEvented(name); + return this._super(...arguments); + }, + + trigger(name) { + this._deprecateEvented(name); + return this._super(...arguments); + }, + }); +} + +export default DEBUG ? DeprecatedEvented : Evented; diff --git a/packages/store/addon/-private/system/many-array.js b/packages/store/addon/-private/system/many-array.js index e881ad9fee4..8db9beee3c5 100644 --- a/packages/store/addon/-private/system/many-array.js +++ b/packages/store/addon/-private/system/many-array.js @@ -3,7 +3,8 @@ */ import { all } from 'rsvp'; -import Evented from '@ember/object/evented'; +//import Evented from '@ember/object/evented'; +import DeprecatedEvent from './deprecated-evented'; import MutableArray from '@ember/array/mutable'; import EmberArray from '@ember/array'; import EmberObject, { get } from '@ember/object'; @@ -53,9 +54,9 @@ import recordDataFor from './record-data-for'; @class ManyArray @extends EmberObject - @uses Ember.MutableArray, Ember.Evented + @uses Ember.MutableArray, EmberData.DeprecatedEvent */ -export default EmberObject.extend(MutableArray, Evented, { +export default EmberObject.extend(MutableArray, DeprecatedEvent, { // here to make TS happy _inverseIsAsync: false, isLoaded: false, diff --git a/packages/store/addon/-private/system/model/errors.js b/packages/store/addon/-private/system/model/errors.js index d6b34c8716b..f3af4ff9bd1 100644 --- a/packages/store/addon/-private/system/model/errors.js +++ b/packages/store/addon/-private/system/model/errors.js @@ -1,8 +1,9 @@ import { mapBy, not } from '@ember/object/computed'; -import Evented from '@ember/object/evented'; +import DeprecatedEvent from '../deprecated-evented'; import ArrayProxy from '@ember/array/proxy'; import { get, computed } from '@ember/object'; import { makeArray, A } from '@ember/array'; +import { DEBUG } from '@glimmer/env'; /** @module ember-data @@ -81,16 +82,18 @@ import { makeArray, A } from '@ember/array'; @extends Ember.ArrayProxy @uses Ember.Evented */ -export default ArrayProxy.extend(Evented, { +export default ArrayProxy.extend(DeprecatedEvent, { /** Register with target handler @method _registerHandlers @private */ - _registerHandlers(target, becameInvalid, becameValid) { - this.on('becameInvalid', target, becameInvalid); - this.on('becameValid', target, becameValid); + _registerHandlers(becameInvalid, becameValid) { + this._registeredHandlers = { + becameInvalid, + becameValid, + }; }, /** @@ -190,14 +193,14 @@ export default ArrayProxy.extend(Evented, { Example ```javascript let errors = get(user, 'errors'); - + // add multiple errors errors.add('password', [ 'Must be at least 12 characters', 'Must contain at least one symbol', 'Cannot contain your name' ]); - + errors.errorsFor('password'); // => // [ @@ -205,7 +208,7 @@ export default ArrayProxy.extend(Evented, { // { attribute: 'password', message: 'Must contain at least one symbol' }, // { attribute: 'password', message: 'Cannot contain your name' }, // ] - + // add a single error errors.add('username', 'This field is required'); @@ -225,7 +228,10 @@ export default ArrayProxy.extend(Evented, { this._add(attribute, messages); if (wasEmpty && !get(this, 'isEmpty')) { - this.trigger('becameInvalid'); + this._registeredHandlers && this._registeredHandlers.becameInvalid(); + if (DEBUG && this._has('becameInvalid')) { + this.trigger('becameInvalid'); + } } }, @@ -279,16 +285,16 @@ export default ArrayProxy.extend(Evented, { ```javascript let errors = get('user', errors); errors.add('phone', ['error-1', 'error-2']); - + errors.errorsFor('phone'); // => // [ // { attribute: 'phone', message: 'error-1' }, // { attribute: 'phone', message: 'error-2' }, // ] - + errors.remove('phone'); - + errors.errorsFor('phone'); // => undefined ``` @@ -303,7 +309,10 @@ export default ArrayProxy.extend(Evented, { this._remove(attribute); if (get(this, 'isEmpty')) { - this.trigger('becameValid'); + this._registeredHandlers && this._registeredHandlers.becameValid(); + if (DEBUG && this._has('becameValid')) { + this.trigger('becameValid'); + } } }, @@ -330,35 +339,35 @@ export default ArrayProxy.extend(Evented, { Manually clears all errors for the record. This will transition the record into a `valid` state, and will trigger the `becameValid` event and lifecycle method. - + Example: - + ```javascript let errors = get('user', errors); errors.add('username', ['error-a']); errors.add('phone', ['error-1', 'error-2']); - + errors.errorsFor('username'); // => // [ // { attribute: 'username', message: 'error-a' }, // ] - + errors.errorsFor('phone'); // => // [ // { attribute: 'phone', message: 'error-1' }, // { attribute: 'phone', message: 'error-2' }, // ] - + errors.clear(); - + errors.errorsFor('username'); // => undefined - + errors.errorsFor('phone'); // => undefined - + errors.get('messages') // => [] ``` @@ -370,7 +379,10 @@ export default ArrayProxy.extend(Evented, { } this._clear(); - this.trigger('becameValid'); + this._registeredHandlers && this._registeredHandlers.becameValid(); + if (DEBUG && this._has('becameValid')) { + this.trigger('becameValid'); + } }, /** diff --git a/packages/store/addon/-private/system/model/internal-model.ts b/packages/store/addon/-private/system/model/internal-model.ts index 35f2e0c66db..e22dbf0dc7e 100644 --- a/packages/store/addon/-private/system/model/internal-model.ts +++ b/packages/store/addon/-private/system/model/internal-model.ts @@ -8,6 +8,8 @@ import RSVP, { Promise, resolve } from 'rsvp'; import Ember from 'ember'; import { DEBUG } from '@glimmer/env'; import { assert, inspect } from '@ember/debug'; +import { deprecate } from '@ember/application/deprecations'; +import Model from './model'; import RootState from './states'; import Snapshot from '../snapshot'; import OrderedSet from '../ordered-set'; @@ -37,6 +39,24 @@ interface BelongsToMetaWrapper { modelName: string; } +let INSTANCE_DEPRECATIONS; +let lookupDeprecations; + +if (DEBUG) { + INSTANCE_DEPRECATIONS = new WeakMap(); + + lookupDeprecations = function lookupInstanceDrecations(instance) { + let deprecations = INSTANCE_DEPRECATIONS.get(instance); + + if (!deprecations) { + deprecations = {}; + INSTANCE_DEPRECATIONS.set(instance, deprecations); + } + + return deprecations; + }; +} + /* The TransitionChainMap caches the `state.enters`, `state.setups`, and final state reached when transitioning from one state to another, so that future transitions can replay the @@ -367,6 +387,31 @@ export default class InternalModel { setOwner(createOptions, getOwner(store)); this._record = store._modelFactoryFor(this.modelName).create(createOptions); + if (DEBUG) { + let klass = this._record.constructor; + let deprecations = lookupDeprecations(klass); + [ + 'becameError', + 'becameInvalid', + 'didCreate', + 'didDelete', + 'didLoad', + 'didUpdate', + 'ready', + 'rolledBack', + ].forEach(methodName => { + if (this instanceof Model && typeof this._record[methodName] === 'function') { + deprecate( + `Attempted to define ${methodName} on ${this._record.modelName}#${this._record.id}`, + deprecations[methodName], + { + id: 'ember-data:record-lifecycle-event-methods', + until: '4.0', + } + ); + } + }); + } this._triggerDeferredTriggers(); } @@ -713,9 +758,7 @@ export default class InternalModel { return this._updatePromiseProxyFor('hasMany', key, { promise, content: manyArray }); } else { assert( - `You looked up the '${key}' relationship on a '${this.type.modelName}' with id ${ - this.id - } but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async ('DS.hasMany({ async: true })')`, + `You looked up the '${key}' relationship on a '${this.type.modelName}' with id ${this.id} but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async ('DS.hasMany({ async: true })')`, !manyArray.anyUnloaded() ); @@ -957,25 +1000,13 @@ export default class InternalModel { @param {String} name @param {Object} context */ - send(name, context?, fromModel?) { + send(name, context?) { let currentState = this.currentState; if (!currentState[name]) { this._unhandledEvent(currentState, name, context); } - if (RECORD_DATA_ERRORS) { - if ( - fromModel && - name === 'becameInvalid' && - this._recordData.getErrors && - this._recordData.getErrors({}).length === 0 - ) { - // this is a very specific internal hack for backsupport for record.send('becameInvalid') - let jsonApiErrors = [{ title: 'Invalid Error', detail: '', source: { pointer: '/data' } }]; - this._recordData.commitWasRejected({}, jsonApiErrors); - } - } return currentState[name](this, context); } @@ -1154,7 +1185,8 @@ export default class InternalModel { let record = this._record; let trigger = record.trigger; for (let i = 0, l = triggers.length; i < l; i++) { - trigger.apply(record, triggers[i]); + let eventName = triggers[i]; + trigger.apply(record, eventName); } triggers.length = 0; diff --git a/packages/store/addon/-private/system/model/model.js b/packages/store/addon/-private/system/model/model.js index cf6d2c4fae7..d413dafc726 100644 --- a/packages/store/addon/-private/system/model/model.js +++ b/packages/store/addon/-private/system/model/model.js @@ -1,6 +1,6 @@ import { isNone } from '@ember/utils'; import EmberError from '@ember/error'; -import Evented from '@ember/object/evented'; +import DeprecatedEvented from '../deprecated-evented'; import EmberObject, { computed, get } from '@ember/object'; import { DEBUG } from '@glimmer/env'; import { assert, warn, deprecate } from '@ember/debug'; @@ -104,9 +104,9 @@ if (RECORD_DATA_STATE) { @class Model @extends EmberObject - @uses Ember.Evented + @uses EmberData.DeprecatedEvented */ -const Model = EmberObject.extend(Evented, { +const Model = EmberObject.extend(DeprecatedEvented, { init() { this._super(...arguments); if (RECORD_DATA_ERRORS) { @@ -455,11 +455,10 @@ const Model = EmberObject.extend(Evented, { let errors = Errors.create(); errors._registerHandlers( - this._internalModel, - function() { + () => { this.send('becameInvalid'); }, - function() { + () => { this.send('becameValid'); } ); @@ -615,7 +614,7 @@ const Model = EmberObject.extend(Evented, { @param {Object} context */ send(name, context) { - return this._internalModel.send(name, context, true); + return this._internalModel.send(name, context); }, /** @@ -709,7 +708,7 @@ const Model = EmberObject.extend(Evented, { }, /** - Unloads the record from the store. This will not send a delete request + Unloads the record from the store. This will not send a delete request to your server, it just unloads the record from memory. @method unloadRecord @@ -934,7 +933,10 @@ const Model = EmberObject.extend(Evented, { fn.apply(this, args); } - this._super(...arguments); + const _hasEvent = DEBUG ? this._has(name) : this.has(name); + if (_hasEvent) { + this._super(...arguments); + } }, attr() { @@ -1290,6 +1292,10 @@ if (DEBUG) { init() { this._super(...arguments); + if (DEBUG) { + this._getDeprecatedEventedInfo = () => `${this._internalModel.modelName}#${this.id}`; + } + if (!this._internalModel) { throw new EmberError( 'You should not call `create` on a model. Instead, call `store.createRecord` with the attributes you would like to set.' diff --git a/packages/store/addon/-private/system/record-arrays/adapter-populated-record-array.js b/packages/store/addon/-private/system/record-arrays/adapter-populated-record-array.js index dcf6b9ece5c..be8c902b901 100644 --- a/packages/store/addon/-private/system/record-arrays/adapter-populated-record-array.js +++ b/packages/store/addon/-private/system/record-arrays/adapter-populated-record-array.js @@ -3,6 +3,7 @@ import { A } from '@ember/array'; import { get } from '@ember/object'; import RecordArray from './record-array'; import cloneNull from '../clone-null'; +import { DEBUG } from '@glimmer/env'; /** Represents an ordered list of records whose order and membership is @@ -49,6 +50,11 @@ export default RecordArray.extend({ this._super(...arguments); this.query = this.query || null; this.links = this.links || null; + + if (DEBUG) { + this._getDeprecatedEventedInfo = () => + `AdapterPopulatedRecordArray containing ${this.modelName} for query: ${this.query}`; + } }, replace() { @@ -83,7 +89,10 @@ export default RecordArray.extend({ this.manager._associateWithRecordArray(internalModels, this); - // TODO: should triggering didLoad event be the last action of the runLoop? - once(this, 'trigger', 'didLoad'); + const _hasDidLoad = DEBUG ? this._has('didLoad') : this.has('didLoad'); + if (_hasDidLoad) { + // TODO: should triggering didLoad event be the last action of the runLoop? + once(this, 'trigger', 'didLoad'); + } }, }); diff --git a/packages/store/addon/-private/system/record-arrays/record-array.js b/packages/store/addon/-private/system/record-arrays/record-array.js index 9c314aedcf2..8eaa7c80802 100644 --- a/packages/store/addon/-private/system/record-arrays/record-array.js +++ b/packages/store/addon/-private/system/record-arrays/record-array.js @@ -1,12 +1,12 @@ /** @module ember-data */ - -import Evented from '@ember/object/evented'; +import DeprecatedEvented from '../deprecated-evented'; import ArrayProxy from '@ember/array/proxy'; import { set, get, computed } from '@ember/object'; import { Promise } from 'rsvp'; +import { DEBUG } from '@glimmer/env'; import { PromiseArray } from '../promise-proxies'; import SnapshotRecordArray from '../snapshot-record-array'; @@ -22,10 +22,14 @@ import SnapshotRecordArray from '../snapshot-record-array'; @uses Ember.Evented */ -export default ArrayProxy.extend(Evented, { +export default ArrayProxy.extend(DeprecatedEvented, { init() { this._super(...arguments); + if (DEBUG) { + this._getDeprecatedEventedInfo = () => `RecordArray containing ${this.modelName}`; + } + /** The array of client ids backing the record array. When a record is requested from the record array, the record