Skip to content

Commit

Permalink
Another checkpoint, using Container now
Browse files Browse the repository at this point in the history
  • Loading branch information
wycats committed Dec 17, 2012
1 parent 479d6bb commit 25f4446
Show file tree
Hide file tree
Showing 24 changed files with 534 additions and 174 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -34,3 +34,4 @@ tmp*.gem
tmp.bpm tmp.bpm
tmp.spade tmp.spade
tests/source tests/source
node_modules
1 change: 1 addition & 0 deletions .jshintrc
Expand Up @@ -19,6 +19,7 @@
"testBoth", "testBoth",
"testWithDefault", "testWithDefault",
"raises", "raises",
"throws",
"deepEqual", "deepEqual",
"start", "start",
"stop", "stop",
Expand Down
138 changes: 138 additions & 0 deletions packages/container/lib/main.js
@@ -0,0 +1,138 @@
var get = Ember.get, set = Ember.set;

function Container() {
this.registry = {};
this.cache = {};
this.typeInjections = {};
this.injections = {};
this.options = {};
this.typeOptions = {};
}

Container.prototype = {
set: function(object, key, value) {
object[key] = value;
},

register: function(type, name, factory, options) {
this.registry[type + ":" + name] = factory;
this.options[type + ":" + name] = options || {};
},

resolve: function(fullName) {
if (this.registry.hasOwnProperty(fullName)) {
return this.registry[fullName];
}
},

lookup: function(fullName) {
if (this.cache.hasOwnProperty(fullName)) {
return this.cache[fullName];
}

var value = instantiate(this, fullName);

if (!value) { return; }

if (isSingleton(this, fullName)) {
this.cache[fullName] = value;
}

return value;
},

optionsForType: function(type, options) {
this.typeOptions[type] = options;
},

typeInjection: function(type, property, fullName) {
var injections = this.typeInjections[type] = this.typeInjections[type] || [];
injections.push({ property: property, fullName: fullName });
},

injection: function(factoryName, property, injectionName) {
var injections = this.injections[factoryName] = this.injections[factoryName] || [];
injections.push({ property: property, fullName: injectionName });
},

destroy: function() {
eachDestroyable(this, function(item) {
item.isDestroying = true;
});

eachDestroyable(this, function(item) {
item.destroy();
});

this.isDestroyed = true;
}
};

function isSingleton(container, fullName) {
var singleton = option(container, fullName, 'singleton');

return singleton !== false;
}

function applyInjections(container, value, injections) {
if (!injections) { return; }

var injection, lookup;

for (var i=0, l=injections.length; i<l; i++) {
injection = injections[i];
lookup = container.lookup(injection.fullName);
container.set(value, injection.property, lookup);
}
}

function option(container, fullName, optionName) {
var options = container.options[fullName];

if (options && options[optionName] !== undefined) {
return options[optionName];
}

var type = fullName.split(":")[0];
options = container.typeOptions[type];

if (options) {
return options[optionName];
}
}

function instantiate(container, fullName) {
var splitName = fullName.split(":"),
type = splitName[0], name = splitName[1],
value;

var factory = container.resolve(fullName);

if (option(container, fullName, 'instantiate') === false) {
return factory;
}

if (factory) {
value = factory.create({ container: container });

var injections = [];
injections = injections.concat(container.typeInjections[type] || []);
injections = injections.concat(container.injections[fullName] || []);

applyInjections(container, value, injections);

return value;
}
}

function eachDestroyable(container, callback) {
var cache = container.cache;

for (var prop in cache) {
if (!cache.hasOwnProperty(prop)) { continue; }
if (option(container, prop, 'instantiate') === false) { continue; }
callback(cache[prop]);
}
}

Ember.Container = Container;
30 changes: 30 additions & 0 deletions packages/container/package.json
@@ -0,0 +1,30 @@
{
"name": "container",
"summary": "A lightweight dependency injection container",
"description": "A lightweight library that provides a pluggable dependency injection container designed for use in Ember.js",
"homepage": "https://github.com/emberjs/ember.js",
"authors": ["Yehuda Katz", "Tom Dale"],
"version": "1.0.0-pre.2",

"dependencies": {
"spade": "~> 1.0.0"
},

"directories": {
"lib": "lib"
},

"bpm:build": {
"bpm_libs.js": {
"files": ["lib"],
"modes": "*"
},

"handlebars/bpm_tests.js": {
"files": ["tests"],
"modes": ["debug"]
}
}
}


181 changes: 181 additions & 0 deletions packages/container/tests/container_test.js
@@ -0,0 +1,181 @@
var get = Ember.get;

module("Container");

function factory() {
var Klass = function(container) {
this.container = container;
};

Klass.prototype.destroy = function() {
this.isDestroyed = true;
};

Klass.create = function(options) {
return new Klass(options.container);
};

return Klass;
}

test("A registered factory returns the same instance each time", function() {
var container = new Ember.Container();
var PostController = factory();

container.register('controller', 'post', PostController);

var postController = container.lookup('controller:post');

ok(postController instanceof PostController, "The lookup is an instance of the factory");

equal(postController, container.lookup('controller:post'));
});

test("A container lookup has access to the container", function() {
var container = new Ember.Container();
var PostController = factory();

container.register('controller', 'post', PostController);

var postController = container.lookup('controller:post');

equal(postController.container, container);
});

test("A factory type with a registered injection receives the injection", function() {
var container = new Ember.Container();
var PostController = factory();
var Store = factory();

container.register('controller', 'post', PostController);
container.register('store', 'main', Store);

container.typeInjection('controller', 'store', 'store:main');

var postController = container.lookup('controller:post');
var store = container.lookup('store:main');

equal(postController.store, store);
});

test("An individual factory with a registered injection receives the injection", function() {
var container = new Ember.Container();
var PostController = factory();
var Store = factory();

container.register('controller', 'post', PostController);
container.register('store', 'main', Store);

container.injection('controller:post', 'store', 'store:main');

var postController = container.lookup('controller:post');
var store = container.lookup('store:main');

equal(postController.store, store);
});

test("A factory with both type and individual injections", function() {
var container = new Ember.Container();
var PostController = factory();
var Store = factory();
var Router = factory();

container.register('controller', 'post', PostController);
container.register('store', 'main', Store);
container.register('router', 'main', Router);

container.injection('controller:post', 'store', 'store:main');
container.typeInjection('controller', 'router', 'router:main');

var postController = container.lookup('controller:post');
var store = container.lookup('store:main');
var router = container.lookup('router:main');

equal(postController.store, store);
equal(postController.router, router);
});

test("A non-singleton factory is never cached", function() {
var container = new Ember.Container();
var PostView = factory();

container.register('view', 'post', PostView, { singleton: false });

var postView1 = container.lookup('view:post');
var postView2 = container.lookup('view:post');

ok(postView1 !== postView2, "Non-singletons are not cached");
});

test("A non-instantiated property is not instantiated", function() {
var container = new Ember.Container();

var template = function() {};
container.register('template', 'foo', template, { instantiate: false });
equal(container.lookup('template:foo'), template);
});

test("A failed lookup returns undefined", function() {
var container = new Ember.Container();

equal(container.lookup("doesnot:exist"), undefined);
});

test("Destroying the container destroys any cached singletons", function() {
var container = new Ember.Container();
var PostController = factory();
var PostView = factory();
var template = function() {};

container.register('controller', 'post', PostController);
container.register('view', 'post', PostView, { singleton: false });
container.register('template', 'post', template, { instantiate: false });

container.injection('controller:post', 'postView', 'view:post');

var postController = container.lookup('controller:post');
var postView = postController.postView;

ok(postView instanceof PostView, "The non-singleton was injected");

container.destroy();

ok(postController.isDestroyed, "Singletons are destroyed");
ok(!postView.isDestroyed, "Non-singletons are not destroyed");
});

test("The container can take a hook to resolve factories lazily", function() {
var container = new Ember.Container();
var PostController = factory();

container.resolve = function(fullName) {
if (fullName === 'controller:post') {
return PostController;
}
};

var postController = container.lookup('controller:post');

ok(postController instanceof PostController, "The correct factory was provided");
});

test("The container can get options that should be applied to all factories for a given type", function() {
var container = new Ember.Container();
var PostView = factory();

container.resolve = function(fullName) {
if (fullName === 'view:post') {
return PostView;
}
};

container.optionsForType('view', { singleton: false });

var postView1 = container.lookup('view:post');
var postView2 = container.lookup('view:post');

ok(postView1 instanceof PostView, "The correct factory was provided");
ok(postView2 instanceof PostView, "The correct factory was provided");

ok(postView1 !== postView2, "The two lookups are different");
});
1 change: 1 addition & 0 deletions packages/ember-application/lib/system.js
@@ -1,2 +1,3 @@
require('container');
require('ember-application/system/dag'); require('ember-application/system/dag');
require('ember-application/system/application'); require('ember-application/system/application');

0 comments on commit 25f4446

Please sign in to comment.