diff --git a/packages/ember-data/lib/serializers/json-serializer.js b/packages/ember-data/lib/serializers/json-serializer.js index d3480799b58..599bccfa067 100644 --- a/packages/ember-data/lib/serializers/json-serializer.js +++ b/packages/ember-data/lib/serializers/json-serializer.js @@ -3,6 +3,10 @@ import coerceId from "ember-data/system/coerce-id"; import normalizeModelName from "ember-data/system/normalize-model-name"; import { modelHasAttributeOrRelationshipNamedType } from "ember-data/utils"; +import { + getOwner +} from 'ember-data/utils'; + import { errorsArrayToHash } from "ember-data/adapters/errors"; var get = Ember.get; @@ -1366,8 +1370,10 @@ export default Serializer.extend({ @return {DS.Transform} transform */ transformFor: function(attributeType, skipAssertion) { - var transform = this.container.lookup('transform:' + attributeType); + var transform = getOwner(this).lookup('transform:' + attributeType); + Ember.assert("Unable to find transform for '" + attributeType + "'", skipAssertion || !!transform); + return transform; } }); diff --git a/packages/ember-data/lib/system/model/internal-model.js b/packages/ember-data/lib/system/model/internal-model.js index b10e0f3e38e..3284ad31d55 100644 --- a/packages/ember-data/lib/system/model/internal-model.js +++ b/packages/ember-data/lib/system/model/internal-model.js @@ -4,6 +4,10 @@ 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 { + getOwner +} from 'ember-data/utils'; + var Promise = Ember.RSVP.Promise; var get = Ember.get; var set = Ember.set; @@ -46,11 +50,10 @@ var guid = 0; @class InternalModel */ -export default function InternalModel(type, id, store, container, data) { +export default function InternalModel(type, id, store, _, data) { this.type = type; this.id = id; this.store = store; - this.container = container; this._data = data || new EmptyObject(); this.modelName = type.modelName; this.dataHasInitialized = false; @@ -110,17 +113,53 @@ InternalModel.prototype = { constructor: InternalModel, materializeRecord: function() { Ember.assert("Materialized " + this.modelName + " record with id:" + this.id + "more than once", this.record === null || this.record === undefined); + // lookupFactory should really return an object that creates // instances with the injections applied - this.record = this.type._create({ + var createOptions = { store: this.store, - container: this.container, _internalModel: this, id: this.id, currentState: get(this, 'currentState'), isError: this.isError, adapterError: this.error - }); + }; + + if (Ember.setOwner) { + // ensure that `Ember.getOwner(this)` works inside a model instance + Ember.setOwner(createOptions, getOwner(this.store)); + } + + // when model factoru injections are disabled, + // we have to ensure that `container` is set on + // the model instances + if (!('container' in this.type.prototype)) { + + // if `Ember.getOwner` is defined, accessing `this.container` is + // deprecated (but functional). In "standard" Ember usage, this + // deprecation is actually created via an `.extend` of the factory, + // but that only happens on models with MODEL_FACTORY_INJECTIONS is enabled + if (Ember.getOwner) { + Object.defineProperty(this.type.prototype, 'container', { + configurable: true, + enumerable: false, + get() { + Ember.deprecate('Using the injected `container` is deprecated. Please use the `getOwner` helper instead to access the owner of this object.', + false, + { id: 'ember-application.injected-container', until: '3.0.0' }); + + return this.store.container; + } + }); + } else { + // We only add `createOptions.container` + // here if that container injection isn't set. + createOptions.container = this.store && this.store.container; + } + } + + this.record = this.type._create(createOptions); + this._triggerDeferredTriggers(); }, diff --git a/packages/ember-data/lib/system/store.js b/packages/ember-data/lib/system/store.js index 3a912f488aa..baa97c34ec4 100644 --- a/packages/ember-data/lib/system/store.js +++ b/packages/ember-data/lib/system/store.js @@ -40,6 +40,10 @@ import { _queryRecord } from "ember-data/system/store/finders"; +import { + getOwner +} from 'ember-data/utils'; + import coerceId from "ember-data/system/coerce-id"; import RecordArrayManager from "ember-data/system/record-array-manager"; @@ -220,7 +224,7 @@ Store = Service.extend({ store: this }); this._pendingSave = []; - this._instanceCache = new ContainerInstanceCache(this.container); + this._instanceCache = new ContainerInstanceCache(getOwner(this)); //Used to keep track of all the find requests that need to be coalesced this._pendingFetch = Map.create(); }, @@ -1475,11 +1479,12 @@ Store = Service.extend({ // container.registry = 2.1 // container._registry = 1.11 - 2.0 // container = < 1.11 - var registry = this.container.registry || this.container._registry || this.container; - var mixin = this.container.lookupFactory('mixin:' + normalizedModelName); + var owner = getOwner(this); + + var mixin = owner._lookupFactory('mixin:' + normalizedModelName); if (mixin) { //Cache the class as a model - registry.register('model:' + normalizedModelName, DS.Model.extend(mixin)); + owner.register('model:' + normalizedModelName, DS.Model.extend(mixin)); } var factory = this.modelFactoryFor(normalizedModelName); if (factory) { @@ -1518,7 +1523,10 @@ Store = Service.extend({ modelFactoryFor: function(modelName) { Ember.assert('Passing classes to store methods has been removed. Please pass a dasherized string instead of '+ Ember.inspect(modelName), typeof modelName === 'string'); var normalizedKey = normalizeModelName(modelName); - return this.container.lookupFactory('model:' + normalizedKey); + + var owner = getOwner(this); + + return owner._lookupFactory('model:' + normalizedKey); }, /** @@ -1701,7 +1709,7 @@ Store = Service.extend({ }, _hasModelFor: function(type) { - return this.container.lookupFactory(`model:${type}`); + return getOwner(this)._lookupFactory(`model:${type}`); }, _pushInternalModel: function(data) { @@ -1867,7 +1875,7 @@ Store = Service.extend({ // lookupFactory should really return an object that creates // instances with the injections applied - var internalModel = new InternalModel(type, id, this, this.container, data); + var internalModel = new InternalModel(type, id, this, null, data); // if we're creating an item, this process will be done // later, once the object has been persisted. 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 4f910314527..6dff1d2a348 100644 --- a/packages/ember-data/lib/system/store/container-instance-cache.js +++ b/packages/ember-data/lib/system/store/container-instance-cache.js @@ -19,9 +19,9 @@ import EmptyObject from "ember-data/system/empty-object"; * @class ContainerInstanceCache * */ -export default function ContainerInstanceCache(container) { - this._container = container; - this._cache = new EmptyObject(); +export default function ContainerInstanceCache(owner) { + this._owner = owner; + this._cache = new EmptyObject(); } ContainerInstanceCache.prototype = new EmptyObject(); @@ -55,7 +55,7 @@ Ember.merge(ContainerInstanceCache.prototype, { instanceFor: function(key) { let cache = this._cache; if (!cache[key]) { - let instance = this._container.lookup(key); + let instance = this._owner.lookup(key); if (instance) { cache[key] = instance; } @@ -74,7 +74,7 @@ Ember.merge(ContainerInstanceCache.prototype, { cacheEntry.destroy(); } } - this._container = null; + this._owner = null; }, constructor: ContainerInstanceCache, diff --git a/packages/ember-data/lib/utils.js b/packages/ember-data/lib/utils.js index b31e9262d10..107f3144341 100644 --- a/packages/ember-data/lib/utils.js +++ b/packages/ember-data/lib/utils.js @@ -54,7 +54,37 @@ function modelHasAttributeOrRelationshipNamedType(modelClass) { return get(modelClass, 'attributes').has('type') || get(modelClass, 'relationshipsByName').has('type'); } +/* + ember-container-inject-owner is a new feature in Ember 2.3 that finally provides a public + API for looking items up. This function serves as a super simple polyfill to avoid + triggering deprecations. +*/ +function getOwner(context) { + var owner; + + if (Ember.getOwner) { + owner = Ember.getOwner(context); + } + + if (!owner && context.container) { + owner = context.container; + } + + if (owner && owner.lookupFactory && !owner._lookupFactory) { + // `owner` is a container, we are just making this work + owner._lookupFactory = owner.lookupFactory; + owner.register = function() { + var registry = owner.registry || owner._registry || owner; + + return registry.register(...arguments); + }; + } + + return owner; +} + export { assertPolymorphicType, - modelHasAttributeOrRelationshipNamedType + modelHasAttributeOrRelationshipNamedType, + getOwner }; diff --git a/packages/ember-data/tests/unit/model/internal-model-test.js b/packages/ember-data/tests/unit/model/internal-model-test.js index 5e81ff5ab17..04ce9673cee 100644 --- a/packages/ember-data/tests/unit/model/internal-model-test.js +++ b/packages/ember-data/tests/unit/model/internal-model-test.js @@ -1,16 +1,16 @@ module("unit/model/internal-model - Internal Model"); -var mockModelFactory = { - _create: function() { - return { trigger: function() {} }; - }, +function MockModelFactory () { } - eachRelationship: function() { - } +MockModelFactory._create = function() { + return { trigger: function() {} }; }; + +MockModelFactory.eachRelationship = function() { }; + test("Materializing a model twice errors out", function() { expect(1); - var internalModel = new DS.InternalModel(mockModelFactory, null, null, null); + var internalModel = new DS.InternalModel(MockModelFactory, null, null, null); internalModel.materializeRecord(); expectAssertion(function() { diff --git a/packages/ember-data/tests/unit/store/model-for-test.js b/packages/ember-data/tests/unit/store/model-for-test.js index c2c117fba91..eadca2207ee 100644 --- a/packages/ember-data/tests/unit/store/model-for-test.js +++ b/packages/ember-data/tests/unit/store/model-for-test.js @@ -13,7 +13,7 @@ module("unit/store/model_for - DS.Store#modelFor", { "blog.post": DS.Model.extend() }); store = env.store; - container = store.container; + container = env.container; registry = env.registry; }, 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 c525782cd00..58c1960bc5a 100644 --- a/packages/ember-data/tests/unit/store/serializer-for-test.js +++ b/packages/ember-data/tests/unit/store/serializer-for-test.js @@ -6,7 +6,7 @@ module("unit/store/serializer_for - DS.Store#serializerFor", { Person = DS.Model.extend({}); var env = setupStore({ person: Person }); store = env.store; - container = store.container; + container = env.container; registry = env.registry; }, diff --git a/tests/ember-configuration.js b/tests/ember-configuration.js index ad317c1bcdf..08485605455 100644 --- a/tests/ember-configuration.js +++ b/tests/ember-configuration.js @@ -17,6 +17,8 @@ ENV['ENABLE_OPTIONAL_FEATURES'] = !!QUnit.urlParams.enableoptionalfeatures; ENV['RAISE_ON_DEPRECATION'] = true; + var Owner; + window.async = function(callback, timeout) { var timer; stop(); @@ -52,13 +54,33 @@ }; window.setupStore = function(options) { - var container, registry; + var container, registry, owner; var env = {}; options = options || {}; + // This is done once upon first setupStore call (we cannot do it eagerly + // because this file is loaded before Ember itself). + if (!Owner) { + if (Ember._RegistryProxyMixin && Ember._ContainerProxyMixin) { + Owner = Ember.Object.extend(Ember._RegistryProxyMixin, Ember._ContainerProxyMixin); + } else { + Owner = Ember.Object.extend(); + } + } + if (Ember.Registry) { + registry = env.registry = new Ember.Registry(); - container = env.container = registry.container(); + + owner = Owner.create({ + __registry__: registry + }); + + container = env.container = registry.container({ + owner: owner + }); + + owner.__container__ = container; } else { container = env.container = new Ember.Container(); registry = env.registry = container;