diff --git a/packages/ember-data/lib/adapters/rest-adapter.js b/packages/ember-data/lib/adapters/rest-adapter.js index 1d6ad807827..d0ac82e6fd9 100644 --- a/packages/ember-data/lib/adapters/rest-adapter.js +++ b/packages/ember-data/lib/adapters/rest-adapter.js @@ -12,6 +12,7 @@ import { import { MapWithDefault } from "ember-data/system/map"; +import EmptyObject from "ember-data/system/empty-object"; import ArrayPolyfills from 'ember-data/ext/ember/array'; var get = Ember.get; @@ -19,7 +20,6 @@ var set = Ember.set; var forEach = ArrayPolyfills.forEach; import { - create, keysFunc } from 'ember-data/system/object-polyfills'; @@ -1042,7 +1042,7 @@ var RestAdapter = Adapter.extend(BuildURLMixin, { }); function parseResponseHeaders(headerStr) { - var headers = create(null); + var headers = new EmptyObject(); if (!headerStr) { return headers; } var headerPairs = headerStr.split('\u000d\u000a'); diff --git a/packages/ember-data/lib/system/clone-null.js b/packages/ember-data/lib/system/clone-null.js index dbf6a02b452..2eada746818 100644 --- a/packages/ember-data/lib/system/clone-null.js +++ b/packages/ember-data/lib/system/clone-null.js @@ -1,9 +1,7 @@ -import { - create -} from 'ember-data/system/object-polyfills'; +import EmptyObject from "ember-data/system/empty-object"; export default function cloneNull(source) { - var clone = create(null); + var clone = new EmptyObject(); for (var key in source) { clone[key] = source[key]; } diff --git a/packages/ember-data/lib/system/empty-object.js b/packages/ember-data/lib/system/empty-object.js new file mode 100644 index 00000000000..f90e0d3b125 --- /dev/null +++ b/packages/ember-data/lib/system/empty-object.js @@ -0,0 +1,17 @@ +// This exists because `Object.create(null)` is absurdly slow compared +// to `new EmptyObject()`. In either case, you want a null prototype +// when you're treating the object instances as arbitrary dictionaries +// and don't want your keys colliding with build-in methods on the +// default object prototype. +var proto = Object.create(null, { + // without this, we will always still end up with (new + // EmptyObject()).constructor === Object + constructor: { + value: undefined, + enumerable: false, + writable: true + } +}); + +export default function EmptyObject() {} +EmptyObject.prototype = proto; diff --git a/packages/ember-data/lib/system/model/internal-model.js b/packages/ember-data/lib/system/model/internal-model.js index 43ddcd05a7a..5c24d137ef6 100644 --- a/packages/ember-data/lib/system/model/internal-model.js +++ b/packages/ember-data/lib/system/model/internal-model.js @@ -2,6 +2,8 @@ import merge from "ember-data/system/merge"; import RootState from "ember-data/system/model/states"; import Relationships from "ember-data/system/relationships/state/create"; import Snapshot from "ember-data/system/snapshot"; +import EmptyObject from "ember-data/system/empty-object"; + import ArrayPolyfills from 'ember-data/ext/ember/array'; import { @@ -16,8 +18,8 @@ var set = Ember.set; var forEach = ArrayPolyfills.forEach; var map = ArrayPolyfills.map; -var _extractPivotNameCache = create(null); -var _splitOnDotCache = create(null); +var _extractPivotNameCache = new EmptyObject(); +var _splitOnDotCache = new EmptyObject(); function splitOnDot(name) { return _splitOnDotCache[name] || ( diff --git a/packages/ember-data/lib/system/model/model.js b/packages/ember-data/lib/system/model/model.js index 35a58c403e5..96d74ecada6 100644 --- a/packages/ember-data/lib/system/model/model.js +++ b/packages/ember-data/lib/system/model/model.js @@ -1,9 +1,9 @@ import { PromiseObject } from "ember-data/system/promise-proxies"; import Errors from "ember-data/system/model/errors"; +import EmptyObject from "ember-data/system/empty-object"; import ArrayPolyfills from 'ember-data/ext/ember/array'; import { - create, keysFunc } from 'ember-data/system/object-polyfills'; @@ -674,7 +674,7 @@ var Model = Ember.Object.extend(Ember.Evented, { var currentData = get(this._internalModel, '_attributes'); var inFlightData = get(this._internalModel, '_inFlightAttributes'); var newData = merge(copy(inFlightData), currentData); - var diffData = create(null); + var diffData = new EmptyObject(); var newDataKeys = keysFunc(newData); diff --git a/packages/ember-data/lib/system/model/states.js b/packages/ember-data/lib/system/model/states.js index 07a4b269b79..04649f1f836 100644 --- a/packages/ember-data/lib/system/model/states.js +++ b/packages/ember-data/lib/system/model/states.js @@ -1,3 +1,5 @@ +import EmptyObject from "ember-data/system/empty-object"; + import { create, keysFunc @@ -360,7 +362,7 @@ var DirtyState = { }, exit: function(internalModel) { - internalModel._inFlightAttributes = create(null); + internalModel._inFlightAttributes = new EmptyObject(); } } }; diff --git a/packages/ember-data/lib/system/relationships/ext.js b/packages/ember-data/lib/system/relationships/ext.js index f4b61fe701b..c0d1d49efe4 100644 --- a/packages/ember-data/lib/system/relationships/ext.js +++ b/packages/ember-data/lib/system/relationships/ext.js @@ -7,12 +7,9 @@ import { Map, MapWithDefault } from "ember-data/system/map"; +import EmptyObject from "ember-data/system/empty-object"; import ArrayPolyfills from 'ember-data/ext/ember/array'; -import { - create -} from 'ember-data/system/object-polyfills'; - var get = Ember.get; var filter = ArrayPolyfills.filter; @@ -191,7 +188,7 @@ Model.reopenClass({ }, inverseMap: Ember.computed(function() { - return create(null); + return new EmptyObject(); }), /** diff --git a/packages/ember-data/lib/system/relationships/state/create.js b/packages/ember-data/lib/system/relationships/state/create.js index a46345c3b98..90e127cf0b1 100644 --- a/packages/ember-data/lib/system/relationships/state/create.js +++ b/packages/ember-data/lib/system/relationships/state/create.js @@ -1,9 +1,6 @@ import ManyRelationship from "ember-data/system/relationships/state/has-many"; import BelongsToRelationship from "ember-data/system/relationships/state/belongs-to"; - -import { - create -} from 'ember-data/system/object-polyfills'; +import EmptyObject from "ember-data/system/empty-object"; var get = Ember.get; @@ -24,7 +21,7 @@ var createRelationshipFor = function(record, relationshipMeta, store) { var Relationships = function(record) { this.record = record; - this.initializedRelationships = create(null); + this.initializedRelationships = new EmptyObject(); }; Relationships.prototype.has = function(key) { diff --git a/packages/ember-data/lib/system/snapshot.js b/packages/ember-data/lib/system/snapshot.js index efb51d45003..9662838dfd0 100644 --- a/packages/ember-data/lib/system/snapshot.js +++ b/packages/ember-data/lib/system/snapshot.js @@ -4,10 +4,11 @@ var get = Ember.get; import { - create, keysFunc } from 'ember-data/system/object-polyfills'; +import EmptyObject from "ember-data/system/empty-object"; + /** @class Snapshot @namespace DS @@ -16,11 +17,11 @@ import { @param {DS.Model} internalModel The model to create a snapshot from */ function Snapshot(internalModel) { - this._attributes = create(null); - this._belongsToRelationships = create(null); - this._belongsToIds = create(null); - this._hasManyRelationships = create(null); - this._hasManyIds = create(null); + this._attributes = new EmptyObject(); + this._belongsToRelationships = new EmptyObject(); + this._belongsToIds = new EmptyObject(); + this._hasManyRelationships = new EmptyObject(); + this._hasManyIds = new EmptyObject(); var record = internalModel.getRecord(); this.record = record; @@ -172,7 +173,7 @@ Snapshot.prototype = { @return {Object} All changed attributes of the current snapshot */ changedAttributes: function() { - let changedAttributes = create(null); + let changedAttributes = new EmptyObject(); let changedAttributeKeys = keysFunc(this._changedAttributes); for (let i=0, length = changedAttributeKeys.length; i < length; i++) { diff --git a/packages/ember-data/lib/system/store.js b/packages/ember-data/lib/system/store.js index 81d3bce8e99..345a47b98c4 100644 --- a/packages/ember-data/lib/system/store.js +++ b/packages/ember-data/lib/system/store.js @@ -53,10 +53,11 @@ import InternalModel from "ember-data/system/model/internal-model"; import ArrayPolyfills from 'ember-data/ext/ember/array'; import { - create, keysFunc } from 'ember-data/system/object-polyfills'; +import EmptyObject from "ember-data/system/empty-object"; + var Backburner = Ember._Backburner || Ember.Backburner || Ember.__loader.require('backburner')['default'] || Ember.__loader.require('backburner')['Backburner']; @@ -329,7 +330,7 @@ Store = Service.extend({ createRecord: function(modelName, inputProperties) { Ember.assert('Passing classes to store methods has been removed. Please pass a dasherized string instead of '+ Ember.inspect(modelName), typeof modelName === 'string'); var typeClass = this.modelFor(modelName); - var properties = copy(inputProperties) || create(null); + var properties = copy(inputProperties) || new EmptyObject(); // If the passed properties do not include a primary key, // give the adapter an opportunity to generate one. Typically, @@ -1338,7 +1339,7 @@ Store = Service.extend({ record.destroy(); // maybe within unloadRecord } - typeMap.metadata = create(null); + typeMap.metadata = new EmptyObject(); } function byType(entry) { @@ -1689,9 +1690,9 @@ Store = Service.extend({ if (typeMap) { return typeMap; } typeMap = { - idToRecord: create(null), + idToRecord: new EmptyObject(), records: [], - metadata: create(null), + metadata: new EmptyObject(), type: typeClass }; diff --git a/packages/ember-data/lib/system/store/container-instance-cache.js b/packages/ember-data/lib/system/store/container-instance-cache.js index 551efb1ed7b..3171498b534 100644 --- a/packages/ember-data/lib/system/store/container-instance-cache.js +++ b/packages/ember-data/lib/system/store/container-instance-cache.js @@ -1,10 +1,11 @@ import Ember from 'ember'; import { - create, keysFunc } from 'ember-data/system/object-polyfills'; +import EmptyObject from "ember-data/system/empty-object"; + /** * The `ContainerInstanceCache` serves as a lazy cache for looking up * instances of serializers and adapters. It has some additional logic for @@ -25,10 +26,10 @@ import { */ function ContainerInstanceCache(container) { this._container = container; - this._cache = create(null); + this._cache = new EmptyObject(); } -ContainerInstanceCache.prototype = create(null); +ContainerInstanceCache.prototype = new EmptyObject(); Ember.merge(ContainerInstanceCache.prototype, { get: function(type, preferredKey, fallbacks) { diff --git a/packages/ember-data/tests/integration/store-test.js b/packages/ember-data/tests/integration/store-test.js index 4dd6764321e..14b5ddc283f 100644 --- a/packages/ember-data/tests/integration/store-test.js +++ b/packages/ember-data/tests/integration/store-test.js @@ -149,8 +149,8 @@ test("destroying the store correctly cleans everything up", function() { filterdPeople = store.filter('person', function() { return true; }); }); - var filterdPeopleWillDestroy = tap(filterdPeople.content, 'willDestroy'); - var adapterPopulatedPeopleWillDestroy = tap(adapterPopulatedPeople.content, 'willDestroy'); + var filterdPeopleWillDestroy = tap(filterdPeople.get('content'), 'willDestroy'); + var adapterPopulatedPeopleWillDestroy = tap(adapterPopulatedPeople.get('content'), 'willDestroy'); run(function() { store.findRecord('person', 2); diff --git a/packages/ember-data/tests/unit/model-test.js b/packages/ember-data/tests/unit/model-test.js index e2cc21be65e..2097a921279 100644 --- a/packages/ember-data/tests/unit/model-test.js +++ b/packages/ember-data/tests/unit/model-test.js @@ -227,12 +227,23 @@ test("changedAttributes() return correct values", function() { equal(keysFunc(mascot.changedAttributes()).length, 0, 'after rollback attributes there are no changes'); }); +function toObj(obj) { + // https://github.com/jquery/qunit/issues/851 + var result = Object.create(null); + for (var key in obj) { + result[key] = obj[key]; + } + return result; +} + test("changedAttributes() works while the record is being saved", function() { expect(1); var cat; var adapter = DS.Adapter.extend({ createRecord(store, model, snapshot) { - deepEqual(cat.changedAttributes(), { name: [undefined, 'Argon'], likes: [undefined, 'Cheese'] }); + deepEqual(toObj(cat.changedAttributes()), { + name: [undefined, 'Argon'], + likes: [undefined, 'Cheese'] }); return {}; } }); @@ -259,7 +270,7 @@ test("changedAttributes() works while the record is being updated", function() { var cat; var adapter = DS.Adapter.extend({ updateRecord(store, model, snapshot) { - deepEqual(cat.changedAttributes(), { name: ['Argon', 'Helia'], likes: ['Cheese', 'Mussels'] }); + deepEqual(toObj(cat.changedAttributes()), { name: ['Argon', 'Helia'], likes: ['Cheese', 'Mussels'] }); return { id: '1', type: 'mascot' }; } });