diff --git a/addon/-private/system/model/internal-model.js b/addon/-private/system/model/internal-model.js index d8f99b67a17..936a6385402 100644 --- a/addon/-private/system/model/internal-model.js +++ b/addon/-private/system/model/internal-model.js @@ -1,7 +1,6 @@ -import { assign, merge } from '@ember/polyfills'; import { A } from '@ember/array'; import { set, get } from '@ember/object'; -import { copy } from '@ember/object/internals'; +import { assign } from '@ember/polyfills'; import EmberError from '@ember/error'; import { isEqual } from '@ember/utils'; import { setOwner } from '@ember/application'; @@ -24,8 +23,6 @@ import { HasManyReference } from "../references"; -const emberAssign = assign || merge; - /* 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 @@ -636,7 +633,7 @@ export default class InternalModel { changedKeys = this._changedKeys(data.attributes); } - emberAssign(this._data, data.attributes); + assign(this._data, data.attributes); this.pushedData(); if (this.hasRecord) { @@ -809,7 +806,7 @@ export default class InternalModel { let oldData = this._data; let currentData = this._attributes; let inFlightData = this._inFlightAttributes; - let newData = emberAssign(copy(inFlightData), currentData); + let newData = assign({}, inFlightData, currentData); let diffData = Object.create(null); let newDataKeys = Object.keys(newData); @@ -1173,9 +1170,9 @@ export default class InternalModel { this.didCleanError(); let changedKeys = this._changedKeys(data); - emberAssign(this._data, this._inFlightAttributes); + assign(this._data, this._inFlightAttributes); if (data) { - emberAssign(this._data, data); + assign(this._data, data); } this._inFlightAttributes = null; @@ -1303,8 +1300,8 @@ export default class InternalModel { attrs= this._attributes; } - original = emberAssign(Object.create(null), this._data); - original = emberAssign(original, this._inFlightAttributes); + original = Object.create(null); + assign(original, this._data, this._inFlightAttributes); for (i = 0; i < length; i++) { key = keys[i]; diff --git a/addon/-private/system/snapshot.js b/addon/-private/system/snapshot.js index 8d9d30f1f61..369e868a3f1 100644 --- a/addon/-private/system/snapshot.js +++ b/addon/-private/system/snapshot.js @@ -1,12 +1,10 @@ /** @module ember-data */ - -import { copy } from '@ember/object/internals'; - import { inspect } from '@ember/debug'; import EmberError from '@ember/error'; import { get } from '@ember/object'; +import { assign } from '@ember/polyfills'; /** @class Snapshot @@ -150,7 +148,7 @@ export default class Snapshot { @return {Object} All attributes of the current snapshot */ attributes() { - return copy(this._attributes); + return assign({}, this._attributes); } /** @@ -173,7 +171,7 @@ export default class Snapshot { for (let i=0, length = changedAttributeKeys.length; i < length; i++) { let key = changedAttributeKeys[i]; - changedAttributes[key] = copy(this._changedAttributes[key]); + changedAttributes[key] = this._changedAttributes[key].slice(); } return changedAttributes; diff --git a/addon/-private/system/store.js b/addon/-private/system/store.js index 44ec7050193..f57a811e28c 100644 --- a/addon/-private/system/store.js +++ b/addon/-private/system/store.js @@ -3,12 +3,11 @@ */ import { A } from '@ember/array'; - -import { copy } from '@ember/object/internals'; import EmberError from '@ember/error'; import MapWithDefault from './map-with-default'; import { run as emberRun } from '@ember/runloop'; import { set, get, computed } from '@ember/object'; +import { assign } from '@ember/polyfills'; import { default as RSVP, Promise } from 'rsvp'; import Service from '@ember/service'; import { typeOf, isPresent, isNone } from '@ember/utils'; @@ -321,7 +320,7 @@ Store = Service.extend({ return emberRun.join(() => { return this._backburner.join(() => { let normalizedModelName = normalizeModelName(modelName); - let properties = copy(inputProperties) || Object.create(null); + let properties = assign({}, inputProperties); // If the passed properties do not include a primary key, // give the adapter an opportunity to generate one. Typically, diff --git a/tests/helpers/deep-copy.js b/tests/helpers/deep-copy.js new file mode 100644 index 00000000000..143c31d3dbc --- /dev/null +++ b/tests/helpers/deep-copy.js @@ -0,0 +1,54 @@ +/* global WeakMap */ +export default function deepCopy(obj) { + return _deepCopy(obj, new WeakMap()); +} + +function isPrimitive(value) { + return typeof value !== 'object' || value === null; +} + +function _deepCopy(oldObject, seen) { + if (Array.isArray(oldObject)) { + return copyArray(oldObject, seen); + } else if (!isPrimitive(oldObject)) { + return copyObject(oldObject, seen); + } else { + return oldObject; + } +} + +function copyObject(oldObject, seen) { + let newObject = {}; + + Object.keys(oldObject).forEach(key => { + let value = oldObject[key]; + let newValue = isPrimitive(value) ? value : seen.get(value); + + if (value && newValue === undefined) { + newValue = newObject[key] = _deepCopy(value, seen); + seen.set(value, newValue); + } + + newObject[key] = newValue; + }); + + return newObject; +} + +function copyArray(oldArray, seen) { + let newArray = []; + + for (let i = 0; i < oldArray.length; i++) { + let value = oldArray[i]; + let newValue = isPrimitive(value) ? value : seen.get(value); + + if (value && newValue === undefined) { + newValue = newArray[i] = _deepCopy(value, seen); + seen.set(value, newValue); + } + + newArray[i] = newValue; + } + + return newArray; +} diff --git a/tests/integration/adapter/build-url-mixin-test.js b/tests/integration/adapter/build-url-mixin-test.js index a2cacd1da63..e97535f32b1 100644 --- a/tests/integration/adapter/build-url-mixin-test.js +++ b/tests/integration/adapter/build-url-mixin-test.js @@ -1,8 +1,8 @@ import { decamelize, underscore } from '@ember/string'; -import { copy } from '@ember/object/internals'; import RSVP from 'rsvp'; import { run } from '@ember/runloop'; import setupStore from 'dummy/tests/helpers/store'; +import deepCopy from 'dummy/tests/helpers/deep-copy'; import { pluralize } from 'ember-inflector'; import { module, test } from 'qunit'; @@ -46,7 +46,7 @@ function ajaxResponse(value) { adapter.ajax = function(url, verb, hash) { passedUrl = url; - return run(RSVP, 'resolve', copy(value, true)); + return run(RSVP, 'resolve', deepCopy(value)); }; } diff --git a/tests/integration/adapter/rest-adapter-test.js b/tests/integration/adapter/rest-adapter-test.js index e5ac658334b..cdb94a18b74 100644 --- a/tests/integration/adapter/rest-adapter-test.js +++ b/tests/integration/adapter/rest-adapter-test.js @@ -1,11 +1,10 @@ import { underscore } from '@ember/string'; -import { copy } from '@ember/object/internals'; import RSVP, { resolve, reject } from 'rsvp'; import { run } from '@ember/runloop'; import { get } from '@ember/object'; import setupStore from 'dummy/tests/helpers/store'; import { singularize } from 'ember-inflector'; - +import deepCopy from 'dummy/tests/helpers/deep-copy'; import testInDebug from 'dummy/tests/helpers/test-in-debug'; import { module, test } from 'qunit'; @@ -56,7 +55,7 @@ function ajaxResponse(value) { passedVerb = verb; passedHash = hash; - return run(RSVP, 'resolve', copy(value, true)); + return run(RSVP, 'resolve', deepCopy(value)); }; } diff --git a/tests/integration/records/rematerialize-test.js b/tests/integration/records/rematerialize-test.js index 76dfdaba91c..19e72905cb6 100644 --- a/tests/integration/records/rematerialize-test.js +++ b/tests/integration/records/rematerialize-test.js @@ -1,12 +1,11 @@ /*eslint no-unused-vars: ["error", { "varsIgnorePattern": "(adam|bob|dudu)" }]*/ import { run } from '@ember/runloop'; -import Ember from 'ember'; import setupStore from 'dummy/tests/helpers/store'; +import deepCopy from 'dummy/tests/helpers/deep-copy'; import { module, test } from 'qunit'; import DS from 'ember-data'; -const { copy } = Ember; const { attr, belongsTo, hasMany, Model } = DS; let env; @@ -173,9 +172,9 @@ test("an async has many relationship to an unloaded record can restore that reco let data; if (param === '1') { - data = copy(BOAT_ONE, true); + data = deepCopy(BOAT_ONE); } else if (param === '1') { - data = copy(BOAT_TWO, true); + data = deepCopy(BOAT_TWO); } else { throw new Error(`404: no such boat with id=${param}`); } @@ -208,8 +207,8 @@ test("an async has many relationship to an unloaded record can restore that reco run(() => { env.store.push({ data: [ - copy(BOAT_ONE, true), - copy(BOAT_TWO, true) + deepCopy(BOAT_ONE), + deepCopy(BOAT_TWO) ] }); }); diff --git a/tests/integration/relationships/json-api-links-test.js b/tests/integration/relationships/json-api-links-test.js index 7c094e7b4c3..d92ea3d1879 100644 --- a/tests/integration/relationships/json-api-links-test.js +++ b/tests/integration/relationships/json-api-links-test.js @@ -1,6 +1,5 @@ import { run } from '@ember/runloop'; import { get } from '@ember/object'; -import Ember from 'ember'; import { resolve } from 'rsvp'; import setupStore from 'dummy/tests/helpers/store'; import { @@ -9,8 +8,8 @@ import { import { module, test } from 'qunit'; import DS from 'ember-data'; import JSONAPIAdapter from "ember-data/adapters/json-api"; +import deepCopy from 'dummy/tests/helpers/deep-copy'; -const { copy } = Ember; const { Model, attr, hasMany, belongsTo } = DS; let env, User, Organisation; @@ -711,11 +710,11 @@ function shouldFetchLinkTests(description, payloads) { link === payloads.user.data.relationships.pets.links.related, 'We fetched the appropriate link' ); - return resolve(copy(payloads.pets, true)); + return resolve(deepCopy(payloads.pets)); }; // setup user - let user = run(() => store.push(copy(payloads.user, true))); + let user = run(() => store.push(deepCopy(payloads.user))); let pets = run(() => user.get('pets')); assert.ok(!!pets, 'We found our pets'); @@ -748,11 +747,11 @@ function shouldFetchLinkTests(description, payloads) { 'We fetched the appropriate link' ); } - return resolve(copy(payloads.pets, true)); + return resolve(deepCopy(payloads.pets)); }; // setup user - let user = run(() => store.push(copy(payloads.user, true))); + let user = run(() => store.push(deepCopy(payloads.user))); let pets = run(() => user.get('pets')); assert.ok(!!pets, 'We found our pets'); @@ -790,11 +789,11 @@ function shouldFetchLinkTests(description, payloads) { 'We fetched the appropriate link' ); } - return resolve(copy(payloads.home, true)); + return resolve(deepCopy(payloads.home)); }; // setup user - let user = run(() => store.push(copy(payloads.user, true))); + let user = run(() => store.push(deepCopy(payloads.user))); let home = run(() => user.get('home')); if (homeRelWasEmpty) { @@ -826,11 +825,11 @@ function shouldFetchLinkTests(description, payloads) { link === payloads.user.data.relationships.home.links.related, 'We fetched the appropriate link' ); - return resolve(copy(payloads.home, true)); + return resolve(deepCopy(payloads.home)); }; // setup user - let user = run(() => store.push(copy(payloads.user, true))); + let user = run(() => store.push(deepCopy(payloads.user))); let home = run(() => user.get('home')); assert.ok(!!home, 'We found our home'); @@ -996,12 +995,12 @@ function shouldReloadWithLinkTests(description, payloads) { link === payloads.user.data.relationships.pets.links.related, 'We fetched the appropriate link' ); - return resolve(copy(payloads.pets, true)); + return resolve(deepCopy(payloads.pets)); }; // setup user and pets - let user = run(() => store.push(copy(payloads.user, true))); - run(() => store.push(copy(payloads.pets, true))); + let user = run(() => store.push(deepCopy(payloads.user))); + run(() => store.push(deepCopy(payloads.pets))); let pets = run(() => user.get('pets')); assert.ok(!!pets, 'We found our pets'); @@ -1024,12 +1023,12 @@ function shouldReloadWithLinkTests(description, payloads) { link === payloads.user.data.relationships.pets.links.related, 'We fetched the appropriate link' ); - return resolve(copy(payloads.pets, true)); + return resolve(deepCopy(payloads.pets)); }; // setup user and pets - let user = run(() => store.push(copy(payloads.user, true))); - run(() => store.push(copy(payloads.pets, true))); + let user = run(() => store.push(deepCopy(payloads.user))); + run(() => store.push(deepCopy(payloads.pets))); let pets = run(() => user.get('pets')); assert.ok(!!pets, 'We found our pets'); @@ -1053,12 +1052,12 @@ function shouldReloadWithLinkTests(description, payloads) { link === payloads.user.data.relationships.home.links.related, 'We fetched the appropriate link' ); - return resolve(copy(payloads.home, true)); + return resolve(deepCopy(payloads.home)); }; // setup user and home - let user = run(() => store.push(copy(payloads.user, true))); - run(() => store.push(copy(payloads.home, true))); + let user = run(() => store.push(deepCopy(payloads.user))); + run(() => store.push(deepCopy(payloads.home))); let home = run(() => user.get('home')); assert.ok(!!home, 'We found our home'); @@ -1081,12 +1080,12 @@ function shouldReloadWithLinkTests(description, payloads) { link === payloads.user.data.relationships.home.links.related, 'We fetched the appropriate link' ); - return resolve(copy(payloads.home, true)); + return resolve(deepCopy(payloads.home)); }; // setup user - let user = run(() => store.push(copy(payloads.user, true))); - run(() => store.push(copy(payloads.home, true))); + let user = run(() => store.push(deepCopy(payloads.user))); + run(() => store.push(deepCopy(payloads.home))); let home; run(() => user.get('home').then(h => home = h)); @@ -1908,8 +1907,8 @@ test('We should not fetch a hasMany relationship with links that we know is empt }; // setup users - let user1 = run(() => store.push(copy(user1Payload, true))); - let user2 = run(() => store.push(copy(user2Payload, true))); + let user1 = run(() => store.push(deepCopy(user1Payload))); + let user2 = run(() => store.push(deepCopy(user2Payload))); // should not fire a request requestedUser = null; diff --git a/tests/integration/store-test.js b/tests/integration/store-test.js index 4c787f80aad..489b0622520 100644 --- a/tests/integration/store-test.js +++ b/tests/integration/store-test.js @@ -1,4 +1,3 @@ -import { copy } from '@ember/object/internals'; import RSVP, { Promise as EmberPromise, resolve @@ -7,6 +6,7 @@ import { run, next } from '@ember/runloop'; import setupStore from 'dummy/tests/helpers/store'; import testInDebug from 'dummy/tests/helpers/test-in-debug'; +import deepCopy from 'dummy/tests/helpers/deep-copy'; import { module, test } from 'qunit'; import DS from 'ember-data'; @@ -211,7 +211,7 @@ test("destroying the store correctly cleans everything up", function(assert) { function ajaxResponse(value) { env.adapter.ajax = function(url, verb, hash) { - return run(RSVP, 'resolve', copy(value, true)); + return run(RSVP, 'resolve', deepCopy(value)); }; } diff --git a/tests/unit/system/relationships/polymorphic-relationship-payloads-test.js b/tests/unit/system/relationships/polymorphic-relationship-payloads-test.js index f1a62e54ae3..f14a2e2ec18 100644 --- a/tests/unit/system/relationships/polymorphic-relationship-payloads-test.js +++ b/tests/unit/system/relationships/polymorphic-relationship-payloads-test.js @@ -1,8 +1,8 @@ import { run } from '@ember/runloop'; -import { copy } from '@ember/object/internals'; import { RelationshipPayloadsManager } from 'ember-data/-private'; import DS from 'ember-data'; import { createStore } from 'dummy/tests/helpers/store'; +import deepCopy from 'dummy/tests/helpers/deep-copy'; import { module, test } from 'qunit'; import testInDebug from '../../../helpers/test-in-debug'; @@ -45,7 +45,7 @@ test('push one side is polymorphic, baseType then subTypes', function(assert) { let id = 1; function makeHat(type, props) { - const resource = copy(props, true); + const resource = deepCopy(props); resource.id = `${id++}`; resource.type = type; resource.attributes.type = type; @@ -89,7 +89,7 @@ test('push one side is polymorphic, subType then baseType', function(assert) { let id = 1; function makeHat(type, props) { - const resource = copy(props, true); + const resource = deepCopy(props); resource.id = `${id++}`; resource.type = type; resource.attributes.type = type; @@ -130,7 +130,7 @@ test('push one side is polymorphic, different subtypes', function(assert) { let id = 1; function makeHat(type, props) { - const resource = copy(props, true); + const resource = deepCopy(props); resource.id = `${id++}`; resource.type = type; resource.attributes.type = type; @@ -177,7 +177,7 @@ test('push both sides are polymorphic', function(assert) { let id = 1; function makeHat(type, props) { - const resource = copy(props, true); + const resource = deepCopy(props); resource.id = `${id++}`; resource.type = type; resource.attributes.type = type;