Skip to content

Commit

Permalink
Introduce instance initializers
Browse files Browse the repository at this point in the history
This commit introduces a new (feature flagged) API for adding
instance initializers.

Instance initializers differ from normal initializers in that they are
passed the app instance rather than a registry, and therefore can access
instances from the container in a safe way.

This design not only allows us to avoid expensive app setup for each
FastBoot request, it also minimizes the amount of work required
between acceptance test runs (once the testing infrastructure
is updated to take advantage of it).

This commit also removes a previously introduced deprecation that was
not behind a feature flag. That deprecation (when emitted with the
feature flag enabled) now points to a comprehensive deprecation guide.
  • Loading branch information
tomdale committed Jan 22, 2015
1 parent 434e9cc commit cf55da2
Show file tree
Hide file tree
Showing 6 changed files with 397 additions and 33 deletions.
21 changes: 21 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ for a detailed explanation.

## Feature Flags

* `ember-application-instance-initializers`

Splits apart initializers into two phases:

* Boot-time Initializers that receive a registry, and use it to set up
code structures
* Instance Initializers that receive an application instance, and use
it to set up application state per run of the application.

In FastBoot, each request will have its own run of the application,
and will only need to run the instance initializers.

In the future, tests will also be able to use this differentiation to
run just the instance initializers per-test.

With this change, `App.initializer` becomes a "boot-time" initializer,
and issues a deprecation warning if instances are accessed.

Apps should migrate any initializers that require instances to the new
`App.instanceInitializer` API.

* `ember-application-initializer-context`

Sets the context of the initializer function to its object instead of the
Expand Down
1 change: 1 addition & 0 deletions features.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"ember-testing-checkbox-helpers": null,
"ember-metal-stream": null,
"ember-htmlbars-each-with-index": true,
"ember-application-instance-initializers": null,
"ember-application-initializer-context": null
},
"debugStatements": [
Expand Down
12 changes: 10 additions & 2 deletions packages/container/lib/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,21 @@ Registry.prototype = {

lookup: function(fullName, options) {
Ember.assert('Create a container on the registry (with `registry.container()`) before calling `lookup`.', this._defaultContainer);
Ember.deprecate('`lookup` should not be called on a Registry. Call `lookup` directly on an associated Container instead.');

if (Ember.FEATURES.isEnabled('ember-application-instance-initializers')) {
Ember.deprecate('`lookup` was called on a Registry. The `initializer` API no longer receives a container, and you should use an `instanceInitializer` to look up objects from the container.', { url: "http://emberjs.com/guides/deprecations#toc_deprecate-access-to-instances-in-initializers" });
}

return this._defaultContainer.lookup(fullName, options);
},

lookupFactory: function(fullName) {
Ember.assert('Create a container on the registry (with `registry.container()`) before calling `lookupFactory`.', this._defaultContainer);
Ember.deprecate('`lookupFactory` should not be called on a Registry. Call `lookupFactory` directly on an associated Container instead.');

if (Ember.FEATURES.isEnabled('ember-application-instance-initializers')) {
Ember.deprecate('`lookupFactory` was called on a Registry. The `initializer` API no longer receives a container, and you should use an `instanceInitializer` to look up objects from the container.', { url: "http://emberjs.com/guides/deprecations#toc_deprecate-access-to-instances-in-initializers" });
}

return this._defaultContainer.lookupFactory(fullName);
},

Expand Down
92 changes: 61 additions & 31 deletions packages/ember-application/lib/system/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,9 @@ var Application = Namespace.extend(DeferredMixin, {
_initialize: function() {
if (this.isDestroyed) { return; }

this.runInitializers();
this.runInitializers(this.__registry__);
this.runInstanceInitializers(this.__instance__);

runLoadHooks('application', this);

// At this point, any initializers or load hooks that would have wanted
Expand Down Expand Up @@ -627,12 +629,31 @@ var Application = Namespace.extend(DeferredMixin, {
@private
@method runInitializers
*/
runInitializers: function() {
var initializersByName = get(this.constructor, 'initializers');
runInitializers: function(registry) {
var App = this;
this._runInitializer('initializers', function(name, initializer) {
Ember.assert("No application initializer named '" + name + "'", !!initializer);

if (Ember.FEATURES.isEnabled("ember-application-initializer-context")) {
initializer.initialize(registry, App);
} else {
var ref = initializer.initialize;
ref(registry, App);
}
});
},

runInstanceInitializers: function(instance) {
this._runInitializer('instanceInitializers', function(name, initializer) {
Ember.assert("No instance initializer named '" + name + "'", !!initializer);
initializer.initialize(instance);
});
},

_runInitializer: function(bucketName, cb) {
var initializersByName = get(this.constructor, bucketName);
var initializers = props(initializersByName);
var registry = this.__registry__;
var graph = new DAG();
var namespace = this;
var initializer;

for (var i = 0; i < initializers.length; i++) {
Expand All @@ -641,15 +662,7 @@ var Application = Namespace.extend(DeferredMixin, {
}

graph.topsort(function (vertex) {
var initializer = vertex.value;
Ember.assert("No application initializer named '" + vertex.name + "'", !!initializer);

if (Ember.FEATURES.isEnabled("ember-application-initializer-context")) {
initializer.initialize(registry, namespace);
} else {
var ref = initializer.initialize;
ref(registry, namespace);
}
cb(vertex.name, vertex.value);
});
},

Expand Down Expand Up @@ -737,8 +750,21 @@ var Application = Namespace.extend(DeferredMixin, {
}
});

if (Ember.FEATURES.isEnabled('ember-application-instance-initializers')) {
Application.reopen({
instanceInitializer: function(options) {
this.constructor.instanceInitializer(options);
}
});

Application.reopenClass({
instanceInitializer: buildInitializerMethod('instanceInitializers', 'instance initializer')
});
}

Application.reopenClass({
initializers: create(null),
instanceInitializers: create(null),

/**
Initializer receives an object which has the following attributes:
Expand Down Expand Up @@ -863,23 +889,7 @@ Application.reopenClass({
@method initializer
@param initializer {Object}
*/
initializer: function(initializer) {
// If this is the first initializer being added to a subclass, we are going to reopen the class
// to make sure we have a new `initializers` object, which extends from the parent class' using
// prototypal inheritance. Without this, attempting to add initializers to the subclass would
// pollute the parent class as well as other subclasses.
if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) {
this.reopenClass({
initializers: create(this.initializers)
});
}

Ember.assert("The initializer '" + initializer.name + "' has already been registered", !this.initializers[initializer.name]);
Ember.assert("An initializer cannot be registered without an initialize function", canInvoke(initializer, 'initialize'));
Ember.assert("An initializer cannot be registered without a name property", initializer.name !== undefined);

this.initializers[initializer.name] = initializer;
},
initializer: buildInitializerMethod('initializers', 'initializer'),

/**
This creates a registry with the default Ember naming conventions.
Expand Down Expand Up @@ -1046,4 +1056,24 @@ function logLibraryVersions() {
}
}

function buildInitializerMethod(bucketName, humanName) {
return function(initializer) {
// If this is the first initializer being added to a subclass, we are going to reopen the class
// to make sure we have a new `initializers` object, which extends from the parent class' using
// prototypal inheritance. Without this, attempting to add initializers to the subclass would
// pollute the parent class as well as other subclasses.
if (this.superclass[bucketName] !== undefined && this.superclass[bucketName] === this[bucketName]) {
var attrs = {};
attrs[bucketName] = create(this[bucketName]);
this.reopenClass(attrs);
}

Ember.assert("The " + humanName + " '" + initializer.name + "' has already been registered", !this[bucketName][initializer.name]);
Ember.assert("An " + humanName + " cannot be registered without an initialize function", canInvoke(initializer, 'initialize'));
Ember.assert("An " + humanName + " cannot be registered without a name property", initializer.name !== undefined);

this[bucketName][initializer.name] = initializer;
};
}

export default Application;
20 changes: 20 additions & 0 deletions packages/ember-application/tests/system/initializers_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import run from "ember-metal/run_loop";
import Application from "ember-application/system/application";
import { indexOf } from "ember-metal/array";
import jQuery from "ember-views/system/jquery";
import Registry from "container/registry";

var app;

Expand Down Expand Up @@ -33,6 +34,25 @@ test("initializers require proper 'name' and 'initialize' properties", function(

});

test("initializers are passed a registry and App", function() {
var MyApplication = Application.extend();

MyApplication.initializer({
name: 'initializer',
initialize: function(registry, App) {
ok(registry instanceof Registry, "initialize is passed a registry");
ok(App instanceof Application, "initialize is passed an Application");
}
});

run(function() {
app = MyApplication.create({
router: false,
rootElement: '#qunit-fixture'
});
});
});

test("initializers can be registered in a specified order", function() {
var order = [];
var MyApplication = Application.extend();
Expand Down
Loading

0 comments on commit cf55da2

Please sign in to comment.