From 869d0d74adbcbd2b4384310e994c9f7b534929e4 Mon Sep 17 00:00:00 2001 From: Dan Gebhardt Date: Sun, 15 Nov 2015 13:01:32 -0500 Subject: [PATCH] [FEATURE ember-container-inject-owner] Inject fake container for non-extendable factories. The fake container is required to provide access to the `container` during instantiation of objects by non-extendable factories. The fake container provides access to the following methods: * `lookup` * `lookupFactory` Deprecation warnings are provided that direct the user to call `getOwner` followed by the equivalent `ContainerProxy` methods (`lookup` and `_lookupFactory`) on the owner. --- packages/container/lib/container.js | 29 ++++++++--- packages/container/tests/container_test.js | 50 ++++++++++++++++++- .../lib/mixins/container_proxy.js | 30 +++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/packages/container/lib/container.js b/packages/container/lib/container.js index 092149cc397..34693880007 100644 --- a/packages/container/lib/container.js +++ b/packages/container/lib/container.js @@ -3,6 +3,7 @@ import { assert, deprecate } from 'ember-metal/debug'; import dictionary from 'ember-metal/dictionary'; import isEnabled from 'ember-metal/features'; import { setOwner } from './owner'; +import { buildFakeContainerWithDeprecations } from 'ember-runtime/mixins/container_proxy'; /** A container used to instantiate and cache objects. @@ -23,6 +24,10 @@ function Container(registry, options) { this.cache = dictionary(options && options.cache ? options.cache : null); this.factoryCache = dictionary(options && options.factoryCache ? options.factoryCache : null); this.validationCache = dictionary(options && options.validationCache ? options.validationCache : null); + + if (isEnabled('ember-container-inject-owner')) { + this._fakeContainerToInject = buildFakeContainerWithDeprecations(this); + } } Container.prototype = { @@ -243,8 +248,11 @@ function factoryFor(container, fullName) { var injectedFactory = factory.extend(injections); + // TODO - remove all `container` injections when Ember reaches v3.0.0 if (isEnabled('ember-container-inject-owner')) { injectDeprecatedContainer(injectedFactory.prototype, container); + } else { + injectedFactory.prototype.container = container; } injectedFactory.reopenClass(factoryInjections); @@ -273,12 +281,6 @@ function injectionsFor(container, fullName) { setOwner(injections, container.owner); - // TODO - Inject a `FakeContainer` instead here. The `FakeContainer` will - // proxy all properties of the container with deprecations. - if (!isEnabled('ember-container-inject-owner')) { - injections.container = container; - } - return injections; } @@ -330,8 +332,21 @@ function instantiate(container, fullName) { // assume the factory was extendable // to create time injections // TODO: support new'ing for instantiation and merge injections for pure JS Functions - obj = factory.create(injectionsFor(container, fullName)); + let injections = injectionsFor(container, fullName); + + // Ensure that a container is available to an object during instantiation. + // TODO - remove when Ember reaches v3.0.0 + if (isEnabled('ember-container-inject-owner')) { + // This "fake" container will be replaced after instantiation with a + // property that raises deprecations every time it is accessed. + injections.container = container._fakeContainerToInject; + } else { + injections.container = container; + } + + obj = factory.create(injections); + // TODO - remove when Ember reaches v3.0.0 if (isEnabled('ember-container-inject-owner')) { injectDeprecatedContainer(obj, container); } diff --git a/packages/container/tests/container_test.js b/packages/container/tests/container_test.js index 7ddeeea29d9..dafec3f7855 100644 --- a/packages/container/tests/container_test.js +++ b/packages/container/tests/container_test.js @@ -1,6 +1,7 @@ import Ember from 'ember-metal/core'; import Registry from 'container/registry'; import factory from 'container/tests/test-helpers/factory'; +import { getOwner } from 'container/owner'; import isEnabled from 'ember-metal/features'; var originalModelInjections; @@ -519,7 +520,7 @@ QUnit.test('Lazy injection validations are cached', function() { }); if (isEnabled('ember-container-inject-owner')) { - QUnit.test('A deprecated `container` property is appended to every instantiated object', function() { + QUnit.test('A deprecated `container` property is appended to every object instantiated from an extendable factory', function() { let registry = new Registry(); let container = registry.container(); let PostController = factory(); @@ -535,6 +536,53 @@ if (isEnabled('ember-container-inject-owner')) { strictEqual(c, container); }, 'Using the injected `container` is deprecated. Please use the `getOwner` helper instead to access the owner of this object.'); }); + + QUnit.test('A deprecated `container` property is appended to every object instantiated from a non-extendable factory, and a fake container is available during instantiation.', function() { + expect(8); + + let owner = {}; + let registry = new Registry(); + let container = registry.container({ owner }); + + // Define a simple non-extendable factory + let PostController = function() {}; + PostController.create = function(options) { + ok(options.container, 'fake container has been injected and is available during `create`.'); + + expectDeprecation(function() { + options.container.lookup('abc:one'); + }, 'Using the injected `container` is deprecated. Please use the `getOwner` helper to access the owner of this object and then call `lookup` instead.'); + + expectDeprecation(function() { + options.container.lookupFactory('abc:two'); + }, 'Using the injected `container` is deprecated. Please use the `getOwner` helper to access the owner of this object and then call `_lookupFactory` instead.'); + + // non-deprecated usage of `lookup` and `_lookupFactory` + owner.lookup = function(fullName) { + equal(fullName, 'abc:one', 'lookup on owner called properly'); + }; + owner._lookupFactory = function(fullName) { + equal(fullName, 'abc:two', '_lookupFactory on owner called properly'); + }; + let foundOwner = getOwner(options); + foundOwner.lookup('abc:one'); + foundOwner._lookupFactory('abc:two'); + + return new PostController(options); + }; + + registry.register('controller:post', PostController); + let postController = container.lookup('controller:post'); + + expectDeprecation(function() { + Ember.get(postController, 'container'); + }, 'Using the injected `container` is deprecated. Please use the `getOwner` helper instead to access the owner of this object.'); + + expectDeprecation(function() { + let c = postController.container; + strictEqual(c, container, 'Injected container is now regular (not fake) container, but access is still deprecated.'); + }, 'Using the injected `container` is deprecated. Please use the `getOwner` helper instead to access the owner of this object.'); + }); } else { QUnit.test('A `container` property is appended to every instantiated object', function() { let registry = new Registry(); diff --git a/packages/ember-runtime/lib/mixins/container_proxy.js b/packages/ember-runtime/lib/mixins/container_proxy.js index ab4e168ecd3..e9c67e0be07 100644 --- a/packages/ember-runtime/lib/mixins/container_proxy.js +++ b/packages/ember-runtime/lib/mixins/container_proxy.js @@ -3,6 +3,7 @@ @submodule ember-runtime */ import run from 'ember-metal/run_loop'; +import { deprecate } from 'ember-metal/debug'; import { Mixin } from 'ember-metal/mixin'; @@ -95,3 +96,32 @@ function containerAlias(name) { return this.__container__[name](...arguments); }; } + +export function buildFakeContainerWithDeprecations(container) { + let fakeContainer = {}; + let propertyMappings = { + lookup: 'lookup', + lookupFactory: '_lookupFactory' + }; + + for (let containerProperty in propertyMappings) { + fakeContainer[containerProperty] = buildFakeContainerFunction(container, containerProperty, propertyMappings[containerProperty]); + } + + return fakeContainer; +} + +function buildFakeContainerFunction(container, containerProperty, ownerProperty) { + return function() { + deprecate( + `Using the injected \`container\` is deprecated. Please use the \`getOwner\` helper to access the owner of this object and then call \`${ownerProperty}\` instead.`, + false, + { + id: 'ember-application.injected-container', + until: '3.0.0', + url: 'http://emberjs.com/deprecations/v2.x#toc_injected-container-access' + } + ); + return container[containerProperty](...arguments); + }; +}