Skip to content

Commit 3a63382

Browse files
mixonicchadhietala
authored andcommitted
[FEATURE factory-for] Allow for incremental roll out factoryFor
The public factoryFor API should always return `class` as the no-double-extend version. However internal APIs already refactored to use FACTORY_FOR should use the double extended version if the no-double-extend feature flag is false.
1 parent c05d241 commit 3a63382

File tree

4 files changed

+156
-91
lines changed

4 files changed

+156
-91
lines changed

packages/container/lib/container.js

Lines changed: 79 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,11 @@ Container.prototype = {
142142
lookupFactory(fullName, options) {
143143
assert('fullName must be a proper full name', this.registry.validateFullName(fullName));
144144

145-
if (isFeatureEnabled('ember-no-double-extend')) {
146-
deprecate('Using "_lookupFactory" is deprecated. Please use container.factoryFor instead.', false, { id: 'container-lookupFactory', until: '2.13.0', url: 'TODO' });
147-
}
145+
deprecate(
146+
'Using "_lookupFactory" is deprecated. Please use container.factoryFor instead.',
147+
!isFeatureEnabled('ember-factory-for'),
148+
{ id: 'container-lookupFactory', until: '2.13.0', url: 'TODO' }
149+
);
148150

149151
return deprecatedFactoryFor(this, this.registry.normalize(fullName), options);
150152
},
@@ -154,57 +156,27 @@ Container.prototype = {
154156
return deprecatedFactoryFor(this, this.registry.normalize(fullName), options);
155157
},
156158

159+
/*
160+
* This internal version of factoryFor swaps between the public API for
161+
* factoryFor (class is the registered class) and a transition implementation
162+
* (class is the double-extended class). It is *not* the public API version
163+
* of factoryFor, which always returns the registered class.
164+
*/
157165
[FACTORY_FOR](fullName, options = {}) {
158-
let manager;
159166
if (isFeatureEnabled('ember-no-double-extend')) {
160-
let normalizedName = this.registry.normalize(fullName);
161-
assert('fullName must be a proper full name', this.registry.validateFullName(normalizedName));
162-
163-
if (options.source) {
164-
normalizedName = this.registry.expandLocalLookup(fullName, options);
165-
// if expandLocalLookup returns falsey, we do not support local lookup
166-
if (!normalizedName) { return; }
167+
if (isFeatureEnabled('ember-factory-for')) {
168+
return this.factoryFor(fullName, options);
169+
} else {
170+
/* This throws in case of a poorly designed build */
171+
throw new Error('If ember-no-double-extend is enabled, ember-factory-for must also be enabled');
167172
}
168-
169-
let factory = this.registry.resolve(normalizedName);
170-
171-
if (factory === undefined) { return; }
172-
173-
manager = new FactoryManager(this, factory, fullName, normalizedName);
174-
} else {
175-
let factory = this.lookupFactory(fullName, options);
176-
if (factory === undefined) { return; }
177-
manager = new DeprecatedFactoryManager(this, factory, fullName);
178173
}
174+
let factory = this.lookupFactory(fullName, options);
175+
if (factory === undefined) { return; }
176+
let manager = new DeprecatedFactoryManager(this, factory, fullName);
179177

180178
runInDebug(() => {
181-
if (HAS_PROXY) {
182-
let validator = {
183-
get(obj, prop) {
184-
if (prop !== 'class' && prop !== 'create') {
185-
throw new Error(`You attempted to access "${prop}" on a factory manager created by container#factoryFor. "${prop}" is not a member of a factory manager."`);
186-
}
187-
188-
return obj[prop];
189-
},
190-
set(obj, prop, value) {
191-
throw new Error(`You attempted to set "${prop}" on a factory manager created by container#factoryFor. A factory manager is a read-only construct.`);
192-
}
193-
};
194-
195-
// Note:
196-
// We have to proxy access to the manager here so that private property
197-
// access doesn't cause the above errors to occur.
198-
let m = manager;
199-
let proxiedManager = {
200-
class: m.class,
201-
create(props) {
202-
return m.create(props);
203-
}
204-
};
205-
206-
manager = new Proxy(proxiedManager, validator);
207-
}
179+
manager = wrapManagerInDeprecationProxy(manager);
208180
});
209181

210182
return manager;
@@ -255,10 +227,46 @@ Container.prototype = {
255227
}
256228
};
257229

230+
/*
231+
* Wrap a factory manager in a proxy which will not permit properties to be
232+
* set on the manager.
233+
*/
234+
function wrapManagerInDeprecationProxy(manager) {
235+
if (HAS_PROXY) {
236+
let validator = {
237+
get(obj, prop) {
238+
if (prop !== 'class' && prop !== 'create') {
239+
throw new Error(`You attempted to access "${prop}" on a factory manager created by container#factoryFor. "${prop}" is not a member of a factory manager."`);
240+
}
241+
242+
return obj[prop];
243+
},
244+
set(obj, prop, value) {
245+
throw new Error(`You attempted to set "${prop}" on a factory manager created by container#factoryFor. A factory manager is a read-only construct.`);
246+
}
247+
};
248+
249+
// Note:
250+
// We have to proxy access to the manager here so that private property
251+
// access doesn't cause the above errors to occur.
252+
let m = manager;
253+
let proxiedManager = {
254+
class: m.class,
255+
create(props) {
256+
return m.create(props);
257+
}
258+
};
259+
260+
return new Proxy(proxiedManager, validator);
261+
}
262+
}
263+
258264
if (isFeatureEnabled('ember-factory-for')) {
259265
/**
260-
Given a fullName, return the corresponding factory. The consumer of factory is repsonsible
261-
for the destruction of the factory as there is no way to understand the objects lifecycle.
266+
Given a fullName, return the corresponding factory. The consumer of the factory
267+
is responsible for the destruction of any factory instances, as there is no
268+
way for the container to ensure instances are destroyed when it itself is
269+
destroyed.
262270
263271
@public
264272
@method factoryFor
@@ -267,8 +275,27 @@ if (isFeatureEnabled('ember-factory-for')) {
267275
@param {String} [options.source] The fullname of the request source (used for local lookup)
268276
@return {any}
269277
*/
270-
Container.prototype.factoryFor = function _factoryFor() {
271-
return this[FACTORY_FOR](...arguments);
278+
Container.prototype.factoryFor = function _factoryFor(fullName, options = {}) {
279+
let normalizedName = this.registry.normalize(fullName);
280+
assert('fullName must be a proper full name', this.registry.validateFullName(normalizedName));
281+
282+
if (options.source) {
283+
normalizedName = this.registry.expandLocalLookup(fullName, options);
284+
// if expandLocalLookup returns falsey, we do not support local lookup
285+
if (!normalizedName) { return; }
286+
}
287+
288+
let factory = this.registry.resolve(normalizedName);
289+
290+
if (factory === undefined) { return; }
291+
292+
let manager = new FactoryManager(this, factory, fullName, normalizedName);
293+
294+
runInDebug(() => {
295+
manager = wrapManagerInDeprecationProxy(manager);
296+
});
297+
298+
return manager;
272299
};
273300
}
274301

packages/container/tests/container_test.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ENV } from 'ember-environment';
33
import { get, isFeatureEnabled } from 'ember-metal';
44
import { Registry } from '../index';
55
import { factory } from 'internal-test-helpers';
6+
import { FACTORY_FOR } from 'container';
67

78
let originalModelInjections;
89

@@ -18,7 +19,7 @@ QUnit.module('Container', {
1819
function lookupFactory(name, container, options) {
1920
let factory;
2021
if (isFeatureEnabled('ember-no-double-extend')) {
21-
expectDeprecation(() => {
22+
ignoreDeprecation(() => {
2223
factory = container.lookupFactory(name, options);
2324
});
2425
} else {
@@ -489,11 +490,7 @@ QUnit.test('factory for non extendables resolves are cached', function() {
489490
});
490491

491492
QUnit.test('The `_onLookup` hook is called on factories when looked up the first time', function() {
492-
if (isFeatureEnabled('ember-factory-for') && isFeatureEnabled('ember-no-double-extend')) {
493-
expect(4);
494-
} else {
495-
expect(2);
496-
}
493+
expect(2);
497494

498495
let registry = new Registry();
499496
let container = registry.container();
@@ -701,6 +698,20 @@ QUnit.test('lookup passes options through to expandlocallookup', function(assert
701698
assert.ok(PostControllerLookupResult instanceof PostController);
702699
});
703700

701+
QUnit.test('#[FACTORY_FOR] class is the injected factory', (assert) => {
702+
let registry = new Registry();
703+
let container = registry.container();
704+
705+
let Component = factory();
706+
registry.register('component:foo-bar', Component);
707+
708+
let factoryCreator = container[FACTORY_FOR]('component:foo-bar');
709+
if (isFeatureEnabled('ember-no-double-extend')) {
710+
assert.deepEqual(factoryCreator.class, Component, 'No double extend');
711+
} else {
712+
assert.deepEqual(factoryCreator.class, container.lookupFactory('component:foo-bar'), 'Double extended class');
713+
}
714+
});
704715

705716
if (isFeatureEnabled('ember-factory-for')) {
706717
QUnit.test('#factoryFor must supply a fullname', (assert) => {
@@ -731,11 +742,7 @@ if (isFeatureEnabled('ember-factory-for')) {
731742
registry.register('component:foo-bar', Component);
732743

733744
let factoryCreator = container.factoryFor('component:foo-bar');
734-
if (isFeatureEnabled('ember-no-double-extend')) {
735-
assert.deepEqual(factoryCreator.class, Component, 'No double extend');
736-
} else {
737-
assert.notDeepEqual(factoryCreator.class, Component, 'Double extended class');
738-
}
745+
assert.deepEqual(factoryCreator.class, Component, 'No double extend');
739746
});
740747

741748
QUnit.test('#factoryFor instance have a common parent', (assert) => {

packages/ember-application/tests/system/dependency_injection/default_resolver_test.js

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,16 @@ QUnit.test('the default resolver looks up templates in Ember.TEMPLATES', functio
6565
setTemplate('fooBar', fooBarTemplate);
6666
setTemplate('fooBar/baz', fooBarBazTemplate);
6767

68+
ignoreDeprecation(() => {
69+
equal(locator.lookupFactory('template:foo'), fooTemplate, 'resolves template:foo');
70+
equal(locator.lookupFactory('template:fooBar'), fooBarTemplate, 'resolves template:foo_bar');
71+
equal(locator.lookupFactory('template:fooBar.baz'), fooBarBazTemplate, 'resolves template:foo_bar.baz');
72+
});
73+
6874
if (isFeatureEnabled('ember-factory-for')) {
6975
equal(locator.factoryFor('template:foo').class, fooTemplate, 'resolves template:foo');
7076
equal(locator.factoryFor('template:fooBar').class, fooBarTemplate, 'resolves template:foo_bar');
7177
equal(locator.factoryFor('template:fooBar.baz').class, fooBarBazTemplate, 'resolves template:foo_bar.baz');
72-
} else {
73-
equal(locator.lookupFactory('template:foo'), fooTemplate, 'resolves template:foo');
74-
equal(locator.lookupFactory('template:fooBar'), fooBarTemplate, 'resolves template:foo_bar');
75-
equal(locator.lookupFactory('template:fooBar.baz'), fooBarBazTemplate, 'resolves template:foo_bar.baz');
7678
}
7779
});
7880

@@ -93,48 +95,61 @@ QUnit.test('the default resolver looks up arbitrary types on the namespace', fun
9395
QUnit.test('the default resolver resolves models on the namespace', function() {
9496
application.Post = EmberObject.extend({});
9597

98+
ignoreDeprecation(() => {
99+
detectEqual(application.Post, locator.lookupFactory('model:post'), 'looks up Post model on application');
100+
});
96101
if (isFeatureEnabled('ember-factory-for')) {
97102
detectEqual(application.Post, locator.factoryFor('model:post').class, 'looks up Post model on application');
98-
} else {
99-
detectEqual(application.Post, locator.lookupFactory('model:post'), 'looks up Post model on application');
100103
}
101104
});
102105

103106
QUnit.test('the default resolver resolves *:main on the namespace', function() {
104107
application.FooBar = EmberObject.extend({});
105108

109+
ignoreDeprecation(() => {
110+
detectEqual(application.FooBar, locator.lookupFactory('foo-bar:main'), 'looks up FooBar type without name on application');
111+
});
106112
if (isFeatureEnabled('ember-factory-for')) {
107113
detectEqual(application.FooBar, locator.factoryFor('foo-bar:main').class, 'looks up FooBar type without name on application');
108-
} else {
109-
detectEqual(application.FooBar, locator.lookupFactory('foo-bar:main'), 'looks up FooBar type without name on application');
110114
}
111115
});
112116

113-
QUnit.test('the default resolver resolves container-registered helpers', function() {
117+
if (isFeatureEnabled('ember-factory-for')) {
118+
QUnit.test('the default resolver resolves container-registered helpers', function() {
119+
let shorthandHelper = makeHelper(() => {});
120+
let helper = Helper.extend();
121+
122+
application.register('helper:shorthand', shorthandHelper);
123+
application.register('helper:complete', helper);
124+
125+
let lookedUpShorthandHelper = locator.factoryFor('helper:shorthand').class;
126+
127+
ok(lookedUpShorthandHelper.isHelperInstance, 'shorthand helper isHelper');
128+
129+
let lookedUpHelper = locator.factoryFor('helper:complete').class;
130+
131+
ok(lookedUpHelper.isHelperFactory, 'complete helper is factory');
132+
ok(helper.detect(lookedUpHelper), 'looked up complete helper');
133+
});
134+
}
135+
136+
QUnit.test('the default resolver resolves container-registered helpers via lookupFor', function() {
114137
let shorthandHelper = makeHelper(() => {});
115138
let helper = Helper.extend();
116139

117140
application.register('helper:shorthand', shorthandHelper);
118141
application.register('helper:complete', helper);
119142

120-
let lookedUpShorthandHelper;
121-
if (isFeatureEnabled('ember-factory-for')) {
122-
lookedUpShorthandHelper = locator.factoryFor('helper:shorthand').class;
123-
} else {
124-
lookedUpShorthandHelper = locator.lookupFactory('helper:shorthand');
125-
}
143+
ignoreDeprecation(() => {
144+
let lookedUpShorthandHelper = locator.lookupFactory('helper:shorthand');
126145

127-
ok(lookedUpShorthandHelper.isHelperInstance, 'shorthand helper isHelper');
146+
ok(lookedUpShorthandHelper.isHelperInstance, 'shorthand helper isHelper');
128147

129-
let lookedUpHelper;
130-
if (isFeatureEnabled('ember-factory-for')) {
131-
lookedUpHelper = locator.factoryFor('helper:complete').class;
132-
} else {
133-
lookedUpHelper = locator.lookupFactory('helper:complete');
134-
}
148+
let lookedUpHelper = locator.lookupFactory('helper:complete');
135149

136-
ok(lookedUpHelper.isHelperFactory, 'complete helper is factory');
137-
ok(helper.detect(lookedUpHelper), 'looked up complete helper');
150+
ok(lookedUpHelper.isHelperFactory, 'complete helper is factory');
151+
ok(helper.detect(lookedUpHelper), 'looked up complete helper');
152+
});
138153
});
139154

140155
QUnit.test('the default resolver resolves helpers on the namespace', function() {

packages/ember-routing/tests/system/controller_for_test.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import controllerFor from '../../system/controller_for';
88
import generateController from '../../system/generate_controller';
99
import { buildOwner } from 'internal-test-helpers';
10+
import { isFeatureEnabled } from 'ember-metal';
1011

1112
function buildInstance(namespace) {
1213
let owner = buildOwner();
@@ -75,18 +76,33 @@ QUnit.module('Ember.generateController', {
7576
}
7677
});
7778

78-
QUnit.test('generateController should create Ember.Controller', function() {
79+
QUnit.test('generateController should return Ember.Controller', function() {
7980
let controller = generateController(appInstance, 'home');
8081

81-
ok(controller instanceof Controller, 'should create controller');
82+
ok(controller instanceof Controller, 'should return controller');
8283
});
8384

8485

85-
QUnit.test('generateController should create App.Controller if provided', function() {
86+
QUnit.test('generateController should return App.Controller if provided', function() {
8687
let controller;
8788
namespace.Controller = Controller.extend();
8889

8990
controller = generateController(appInstance, 'home');
9091

91-
ok(controller instanceof namespace.Controller, 'should create controller');
92+
ok(controller instanceof namespace.Controller, 'should return controller');
93+
});
94+
95+
QUnit.test('generateController should return controller:basic if provided', function() {
96+
let controller;
97+
98+
let BasicController = Controller.extend();
99+
appInstance.register('controller:basic', BasicController);
100+
101+
controller = generateController(appInstance, 'home');
102+
103+
if (isFeatureEnabled('ember-no-double-extend')) {
104+
ok(controller instanceof BasicController, 'should return base class of controller');
105+
} else {
106+
ok(controller instanceof appInstance._lookupFactory('controller:basic'), 'should return double-extended controller');
107+
}
92108
});

0 commit comments

Comments
 (0)