Skip to content

Commit

Permalink
Many great changes for a new release
Browse files Browse the repository at this point in the history
  • Loading branch information
jcreamer898 committed Dec 23, 2014
1 parent 0712bb3 commit b9c295d
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 73 deletions.
4 changes: 4 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ module.exports = function(grunt) {
lib_test: {
files: '<%= jshint.lib_test.src %>',
tasks: ['jshint:lib_test', 'nodeunit']
},
build: {
files: '<%= jshint.lib_test.src %>',
tasks: ['concat', 'uglify']
}
}
});
Expand Down
98 changes: 65 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,93 @@ Dependency injection is a software pattern that helps increase the testability o
In other words, take the following code...

```js
function Car(make, model, color) {
this.make = make;
this.model = model;
this.color = color;

this.service = new Service();
function Car() {
this.engine = new Engine();
this.dashboard = new Dashboard();
}
```

In the preceding code, `Car` is now dependent upon `Service`. This makes it more difficult to unit test the `Car` in complete isolation. There's no way to create an
instance of a `Car` without in turn creating an instance of a `Service`.
In the preceding code, `Car` is now dependent upon `Engine` and `Dashboard`. This makes it more difficult to unit test the `Car` in complete isolation. There's no way to create an
instance of a `Car` without in turn creating an instance of an `Engine` and a `Dashboard`.

Dependency injection allows for a small bit of configuration that says, 'When a class needs a dependency named x, create an instance of y'.

So, you can now have code that would look like...

```js
function Car(make, model, color, service) {
this.make = make;
this.model = model;
this.color = color;
this.service = service;

bindr(this, arguments);
function Car(engine, dashboard) {
this.engine = engine;
this.dashboard = dashboard;
}

bindr.bind('service', Service);
function Engine() {}
function Dashboard() {}

Car = bindr(Car, ['engine', 'dashboard']);

bindr.bind({
"engine": Engine,
"dashboard": Dashboard
});
```

Now `this.service` will be assigned a new instance of `Service`. **NOTE:** if you pass something in for `service` when creating
a new `Car`, the argument will override the injection.
Now `this.engine` will be assigned a new instance of `Engine`, and likewise for `Dashboard`.

`bindr` comes in and allows you to switch a dependencies constructor out. In production code, the `Car` might be dependent upon `Service`, but when writing unit tests,
`bindr` allows you to swap that dependency out for a `FakeService`.
`bindr` comes in and allows you to switch a dependencies constructor out. In production code, the `Car` might be dependent upon `Engine`, but when writing unit tests,
`bindr` allows you to swap that dependency out for a `FakeEngine`.

```js
var Car = require('Car'),
bindr = require('bindr');

function FakeService() {}
var Car = require('Car');

bindr.bind('service', FakeService);
function FakeEngine() {}
function FakeDashboard() {}

describe('when a car is created', function() {
it('should be awesome', function() {
var car = new Car();

expect(car.service instanceof FakeService).to.be.ok();
var car = new Car(new FakeEngine(), new FakeDashboard());
});
});
```

In the unit tests, you simply bind service to `FakeService` instead of `Service` and the dependency will automatically get passed in.
Now you are able to test your `Car` in complete isolation.

Another great advantage of Bindr is that it doesn't change your existing functions other than creating a small wrapper. You can simply plug them in your existing constructors, and use them as you normally would.

# API
There is a UMD wrapper around `bindr`, so you can either require it as an node, or AMD module, or it will expose `bindr` on the `window`.

### `bindr(fn, dependencies)`
Wraps a constructor for allowing dependency injection.

* `fn` {function}: the constructor you want to wrap
* `dep` {array}: list of the dependencies of this constructor

Returns a wrapped constructor

### AMD
### `bindr.bind(options)`
Register dependencies with `bindr`.

* `options` {object}: an object containing dependencies

### `bindr.bind(name).to(Dependency)`
Register a `Dependency` to `name`.

* `name` {string}: The name of the dependency to create
* `dependency` {function}: The dependency to inject

### `bindr.reset()`
Clears all registered dependencies.

### `bindr.trace()`
Returns an object containing a trace of all dependencies

### `bindr.trace(name)`
Returns an object containing a trace of all dependencies for a given instance.

* `name` {object}: An instance of a function registered with `bindr()`


<!-- ### AMD
Another nice feature of `bindr` is its use in AMD projects with Require.js.
In a typical AMD module, you'd have code such as the following...
Expand All @@ -78,7 +110,7 @@ define(['service'], function(Service) {
});
```
You can see that when you require `car`, you automtically end up requiring `service` as well. `bindr` allows you to remove that dependency from the `car` module and swap
You can see that when you require `car`, you automtically end up requiring `service` as well. `bindr` allows you to remove that dependency from the `car` module and swap
out what service the car uses.
```js
Expand All @@ -104,13 +136,13 @@ define(['bindr'], function (bindr) {
this.model = model;
this.color = color;
this.service = service;
bindr(this, arguments);
}
return Car;
});
```
``` -->

This way you can easily swap out the `service` dependency of the `car`.

Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bindr",
"version": "0.0.1",
"version": "0.2.0",
"main": [
"./dist/bindr.js",
"./dist/bindr.min.js"
Expand Down
86 changes: 78 additions & 8 deletions dist/bindr.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*! bindr - v0.0.1 - 2013-07-27
* Copyright (c) 2013 ; Licensed */
/*! bindr - v0.0.1 - 2014-12-23
* Copyright (c) 2014 ; Licensed */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
Expand Down Expand Up @@ -62,36 +62,106 @@ function Bindr() {
//
// bindr.bind('service', Service);
Bindr.prototype.bind = function(from, dependency) {
var injection;

if (typeof from === "object") {
for(injection in from) {
if (from.hasOwnProperty(injection)) {
this.injections[injection] = new Injection(injection, from[injection]);
}
}
return;
}

this.injections[from] = new Injection(from, dependency);
return this.injections[from];
};

// Process all of the incoming arguments to a function and inject
// the functions dependencies.
Bindr.prototype.run = function(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop) && this.injections[prop]) {
obj[prop] = create.apply(this.injections[prop].dependency, this.injections[prop].args);
Bindr.prototype.run = function(obj, args) {
var dependency, i,
name = Object.getPrototypeOf(obj).constructor.name;

for(i = 0; i < obj.$binder.constructorArgs.length; i++) {
dependency = obj.$binder.deps[i];

if(this.injections[dependency]) {
Array.prototype.splice.call(args, i, 0, create.apply(
this.injections[dependency].dependency,
this.injections[dependency].args
));

obj.$binder.dependsOn.push(dependency);
dependencyGraph[name] = dependencyGraph[name] || {};
dependencyGraph[name][dependency] = dependencyGraph[name][dependency] ||
dependencyGraph[this.injections[dependency].dependency.prototype.constructor.name] || {};
}
}
};

var rArgs = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;

var dependencyGraph = {};

// Setup a constructor for DI
Bindr.prototype.setupConstructor = function(fn, deps) {
var Injectable = function Injectable() {
_bindr.run(this, arguments);

Injectable.prototype.constructor.apply(this, arguments);
};

var matches = rArgs.exec(fn.toString()),
constructorArgs;

if (matches.length) {
constructorArgs = matches[1].split(",");
}

Injectable.prototype.constructor = fn;
Injectable.prototype.$binder = {
name: fn.prototype.constructor.name,
deps: deps,
constructorArgs: constructorArgs,
dependsOn: []
};

return Injectable;
};

Bindr.prototype.trace = function(name) {
if (typeof name === "object") {
name = Object.getPrototypeOf(name).constructor.name;
return dependencyGraph[name];
}

return dependencyGraph;
};

Bindr.prototype.reset = function() {
this.injections = {};
dependencyGraph = {};
};

// Create a local instance of `Bindr`.
var _bindr = new Bindr();

// Return this function to the window. This allows for aliasing `Bindr.run` to just `bindr()`.
// Return this function to the window. This allows for aliasing `Bindr.setupConstructor` to just `bindr()`.
//
// bindr(this, arguments);
var bindr = function() {
if (arguments.length === 2) {
return _bindr.run.apply(_bindr, arguments);
return _bindr.setupConstructor.apply(_bindr, arguments);
}

return this;
};

// Setup an API for the `bind` method.
bindr.bind = _bindr.bind.bind(_bindr);
bindr.trace = _bindr.trace.bind(_bindr);
bindr.reset = _bindr.reset.bind(_bindr);

return bindr;

Expand Down
6 changes: 3 additions & 3 deletions dist/bindr.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion example/browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<body>
<pre id="car"></pre>

<script src="../dist/bindr.js"></script>
<script src="../../dist/bindr.js"></script>
<script src="js/demo.js"></script>
</body>
</html>
44 changes: 30 additions & 14 deletions example/browser/js/demo.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
function Model() {
bindr(this, arguments);
function Car(engine, dashboard) {
this.engine = engine;
this.dashboard = dashboard;
}

function Car(make, model, color, service) {
this.make = make;
this.model = model;
this.color = color;
this.service = service;

bindr(this, arguments);
function Engine() {}

function Dashboard(ac, radio) {
this.ac = ac;
this.radio = radio;
}

function Ac () {}
function Radio (stereo, cd) {
this.stereo = stereo;
this.cd = cd;
}

function Service() {}
function FakeService() {}
function Stereo() {}
function Cd() {}

Car = bindr(Car, ['engine', 'dashboard']);
Dashboard = bindr(Dashboard, ['ac', 'radio']);
Radio = bindr(Radio, ['stereo', 'cd']);

bindr.bind('service', FakeService);
bindr.bind({
"engine": Engine,
"dashboard": Dashboard,
"ac": Ac,
"radio": Radio,
"stereo": Stereo,
"cd": Cd
});

var ford = new Car('Ford', 'Fusion', 'Maroon');
var ford = new Car();

document.getElementById('car').innerHTML = JSON.stringify(ford, null, 4);
document.getElementById('car').innerHTML = JSON.stringify(ford, null, 4);
Loading

0 comments on commit b9c295d

Please sign in to comment.