Skip to content

Commit

Permalink
Eliminates ugly constructor hack. Fixes #29
Browse files Browse the repository at this point in the history
  • Loading branch information
Evan Cowden committed Jan 29, 2017
1 parent 723f537 commit 40249fb
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 254 deletions.
55 changes: 11 additions & 44 deletions lib/pluto.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ function pluto() {

function getArgumentNames(func) {
const funStr = func.toString()
return funStr.slice(funStr.indexOf('(') + 1, funStr.indexOf(')')).match(/([^\s,]+)/g)
const argumentNames = funStr.slice(funStr.indexOf('(') + 1, funStr.indexOf(')')).match(/([^\s,]+)/g)

// the above can return `null` when there are no argumentNames
return argumentNames || []
}

function createFactoryResolver(factory) {
Expand All @@ -41,53 +44,17 @@ function pluto() {

function createConstructorResolver(Constructor) {
return co.wrap(function* () {
/*
* It turns out that dynamically invoking constructor functions is a bit tricky. I have decided to
* manually invoke them for now until I can do further research and choose the best alternative.
* For now, constructor injection will be limited to eight arguments.
*/
const argumentNames = getArgumentNames(Constructor)
if (!argumentNames || argumentNames.length === 0) {
return new Constructor()
}

const argumentCount = argumentNames.length
const args = yield getAll(argumentNames)
if (argumentCount === 1) {
return new Constructor(args[0])
}

if (argumentCount === 2) {
return new Constructor(args[0], args[1])
}

if (argumentCount === 3) {
return new Constructor(args[0], args[1], args[2])
}

if (argumentCount === 4) {
return new Constructor(args[0], args[1], args[2], args[3])
}

if (argumentCount === 5) {
return new Constructor(args[0], args[1], args[2], args[3], args[4])
}

if (argumentCount === 6) {
return new Constructor(args[0], args[1], args[2], args[3], args[4], args[5])
}

if (argumentCount === 7) {
return new Constructor(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
}

if (argumentCount === 8) {
return new Constructor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
}
// For future reference,
// this can be done with the spread operator in Node versions >= v5. e.g.,
//
// return new Constructor(...args)
//
// For now, this workaround is a good middle ground.

const msg = `Pluto cannot inject constructor functions with ${MAX_CONSTRUCTOR_ARGUMENTS} or more arguments ` +
"at this time (it's a long story). Please use a non-constructor factory function instead or consider injecting fewer dependencies."
throw Error(msg)
return new (Constructor.bind.apply(Constructor, [null].concat(args)))
})
}

Expand Down
210 changes: 0 additions & 210 deletions lib/plutoSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,216 +229,6 @@ test('when a constructor has one parameter, bind.get(name) returns the new Const
t.is(actual.param1, 'the first injected parameter')
})

test('when a constructor has two parameters, bind.get(name) returns the new Constructor() with the parameters injected', function* (t) {
const Root = function ($param1, $param2) {
this.param1 = $param1
this.param2 = $param2
}

const bind = pluto()
bind('$Root').toConstructor(Root)
bind('$param1').toInstance('the first injected parameter')
bind('$param2').toInstance('the second injected parameter')

const actual = yield bind.get('$Root')

t.truthy(actual instanceof Root)
t.is(actual.param1, 'the first injected parameter')
t.is(actual.param2, 'the second injected parameter')
})

test('when a constructor has three parameters, bind.get(name) returns the new Constructor() with the parameters injected', function* (t) {
const Root = function ($param1, $param2, $param3) {
this.param1 = $param1
this.param2 = $param2
this.param3 = $param3
}

const bind = pluto()
bind('$Root').toConstructor(Root)
bind('$param1').toInstance('the first injected parameter')
bind('$param2').toInstance('the second injected parameter')
bind('$param3').toInstance('the third injected parameter')

const actual = yield bind.get('$Root')

t.truthy(actual instanceof Root)
t.is(actual.param1, 'the first injected parameter')
t.is(actual.param2, 'the second injected parameter')
t.is(actual.param3, 'the third injected parameter')
})

test('when a constructor has four parameters, module.get(name) returns the new Constructor() with the parameters injected', function* (t) {
const Root = function ($param1, $param2, $param3, $param4) {
this.param1 = $param1
this.param2 = $param2
this.param3 = $param3
this.param4 = $param4
}

const bind = pluto()
bind('$Root').toConstructor(Root)
bind('$param1').toInstance('the first injected parameter')
bind('$param2').toInstance('the second injected parameter')
bind('$param3').toInstance('the third injected parameter')
bind('$param4').toInstance('the fourth injected parameter')

const actual = yield bind.get('$Root')

t.truthy(actual instanceof Root)
t.is(actual.param1, 'the first injected parameter')
t.is(actual.param2, 'the second injected parameter')
t.is(actual.param3, 'the third injected parameter')
t.is(actual.param4, 'the fourth injected parameter')
})

test('when a constructor has five parameters, module.get(name) returns the new Constructor() with the parameters injected', function* (t) {
const Root = function ($param1, $param2, $param3, $param4, $param5) {
this.param1 = $param1
this.param2 = $param2
this.param3 = $param3
this.param4 = $param4
this.param5 = $param5
}

const bind = pluto()
bind('$Root').toConstructor(Root)
bind('$param1').toInstance('the first injected parameter')
bind('$param2').toInstance('the second injected parameter')
bind('$param3').toInstance('the third injected parameter')
bind('$param4').toInstance('the fourth injected parameter')
bind('$param5').toInstance('the fifth injected parameter')

const actual = yield bind.get('$Root')

t.truthy(actual instanceof Root)
t.is(actual.param1, 'the first injected parameter')
t.is(actual.param2, 'the second injected parameter')
t.is(actual.param3, 'the third injected parameter')
t.is(actual.param4, 'the fourth injected parameter')
t.is(actual.param5, 'the fifth injected parameter')
})

test('when a constructor has six parameters, module.get(name) returns the new Constructor() with the parameters injected', function* (t) {
const Root = function ($param1, $param2, $param3, $param4, $param5, $param6) {
this.param1 = $param1
this.param2 = $param2
this.param3 = $param3
this.param4 = $param4
this.param5 = $param5
this.param6 = $param6
}

const bind = pluto()
bind('$Root').toConstructor(Root)
bind('$param1').toInstance('the first injected parameter')
bind('$param2').toInstance('the second injected parameter')
bind('$param3').toInstance('the third injected parameter')
bind('$param4').toInstance('the fourth injected parameter')
bind('$param5').toInstance('the fifth injected parameter')
bind('$param6').toInstance('the sixth injected parameter')

const actual = yield bind.get('$Root')

t.truthy(actual instanceof Root)
t.is(actual.param1, 'the first injected parameter')
t.is(actual.param2, 'the second injected parameter')
t.is(actual.param3, 'the third injected parameter')
t.is(actual.param4, 'the fourth injected parameter')
t.is(actual.param5, 'the fifth injected parameter')
t.is(actual.param6, 'the sixth injected parameter')
})

test('when a constructor has seven parameters, module.get(name) returns the new Constructor() with the parameters injected', function* (t) {
const Root = function ($param1, $param2, $param3, $param4, $param5, $param6, $param7) {
this.param1 = $param1
this.param2 = $param2
this.param3 = $param3
this.param4 = $param4
this.param5 = $param5
this.param6 = $param6
this.param7 = $param7
}

const bind = pluto()
bind('$Root').toConstructor(Root)
bind('$param1').toInstance('the first injected parameter')
bind('$param2').toInstance('the second injected parameter')
bind('$param3').toInstance('the third injected parameter')
bind('$param4').toInstance('the fourth injected parameter')
bind('$param5').toInstance('the fifth injected parameter')
bind('$param6').toInstance('the sixth injected parameter')
bind('$param7').toInstance('the seventh injected parameter')

const actual = yield bind.get('$Root')

t.truthy(actual instanceof Root)
t.is(actual.param1, 'the first injected parameter')
t.is(actual.param2, 'the second injected parameter')
t.is(actual.param3, 'the third injected parameter')
t.is(actual.param4, 'the fourth injected parameter')
t.is(actual.param5, 'the fifth injected parameter')
t.is(actual.param6, 'the sixth injected parameter')
t.is(actual.param7, 'the seventh injected parameter')
})

test('when a constructor has eight parameters, module.get(name) returns the new Constructor() with the parameters injected', function* (t) {
const Root = function ($param1, $param2, $param3, $param4, $param5, $param6, $param7, $param8) {
this.param1 = $param1
this.param2 = $param2
this.param3 = $param3
this.param4 = $param4
this.param5 = $param5
this.param6 = $param6
this.param7 = $param7
this.param8 = $param8
}

const bind = pluto()
bind('$Root').toConstructor(Root)
bind('$param1').toInstance('the first injected parameter')
bind('$param2').toInstance('the second injected parameter')
bind('$param3').toInstance('the third injected parameter')
bind('$param4').toInstance('the fourth injected parameter')
bind('$param5').toInstance('the fifth injected parameter')
bind('$param6').toInstance('the sixth injected parameter')
bind('$param7').toInstance('the seventh injected parameter')
bind('$param8').toInstance('the eighth injected parameter')

const actual = yield bind.get('$Root')

t.truthy(actual instanceof Root)
t.is(actual.param1, 'the first injected parameter')
t.is(actual.param2, 'the second injected parameter')
t.is(actual.param3, 'the third injected parameter')
t.is(actual.param4, 'the fourth injected parameter')
t.is(actual.param5, 'the fifth injected parameter')
t.is(actual.param6, 'the sixth injected parameter')
t.is(actual.param7, 'the seventh injected parameter')
t.is(actual.param8, 'the eighth injected parameter')
})

test('when a constructor has nine parameters, module.get(name) throws an exception', function* (t) {
// TODO: this is an issue that should be easy to work around nowadays
const Root = function ($param1, $param2, $param3, $param4, $param5, $param6, $param7, $param8, $param9) {}

const bind = pluto()
bind('$Root').toConstructor(Root)
bind('$param1').toInstance('the first injected parameter')
bind('$param2').toInstance('the second injected parameter')
bind('$param3').toInstance('the third injected parameter')
bind('$param4').toInstance('the fourth injected parameter')
bind('$param5').toInstance('the fifth injected parameter')
bind('$param6').toInstance('the sixth injected parameter')
bind('$param7').toInstance('the seventh injected parameter')
bind('$param8').toInstance('the eighth injected parameter')
bind('$param9').toInstance('the ninth injected parameter')

const error = yield t.throws(bind.get('$Root'), Error)

t.is(error.message, "Pluto cannot inject constructor functions with 8 or more arguments at this time (it's a long story). Please use a non-constructor factory function instead or consider injecting fewer dependencies.")
})

test('bind.get(name) rejects if the specified name is not mapped', function* (t) {
const bind = pluto()
const error = yield t.throws(bind.get('totally bogus key'), Error)
Expand Down

0 comments on commit 40249fb

Please sign in to comment.