tame_the_beast 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Guardfile +8 -0
- data/LICENSE.md +20 -0
- data/Readme.md +124 -0
- data/lib/tame_the_beast.rb +12 -0
- data/lib/tame_the_beast/armory.rb +142 -0
- data/lib/tame_the_beast/container.rb +23 -0
- data/lib/tame_the_beast/reg_entry.rb +26 -0
- data/lib/tame_the_beast/stub.rb +11 -0
- data/lib/tame_the_beast/version.rb +3 -0
- data/spec/acceptance/tame_the_beast_spec.rb +268 -0
- data/tame_the_beast.gemspec +23 -0
- metadata +133 -0
data/Guardfile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
notification :growl
|
2
|
+
|
3
|
+
cli_options = `cat $HOME/.rspec .rspec 2>/dev/null`.gsub("\n", ' ')
|
4
|
+
guard 'rspec', :version => 2, :cli => cli_options do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
end
|
data/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Thomas Stratmann
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Readme.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# TameTheBeast
|
2
|
+
|
3
|
+
## What?
|
4
|
+
|
5
|
+
TameTheBeast lets you define the creation of components of your application in a central container.
|
6
|
+
It is inspired by [this blog post](http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc) by
|
7
|
+
Jim Weirich.
|
8
|
+
|
9
|
+
## Why?
|
10
|
+
|
11
|
+
A central singleton is eventually inevitable for any application, but if you are like me, it tends to
|
12
|
+
suck up and swallow functionality that really should be separate from each other. You end up with one big blob
|
13
|
+
of unmanageable spaghetti since everything is tightly coupled.
|
14
|
+
|
15
|
+
Dependency injection is a way out that has proven to be effective in OOP. TameTheBeast aims at giving some nice lightweight sugar above it. (What it actually does is it patronizes you. Have been warned.)
|
16
|
+
|
17
|
+
## Show me code!
|
18
|
+
|
19
|
+
container = TameTheBease.new
|
20
|
+
|
21
|
+
container.register(:app_state) do
|
22
|
+
{ :running => false, :initializing => true }
|
23
|
+
end
|
24
|
+
|
25
|
+
container.register(:splash_window, :using => :app_state) do |h|
|
26
|
+
SplashWindow.new(h.app_state)
|
27
|
+
end
|
28
|
+
|
29
|
+
# ...many more components registered here...
|
30
|
+
|
31
|
+
resolution = container.resolve(:for => [:splash_window, ...])
|
32
|
+
# => { :splash_window => <SplashWindow...> }
|
33
|
+
|
34
|
+
So this is the pattern:
|
35
|
+
|
36
|
+
* register your components with a slot (which must be a Symbol) and a constructing block
|
37
|
+
* the block argument gives you access to your dependencies. I call it the **inject hash**.
|
38
|
+
* declare your dependencies with the :using option, they will appear in the inject hash
|
39
|
+
* resolve for the components you need to kick your application up
|
40
|
+
|
41
|
+
Explicitly declaring your dependencies in this way _really_ helps in refactoring later!
|
42
|
+
|
43
|
+
## Features
|
44
|
+
|
45
|
+
### Sugar
|
46
|
+
|
47
|
+
#### Hash access
|
48
|
+
|
49
|
+
Access the resolution and the inject hashes by `[]` or by name.
|
50
|
+
That means you can say `resolution[:app_state]` as well as `resolution.app_state`. Same for the inject hashes.
|
51
|
+
|
52
|
+
#### Control what's getting resolved for
|
53
|
+
|
54
|
+
There are three ways to accomplish that:
|
55
|
+
|
56
|
+
container.resolve_for :configuration, :app_state
|
57
|
+
# or, at register time
|
58
|
+
container.register(:printer_dialog, :resolve => true)
|
59
|
+
# or, at resolution time
|
60
|
+
container.resolve(:for => [:configuration, :app_state])
|
61
|
+
|
62
|
+
All components mentioned in any of the three ways will get resolved for. Call `resolve_for` as often as you like.
|
63
|
+
|
64
|
+
#### Slim dependency notation
|
65
|
+
|
66
|
+
Slots have to be symbols, but when declaring dependencies or resolving,
|
67
|
+
you can reference them as strings like this:
|
68
|
+
|
69
|
+
container.register(:foo, :using => %w{bar baz}) { ... }
|
70
|
+
|
71
|
+
container.resolve_for %w{foo bar baz}
|
72
|
+
|
73
|
+
#### Stubbing
|
74
|
+
|
75
|
+
Is this really sugar? Not sure. Anyway, you can leave off the block on `register`,
|
76
|
+
and the component will be initialized as a stub. This way you can play around with the effects of
|
77
|
+
refactorings on dependencies to some extent without having to go too deep.
|
78
|
+
|
79
|
+
The stub will yell at you when you try to use it.
|
80
|
+
|
81
|
+
### Is my container complete? Do I have a dependency loop?
|
82
|
+
|
83
|
+
Just ask.
|
84
|
+
|
85
|
+
container.complete?
|
86
|
+
container.free_of_loops?
|
87
|
+
|
88
|
+
If there are dependency loops, `resolve` will raise an exception.
|
89
|
+
|
90
|
+
### Give me a dependency graph, please.
|
91
|
+
|
92
|
+
Nothing fancy yet, but you can do
|
93
|
+
|
94
|
+
container.render_dependencies(:format => :hash)
|
95
|
+
# => { :splash_window => [:app_state], ... }
|
96
|
+
|
97
|
+
Let me know if you know of a way to visualize this easily!
|
98
|
+
|
99
|
+
### Post-injection as a last resort
|
100
|
+
|
101
|
+
I have not yet completely made up my mind about this yet, but it seems like it is not always possible to avoid circular dependencies. You can break them up and post-inject like this:
|
102
|
+
|
103
|
+
container.register(:component) do |h|
|
104
|
+
...
|
105
|
+
end.post_inject_into { |h| h.parent = h.root_something } # or similar foo
|
106
|
+
|
107
|
+
The post injection block will be called right before the resolution is returned. It is passed
|
108
|
+
the resolution.
|
109
|
+
|
110
|
+
If you think you need this feature, _really_ think hard if you cannot find a way around (I believe there usually is). Using this feature should actually give you some pain, but I could not find a reliable way to implement this.
|
111
|
+
|
112
|
+
### Multi-phase initialization / Injection of pre-existing objects
|
113
|
+
|
114
|
+
There is no such thing as incremental resolution, you cannot use a component directly while still registering construction of others.
|
115
|
+
|
116
|
+
However, you can break up the registration into multiple phases and simply inject the result of prior `resolve` runs:
|
117
|
+
|
118
|
+
container.inject(resolution_from_the_past)
|
119
|
+
|
120
|
+
If you have existing objects, you can actually keep them in a hash and inject them in this way. There is nothing special about using a `resolve` result here!
|
121
|
+
|
122
|
+
## License
|
123
|
+
|
124
|
+
Released under the MIT License. See the [LICENSE](https://github.com/schnittchen/tame_the_beast/blob/master/LICENSE.md) file for further details.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'tame_the_beast/armory'
|
2
|
+
|
3
|
+
module TameTheBeast
|
4
|
+
class Incomplete < RuntimeError; end
|
5
|
+
class CircularDependency < RuntimeError; end
|
6
|
+
class BadComponent < RuntimeError; end
|
7
|
+
class StubUsedError < RuntimeError; end
|
8
|
+
|
9
|
+
def self.new(*args)
|
10
|
+
Armory.new *args
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'tame_the_beast/reg_entry'
|
2
|
+
require 'tame_the_beast/stub'
|
3
|
+
|
4
|
+
module TameTheBeast
|
5
|
+
class Armory
|
6
|
+
def initialize
|
7
|
+
@registry = {}
|
8
|
+
@resolve_for = []
|
9
|
+
end
|
10
|
+
|
11
|
+
class ChainedDSLObject
|
12
|
+
def initialize(reg_entry)
|
13
|
+
@reg_entry = reg_entry
|
14
|
+
end
|
15
|
+
|
16
|
+
def post_inject_into(&block)
|
17
|
+
@reg_entry.post_inject_block = block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def register(slot, options = {}, &block)
|
22
|
+
using = options.delete(:using) || []
|
23
|
+
@resolve_for << slot if options[:resolve]
|
24
|
+
reg_entry = _register(slot, using, block)
|
25
|
+
return ChainedDSLObject.new reg_entry
|
26
|
+
end
|
27
|
+
|
28
|
+
def inject(hash_like)
|
29
|
+
hash_like.each do |slot, object|
|
30
|
+
block = lambda { object }
|
31
|
+
_register(slot, [], block)
|
32
|
+
end
|
33
|
+
return self
|
34
|
+
end
|
35
|
+
|
36
|
+
def complete?
|
37
|
+
@registry.each do |slot, entry|
|
38
|
+
entry.dependent_slots.each do |dependent_slot|
|
39
|
+
unless @registry.key? dependent_slot
|
40
|
+
yield dependent_slot, slot if block_given?
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
|
48
|
+
def free_of_loops?
|
49
|
+
raise Incomplete, "Armory is incomplete!" unless complete?
|
50
|
+
|
51
|
+
dependency_chains = @registry.keys.map &method(:Array)
|
52
|
+
|
53
|
+
until dependency_chains.empty?
|
54
|
+
dependency_chains = dependency_chains.map do |chain|
|
55
|
+
chain_begin, chain_end = chain.first, chain.last
|
56
|
+
|
57
|
+
@registry[chain.last].dependent_slots.map do |dependent_slot|
|
58
|
+
if dependent_slot == chain_begin
|
59
|
+
yield chain if block_given?
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
|
63
|
+
chain + [dependent_slot]
|
64
|
+
end
|
65
|
+
|
66
|
+
end.reduce([], :+)
|
67
|
+
end
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
|
71
|
+
def render_dependencies(options = {})
|
72
|
+
format = options[:format] || :hash
|
73
|
+
raise "Unknown format #{format}" unless %w{hash}.include? format.to_s
|
74
|
+
|
75
|
+
hash = Hash[@registry.map do |slot, entry|
|
76
|
+
[slot, entry.dependent_slots]
|
77
|
+
end]
|
78
|
+
|
79
|
+
return hash
|
80
|
+
end
|
81
|
+
|
82
|
+
def resolve_for(*args)
|
83
|
+
args = args.first if args.first.kind_of? Array
|
84
|
+
@resolve_for += args.map &:to_sym
|
85
|
+
return self
|
86
|
+
end
|
87
|
+
|
88
|
+
def resolve(options = {})
|
89
|
+
inject_dependent_reg_entries
|
90
|
+
|
91
|
+
assert_complete_and_free_of_loops
|
92
|
+
|
93
|
+
#do the actual resolution
|
94
|
+
@resolve_for += Array(options[:for]).map &:to_sym
|
95
|
+
#return magic hash here
|
96
|
+
resolution = Hash[@resolve_for.map { |key| [key, @registry[key].value] }]
|
97
|
+
|
98
|
+
#remove unused entries
|
99
|
+
@registry.values.reject(&:constructed?).each { |reg_entry| @registry.delete reg_entry.key }
|
100
|
+
|
101
|
+
#call post_inject_blocks
|
102
|
+
component_hash = Container.from_reg_entries @registry.values
|
103
|
+
@registry.values.map(&:post_inject_block).compact.each do |post_inject_block|
|
104
|
+
post_inject_block.call(component_hash)
|
105
|
+
end
|
106
|
+
|
107
|
+
@registry.clear
|
108
|
+
|
109
|
+
return Container[resolution]
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def _register(slot, using, block)
|
115
|
+
slot = slot.to_sym
|
116
|
+
using = Array(using).map &:to_sym
|
117
|
+
block = block_or_default_for_slot slot, block
|
118
|
+
|
119
|
+
reg_entry = RegEntry.new :slot => slot, :constructor => block, :dependent_slots => using
|
120
|
+
@registry[slot] = reg_entry
|
121
|
+
return reg_entry
|
122
|
+
end
|
123
|
+
|
124
|
+
def block_or_default_for_slot(key, block)
|
125
|
+
return block || lambda do
|
126
|
+
Stub.new key.inspect
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def inject_dependent_reg_entries
|
131
|
+
@registry.values.each do |reg_entry|
|
132
|
+
reg_entry.dependent_reg_entries = reg_entry.dependent_slots.map { |slot| @registry[slot] }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def assert_complete_and_free_of_loops
|
137
|
+
complete? { |dependent_slot, slot| raise Incomplete, "No component #{dependent_slot} defined (needed by #{slot}" }
|
138
|
+
free_of_loops? { |bad_chain| raise CircularDependency, "Circular dependency: #{bad_chain.join ' '}" }
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module TameTheBeast
|
2
|
+
class Container < Hash
|
3
|
+
def self.[](*args)
|
4
|
+
new.replace Hash[*args]
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.from_reg_entries(entries)
|
8
|
+
self[entries.map { |re| [re.key, re.value] }]
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(sym)
|
12
|
+
self[sym]
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
fetch key, &method(:_bad_access)
|
17
|
+
end
|
18
|
+
|
19
|
+
def _bad_access(sym)
|
20
|
+
raise BadComponent, "component #{sym} unknown or unavailable"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'tame_the_beast/container'
|
2
|
+
|
3
|
+
module TameTheBeast
|
4
|
+
class RegEntry
|
5
|
+
attr_reader :key, :dependent_slots, :constructed
|
6
|
+
attr_writer :dependent_reg_entries
|
7
|
+
attr_accessor :post_inject_block
|
8
|
+
alias_method :constructed?, :constructed
|
9
|
+
|
10
|
+
def initialize(data)
|
11
|
+
@key, @constructor, @dependent_slots = data.values_at :slot, :constructor, :dependent_slots
|
12
|
+
@post_inject_block, @constructed = nil, false
|
13
|
+
end
|
14
|
+
|
15
|
+
def value
|
16
|
+
return @value if @constructed
|
17
|
+
@value = @constructor.call(constructor_argument).tap { @constructed = true }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def constructor_argument
|
23
|
+
Container.from_reg_entries @dependent_reg_entries
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'tame_the_beast'
|
2
|
+
|
3
|
+
module TameTheBeast
|
4
|
+
describe TameTheBeast do
|
5
|
+
subject { TameTheBeast.new }
|
6
|
+
|
7
|
+
describe ".resolve" do
|
8
|
+
context "with registered dependencies a -> b -> c" do
|
9
|
+
before(:each) do
|
10
|
+
@component_hashes = {}
|
11
|
+
subject.register(:a, :using => :b) do |c|
|
12
|
+
@component_hashes[:a] = c
|
13
|
+
{ :item => :a }
|
14
|
+
end
|
15
|
+
subject.register(:b, :using => :c) do |c|
|
16
|
+
@component_hashes[:b] = c
|
17
|
+
{ :item => :b }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should detect that c has not been registered" do
|
22
|
+
lambda { subject.resolve(:for => %w{a b}) }.should raise_error(described_class::Incomplete)
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with registered dependency c -> a" do
|
26
|
+
before(:each) { subject.register(:c, :using => :a) { 2 } }
|
27
|
+
|
28
|
+
it "should detect circular dependency" do
|
29
|
+
lambda { subject.resolve(:for => %w{a b c}) }.should raise_error(described_class::CircularDependency)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "with simple leaf node c" do
|
34
|
+
before(:each) do
|
35
|
+
subject.register(:c) do |c|
|
36
|
+
@component_hashes[:c] = c
|
37
|
+
{ :item => :c }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should call constructor blocks with an argument" do
|
42
|
+
subject.resolve(:for => %w{a b c})
|
43
|
+
|
44
|
+
@component_hashes.should have_key(:a)
|
45
|
+
@component_hashes.should have_key(:b)
|
46
|
+
@component_hashes.should have_key(:c)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should pass sparse component hash to constructor blocks" do
|
50
|
+
subject.resolve(:for => %w{a b c})
|
51
|
+
|
52
|
+
@component_hashes[:a].should have(1).item
|
53
|
+
@component_hashes[:b].should have(1).item
|
54
|
+
@component_hashes[:c].should be_empty
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should raise BadComponent when trying to access an empty slot in a component hash" do
|
58
|
+
subject.resolve(:for => %w{a b c})
|
59
|
+
|
60
|
+
chash = @component_hashes[:a]
|
61
|
+
lambda { chash.a }.should raise_error(described_class::BadComponent)
|
62
|
+
lambda { chash.c }.should raise_error(described_class::BadComponent)
|
63
|
+
lambda { chash.x }.should raise_error(described_class::BadComponent)
|
64
|
+
|
65
|
+
chash = @component_hashes[:b]
|
66
|
+
lambda { chash.a }.should raise_error(described_class::BadComponent)
|
67
|
+
lambda { chash.b }.should raise_error(described_class::BadComponent)
|
68
|
+
|
69
|
+
chash = @component_hashes[:c]
|
70
|
+
lambda { chash.a }.should raise_error(described_class::BadComponent)
|
71
|
+
lambda { chash.b }.should raise_error(described_class::BadComponent)
|
72
|
+
lambda { chash.c }.should raise_error(described_class::BadComponent)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should pass component hash to constructor blocks containing the right components" do
|
76
|
+
subject.resolve(:for => %w{a b c})
|
77
|
+
|
78
|
+
@component_hashes[:a].b[:item].should == :b
|
79
|
+
@component_hashes[:b].c[:item].should == :c
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "result" do
|
83
|
+
let(:resolution) { subject.resolve(:for => %w{a b}) }
|
84
|
+
|
85
|
+
it "contains requested components" do
|
86
|
+
# resolution = subject.resolve(:for => %w{a b})
|
87
|
+
resolution.should have(2).items
|
88
|
+
resolution[:a][:item].should == :a
|
89
|
+
resolution[:b][:item].should == :b
|
90
|
+
end
|
91
|
+
|
92
|
+
it "exposes components as methods as well" do
|
93
|
+
resolution.a.should be_equal(resolution[:a])
|
94
|
+
resolution.b.should be_equal(resolution[:b])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ".inject" do
|
99
|
+
it "is chainable" do
|
100
|
+
subject.inject({}).should be_equal(subject)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "effect of .inject" do
|
105
|
+
before(:each) do
|
106
|
+
subject.inject :injected => 'injected'
|
107
|
+
end
|
108
|
+
|
109
|
+
it "allows me to resolve for injected objects" do
|
110
|
+
resolution = subject.resolve(:for => %w{injected})
|
111
|
+
|
112
|
+
resolution.should have_key(:injected)
|
113
|
+
resolution[:injected].should == 'injected'
|
114
|
+
end
|
115
|
+
|
116
|
+
it "allows me to use injected object for injecting into another slot object" do
|
117
|
+
subject.register(:x, :using => :injected) do |c|
|
118
|
+
@component_hashes[:x] = c
|
119
|
+
"x"
|
120
|
+
end
|
121
|
+
subject.resolve :for => :x
|
122
|
+
|
123
|
+
chash = @component_hashes[:x]
|
124
|
+
chash.should have_key(:injected)
|
125
|
+
chash[:injected].should == "injected"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "with leaf node c subject to post injection" do
|
131
|
+
before(:each) do
|
132
|
+
subject.register(:c) do |c|
|
133
|
+
@component_hashes[:c] = c
|
134
|
+
{ :item => :c }
|
135
|
+
end.post_inject_into { |c| @component_hashes[:c_post] = c }
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should invoke post_inject_into block with an argument" do
|
139
|
+
subject.resolve(:for => %w{a})
|
140
|
+
|
141
|
+
@component_hashes.should have_key(:c_post)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should invoke post_inject_into block with sparse component hash" do
|
145
|
+
subject.resolve(:for => %w{a})
|
146
|
+
|
147
|
+
@component_hashes[:c_post].should have(3).items
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should invoke post_inject_into block with component hash containing needed components" do
|
151
|
+
subject.resolve(:for => %w{a})
|
152
|
+
|
153
|
+
chash = @component_hashes[:c_post]
|
154
|
+
chash[:a][:item].should == :a
|
155
|
+
chash[:b][:item].should == :b
|
156
|
+
chash[:c][:item].should == :c
|
157
|
+
end
|
158
|
+
|
159
|
+
context "with detached node d" do
|
160
|
+
before(:each) do
|
161
|
+
subject.register(:d, :using => :a) do |c|
|
162
|
+
@component_hashes[:d_post] = c
|
163
|
+
{ :item => :d }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should not invoke constructor block for d" do
|
168
|
+
subject.resolve(:for => :a)
|
169
|
+
|
170
|
+
@component_hashes.should_not have_key(:d_post)
|
171
|
+
@component_hashes[:b].c[:item].should == :c
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should not keep d in component hash passed to c's post inject block" do
|
175
|
+
subject.resolve(:for => :a)
|
176
|
+
|
177
|
+
chash = @component_hashes[:c_post]
|
178
|
+
chash.should have(3).items
|
179
|
+
lambda { chash.d }.should raise_error(described_class::BadComponent)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "stubbing" do
|
187
|
+
before(:each) do
|
188
|
+
subject.register(:a)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should pass component hash to constructor blocks containing the right components" do
|
192
|
+
resolution = subject.resolve(:for => :a)
|
193
|
+
resolution[:a].class.should == Stub
|
194
|
+
lambda { resolution[:a].some_method }.should raise_error(described_class::StubUsedError)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "render_dependencies" do
|
199
|
+
context "with dependencies a -> b -> c, a -> d" do
|
200
|
+
before(:each) do
|
201
|
+
subject.register(:a, :using => %w{b d})
|
202
|
+
subject.register(:b, :using => :c)
|
203
|
+
subject.register(:c)
|
204
|
+
subject.register(:d)
|
205
|
+
end
|
206
|
+
|
207
|
+
let(:dependencies) { subject.render_dependencies(:format => :hash) }
|
208
|
+
|
209
|
+
it "gives me a nice hash with dependencies" do
|
210
|
+
keys = dependencies.keys
|
211
|
+
keys.sort_by(&:to_s).should == [:a, :b, :c, :d]
|
212
|
+
|
213
|
+
dependencies.each_value.sort_by(&:to_s)
|
214
|
+
|
215
|
+
dependencies[:a].should == [:b, :d]
|
216
|
+
dependencies[:b].should == [:c]
|
217
|
+
dependencies[:c].should be_empty
|
218
|
+
dependencies[:d].should be_empty
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "marking for resolution" do
|
224
|
+
describe ".resolve_for" do
|
225
|
+
before(:each) do
|
226
|
+
subject.register(:a)
|
227
|
+
subject.register(:b)
|
228
|
+
subject.register(:c)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "is chainable" do
|
232
|
+
subject.resolve_for.should be_equal(subject)
|
233
|
+
end
|
234
|
+
|
235
|
+
it "has the effekt that .resolve will deliver the argument(s). pt. 1" do
|
236
|
+
subject.resolve_for :a
|
237
|
+
subject.resolve.keys.should == [:a]
|
238
|
+
end
|
239
|
+
|
240
|
+
it "has the effekt that .resolve will deliver the argument(s). pt. 2" do
|
241
|
+
subject.resolve_for :a
|
242
|
+
subject.resolve(:for => :b).keys.sort_by(&:to_s).should == [:a, :b]
|
243
|
+
end
|
244
|
+
|
245
|
+
it "has the effekt that .resolve will deliver the argument(s). pt. 3" do
|
246
|
+
subject.resolve_for *%w{a b}
|
247
|
+
subject.resolve.keys.sort_by(&:to_s).should == [:a, :b]
|
248
|
+
end
|
249
|
+
|
250
|
+
it "has the effekt that .resolve will deliver the argument(s). pt. 4" do
|
251
|
+
subject.resolve_for %w{a b}
|
252
|
+
subject.resolve.keys.sort_by(&:to_s).should == [:a, :b]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe ".register(:resolve => true)" do
|
257
|
+
before(:each) do
|
258
|
+
subject.register(:a)
|
259
|
+
subject.register(:b, :resolve => true)
|
260
|
+
end
|
261
|
+
|
262
|
+
it "has the effekt that .resolve will deliver the component" do
|
263
|
+
subject.resolve.keys.should == [:b]
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "tame_the_beast/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "tame_the_beast"
|
7
|
+
s.version = TameTheBeast::VERSION
|
8
|
+
s.authors = ["Thomas Stratmann"]
|
9
|
+
s.email = ["thomas.stratmann@9elements.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Systematic dependency injection: keep your singletons manageable}
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency "ruby-debug"
|
20
|
+
s.add_development_dependency "rspec", "2.8.0"
|
21
|
+
s.add_development_dependency "growl"
|
22
|
+
s.add_development_dependency "guard-rspec"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tame_the_beast
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Thomas Stratmann
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-04-23 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: ruby-debug
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - "="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 47
|
43
|
+
segments:
|
44
|
+
- 2
|
45
|
+
- 8
|
46
|
+
- 0
|
47
|
+
version: 2.8.0
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: growl
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
version_requirements: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: guard-rspec
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
type: :development
|
77
|
+
version_requirements: *id004
|
78
|
+
description: "Systematic dependency injection: keep your singletons manageable"
|
79
|
+
email:
|
80
|
+
- thomas.stratmann@9elements.com
|
81
|
+
executables: []
|
82
|
+
|
83
|
+
extensions: []
|
84
|
+
|
85
|
+
extra_rdoc_files: []
|
86
|
+
|
87
|
+
files:
|
88
|
+
- Guardfile
|
89
|
+
- LICENSE.md
|
90
|
+
- Readme.md
|
91
|
+
- lib/tame_the_beast.rb
|
92
|
+
- lib/tame_the_beast/armory.rb
|
93
|
+
- lib/tame_the_beast/container.rb
|
94
|
+
- lib/tame_the_beast/reg_entry.rb
|
95
|
+
- lib/tame_the_beast/stub.rb
|
96
|
+
- lib/tame_the_beast/version.rb
|
97
|
+
- spec/acceptance/tame_the_beast_spec.rb
|
98
|
+
- tame_the_beast.gemspec
|
99
|
+
homepage: ""
|
100
|
+
licenses: []
|
101
|
+
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
hash: 3
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
requirements: []
|
126
|
+
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.8.17
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: "Systematic dependency injection: keep your singletons manageable"
|
132
|
+
test_files:
|
133
|
+
- spec/acceptance/tame_the_beast_spec.rb
|