jerry 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/jerry.rb CHANGED
@@ -1,53 +1,41 @@
1
1
  require 'jerry/version'
2
+ require 'jerry/errors'
2
3
  require 'jerry/config'
3
4
 
4
5
  # Inversion of Control container.
5
6
  #
6
- # This class is in charge of bootstrapping your application. This is done by defining {Jerry::Config configs}.
7
+ # This class is in charge of bootstrapping your application. This is done by
8
+ # defining {Jerry::Config configs}.
7
9
  #
8
- # @example
9
- # class MyConfig < Jerry::Config
10
- # component(:app) { MyApp.new }
10
+ # @example Basic usage
11
+ # class FooConfig < Jerry::Config
12
+ # # ...
11
13
  # end
12
- # jerry = Jerry.new MyConfig.new
13
- # jerry.rig :app #=> #<MyApp:...>
14
+ #
15
+ # class BarConfig < Jerry::Config
16
+ # # ...
17
+ # end
18
+ #
19
+ # jerry = Jerry.new FooConfig.new, BarConfig.new
20
+ # jerry[SomeClass] #=> #<Someclass:...>
14
21
  class Jerry
15
- # Indicated that an error occurred while rigging a component
16
- class RigError < StandardError; end
17
-
18
- # @param [Jerry::Config] configs Configs used to rig components. Multiple config can be given. If two configs
19
- # define the same component, the later config will have priority.
22
+ # @param configs [Array<Jerry::Config>] configurations describing how to wire
23
+ # your application
20
24
  def initialize(*configs)
21
- @index = {}
22
-
23
- configs.each { |config| self << config }
24
- end
25
-
26
- # Load a config
27
- #
28
- # @param [Jerry::Config] config Config to be loaded. If the loaded config defines a component already defined
29
- # by another config, the component from the new config will take priority.
30
- def <<(config)
31
- components = config.components
32
- components.each { |component| @index[component] = config }
33
- config.jerry = self
34
- end
35
-
36
- # Rigs a component
37
- #
38
- # @param [Symbol] component Component to rig.
39
- # @return The component requested
40
- # @raise [Jerry::RigError] when the requested component does not exist
41
- def rig(component)
42
- raise RigError, "could not find component #{component}" unless knows? component
25
+ configs.each { |conf| conf.jerry = self }
43
26
 
44
- @index[component].public_send component
27
+ @configs = configs
45
28
  end
46
29
 
47
- # Checks if a component exists
48
- #
49
- # @param [Symbol] component component to check
50
- def knows?(component)
51
- @index.has_key? component
30
+ # @param key what to provide
31
+ # @return an insance of the sepcified key provided by one of the configs
32
+ # @raise [Jerry::InstantiationError] if can't instanciate key
33
+ def [](key)
34
+ config = @configs.find { |conf| conf.knows? key }
35
+ if config
36
+ config[key]
37
+ else
38
+ fail Jerry::InstantiationError, "Can't find #{key} in any config"
39
+ end
52
40
  end
53
41
  end
@@ -0,0 +1,109 @@
1
+ require 'jerry/class_provider'
2
+
3
+ describe Jerry::ClassProvider do
4
+ let(:jerry) { double 'jerry' }
5
+ let(:config) { double 'config' }
6
+ let(:klass) { dummy_class }
7
+
8
+ def dummy_class
9
+ Class.new do
10
+ define_method(:initialize) { |*| }
11
+ end
12
+ end
13
+
14
+ describe '#call' do
15
+ it 'should return an instance of the class' do
16
+ provider = Jerry::ClassProvider.new klass, []
17
+ instance = double 'instance'
18
+ allow(klass).to receive(:new).and_return(instance)
19
+
20
+ expect(provider.call jerry, config).to eq instance
21
+ end
22
+
23
+ it 'should pass constructor arguments in the right order' do
24
+ provider = Jerry::ClassProvider.new klass, [
25
+ proc { 'fi' }, proc { 'fo' }, proc { 'fum' }]
26
+
27
+ expect(klass).to receive(:new).with('fi', 'fo', 'fum')
28
+
29
+ provider.call jerry, config
30
+ end
31
+ end
32
+
33
+ describe 'with a class argument' do
34
+ it 'should get the instance from jerry' do
35
+ arg_klass = dummy_class
36
+ provider = Jerry::ClassProvider.new klass, [arg_klass]
37
+
38
+ expect(jerry).to receive(:[]).with(arg_klass)
39
+
40
+ provider.call jerry, config
41
+ end
42
+
43
+ it 'should pass the instance from jerry to the constructor' do
44
+ provider = Jerry::ClassProvider.new klass, [dummy_class]
45
+ instance = double 'instance'
46
+ allow(jerry).to receive(:[]).and_return(instance)
47
+
48
+ expect(klass).to receive(:new).with(instance)
49
+
50
+ provider.call jerry, config
51
+ end
52
+ end
53
+
54
+ describe 'with a symbol argument' do
55
+ it 'should get the instance from jerry' do
56
+ provider = Jerry::ClassProvider.new klass, [:foobar]
57
+
58
+ expect(jerry).to receive(:[]).with(:foobar)
59
+
60
+ provider.call jerry, config
61
+ end
62
+
63
+ it 'should pass the instance from jerry to the constructor' do
64
+ provider = Jerry::ClassProvider.new klass, [:foobar]
65
+ instance = double 'instance'
66
+ allow(jerry).to receive(:[]).with(:foobar).and_return(instance)
67
+
68
+ expect(klass).to receive(:new).with(instance)
69
+
70
+ provider.call jerry, config
71
+ end
72
+ end
73
+
74
+ describe 'with callable argument' do
75
+ it 'should call the callable' do
76
+ expect do |callable|
77
+ provider = Jerry::ClassProvider.new klass, [callable.to_proc]
78
+ provider.call jerry, config
79
+ end.to yield_control
80
+ end
81
+
82
+ it 'should pass the result of the callable to the constructor' do
83
+ instance = double 'instance'
84
+ provider = Jerry::ClassProvider.new klass, [proc { instance }]
85
+
86
+ expect(klass).to receive(:new).with(instance)
87
+
88
+ provider.call jerry, config
89
+ end
90
+
91
+ it 'should pass the jerry and config instance to the proc' do
92
+ expect do |callable|
93
+ provider = Jerry::ClassProvider.new klass, [callable.to_proc]
94
+ provider.call jerry, config
95
+ end.to yield_with_args(jerry, config)
96
+ end
97
+
98
+ it 'should call the proc in the context of the config' do
99
+ config = double 'config'
100
+ config.instance_eval { @stuff = 'something private' }
101
+ callable = proc { @stuff }
102
+ provider = Jerry::ClassProvider.new klass, [callable]
103
+
104
+ expect(klass).to receive(:new).with('something private')
105
+
106
+ provider.call jerry, config
107
+ end
108
+ end
109
+ end
data/spec/config_spec.rb CHANGED
@@ -1,172 +1,192 @@
1
- require 'rspec'
2
1
  require 'jerry/config'
2
+ require 'fixtures/house'
3
3
 
4
4
  describe Jerry::Config do
5
- let(:klass) {Class.new(Jerry::Config)}
5
+ let(:config_klass) { Class.new Jerry::Config }
6
+ let(:config) { config_klass.new }
7
+ let(:jerry) { double 'jerry' }
6
8
 
7
- describe 'subclass' do
8
- it 'should respond to all inherited class methods' do
9
- expect(klass).to respond_to :components
10
- expect(klass).to respond_to :component
11
- end
9
+ before do
10
+ config.jerry = jerry
12
11
  end
13
12
 
14
- describe '#components' do
15
- it 'should call the class contents method' do
16
- instance = klass.new
17
-
18
- expect(klass).to receive(:components).and_return([:something])
13
+ describe '::bind' do
14
+ it 'should create a class provider' do
15
+ klass = double 'class'
16
+ args = [Class.new, :foobar, -> { 'stuff' }]
17
+ expect(Jerry::ClassProvider).to receive(:new).with(klass, args)
19
18
 
20
- expect(instance.components).to eq([:something])
19
+ Class.new Jerry::Config do
20
+ bind klass, args
21
+ end
21
22
  end
22
- end
23
23
 
24
- describe '#rig' do
25
- it 'should call rig on the previously set jerry' do
26
- jerry = double('jerry')
27
- config = klass.new
28
- config.jerry = jerry
24
+ it 'should default ctor_args to empty array' do
25
+ expect(Jerry::ClassProvider).to receive(:new).with(anything, [])
26
+
27
+ Class.new Jerry::Config do
28
+ bind Class.new
29
+ end
30
+ end
29
31
 
30
- expect(jerry).to receive(:rig).with(:target)
32
+ it 'should use the class to identify the provider' do
33
+ klass = Class.new
34
+ config = Class.new Jerry::Config do
35
+ bind klass
36
+ end
31
37
 
32
- config.instance_eval { rig :target }
38
+ expect(config.providers).to have_key klass
33
39
  end
34
40
 
35
- it 'should return the result from the jerry rig method' do
36
- jerry = double('jerry')
37
- allow(jerry).to receive(:rig).with(anything).and_return 42
38
- config = klass.new
39
- config.jerry = jerry
41
+ it 'should return the class' do
42
+ klass = Class.new
43
+ thing = nil
40
44
 
41
- expect(config.instance_eval { rig :something }).to eq(42)
45
+ Class.new Jerry::Config do
46
+ thing = bind klass
47
+ end
48
+
49
+ expect(thing).to eq klass
42
50
  end
43
51
  end
44
52
 
45
- describe '#knows?' do
46
- it 'should call the knows? method on the previously set jerry' do
47
- jerry = double('jerry')
48
- config = klass.new
49
- config.jerry = jerry
53
+ describe '::named_bind' do
54
+ it 'should create a class provider' do
55
+ klass = double 'class'
56
+ args = [Class.new, :foobar, proc { 'stuff' }]
57
+ expect(Jerry::ClassProvider).to receive(:new).with(klass, args)
50
58
 
51
- expect(jerry).to receive(:knows?).with(:target)
59
+ Class.new Jerry::Config do
60
+ named_bind :stuff, klass, args
61
+ end
62
+ end
52
63
 
53
- config.instance_eval { knows? :target }
64
+ it 'should default ctor args to emtpy array' do
65
+ expect(Jerry::ClassProvider).to receive(:new).with(anything, [])
66
+
67
+ Class.new Jerry::Config do
68
+ named_bind :thing, Class.new
69
+ end
54
70
  end
55
71
 
56
- it 'should return the results from the jerry knows? method' do
57
- jerry = double('jerry')
58
- allow(jerry).to receive(:knows?).with(anything).and_return true
59
- config = klass.new
60
- config.jerry = jerry
72
+ it 'should use the given key to store the provider' do
73
+ config = Class.new Jerry::Config do
74
+ named_bind :the_name, Class.new
75
+ end
61
76
 
62
- expect(config.instance_eval { knows? :something }).to be_truthy
77
+ expect(config.providers).to have_key :the_name
63
78
  end
64
- end
65
79
 
66
- describe 'class methods' do
67
- describe '#component' do
68
- it 'should create a method with the given name' do
69
- klass.component(:my_component) {}
80
+ it 'should return the key' do
81
+ thing = nil
70
82
 
71
- expect(klass.new).to respond_to(:my_component)
83
+ Class.new Jerry::Config do
84
+ thing = named_bind :foobar, Class.new
72
85
  end
73
86
 
74
- it 'should add the name to the list of components' do
75
- klass.component(:target) {}
87
+ expect(thing).to eq :foobar
88
+ end
89
+ end
76
90
 
77
- expect(klass.components).to include(:target)
91
+ describe '::singleton' do
92
+ it 'should always use the same instance' do
93
+ config = Class.new Jerry::Config do
94
+ singleton named_bind :thing, Class.new
78
95
  end
79
96
 
80
- it 'should raise an error when scope is unknown' do
81
- expect{klass.component(:target, scope: :not_a_thing) {}}.to raise_error(Jerry::ComponentError)
97
+ jerry = Jerry.new config.new
98
+ thing1 = jerry[:thing]
99
+ thing2 = jerry[:thing]
100
+
101
+ expect(thing1).to equal thing2
102
+ end
103
+
104
+ it 'should only instantiate the class once' do
105
+ klass = double 'class', new: double('instance')
106
+ config = Class.new Jerry::Config do
107
+ singleton bind klass
82
108
  end
83
109
 
84
- it 'should raise an error when block is missing' do
85
- expect{klass.component :target}.to raise_error(Jerry::ComponentError)
110
+ expect(klass).to receive(:new).once
111
+
112
+ jerry = Jerry.new config.new
113
+ 2.times { jerry[klass] }
114
+ end
115
+ end
116
+
117
+ describe '#[]' do
118
+ it 'should get the provider instance from ::bind' do
119
+ instance = double 'instance'
120
+ klass = double 'class', new: instance
121
+
122
+ config_klass.class_eval do
123
+ bind klass
86
124
  end
87
125
 
88
- describe 'defined method' do
89
- it 'should have the right self set' do
90
- klass.component(:_self) { self }
91
- instance = klass.new
92
-
93
- expect(instance._self).not_to be_a Class
94
- expect(instance._self).to be_a klass
95
- expect(instance._self).to eq(instance)
96
- end
97
-
98
- context 'with scope set to :single' do
99
- it 'should only call the block once' do
100
- call_count = 0
101
- klass.component(:target, scope: :single) {call_count += 1}
102
- instance = klass.new
103
-
104
- 3.times {instance.target}
105
-
106
- expect(call_count).to eq(1)
107
- end
108
-
109
- it 'should keep returning the first created component' do
110
- call_count = 0
111
- klass.component(:target, scope: :single) {call_count += 1}
112
- instance = klass.new
113
-
114
- expect(instance.target).to eq(1)
115
- expect(instance.target).to eq(1)
116
- expect(instance.target).to eq(1)
117
- end
118
- end
119
-
120
- context 'with scope set to :instance' do
121
- it 'should call the block every time' do
122
- call_count = 0
123
- klass.component(:target, scope: :instance) {call_count += 1}
124
- instance = klass.new
125
-
126
- 3.times {instance.target}
127
-
128
- expect(call_count).to eq(3)
129
- end
130
-
131
- it 'should return a new instance every time' do
132
- call_count = 0
133
- klass.component(:target, scope: :instance) {call_count += 1}
134
- instance = klass.new
135
-
136
- expect(instance.target).to eq(1)
137
- expect(instance.target).to eq(2)
138
- expect(instance.target).to eq(3)
139
- end
140
- end
141
-
142
- context 'with no scope' do
143
- it 'should only call the block once' do
144
- call_count = 0
145
- klass.component(:target) {call_count += 1}
146
- instance = klass.new
147
-
148
- 3.times {instance.target}
149
-
150
- expect(call_count).to eq(1)
151
- end
152
-
153
- it 'should keep returning the first created component' do
154
- call_count = 0
155
- klass.component(:target) {call_count += 1}
156
- instance = klass.new
157
-
158
- expect(instance.target).to eq(1)
159
- expect(instance.target).to eq(1)
160
- expect(instance.target).to eq(1)
161
- end
162
- end
126
+ expect(config[klass]).to eq instance
127
+ end
128
+
129
+ it 'should pass jerry to the provider' do
130
+ provider = double 'provider'
131
+ config_klass.send(:providers)[:foobar] = provider
132
+
133
+ expect(provider).to receive(:call).with(jerry, anything)
134
+
135
+ config[:foobar]
136
+ end
137
+
138
+ it 'should pass config instance to the provider' do
139
+ provider = double 'provider'
140
+ config_klass.providers[:something] = provider
141
+
142
+ expect(provider).to receive(:call).with(anything, config)
143
+
144
+ config[:something]
145
+ end
146
+
147
+ it 'should fail when provider is missing' do
148
+ expect { config[:not_there] }.to raise_error(Jerry::InstantiationError)
149
+ end
150
+
151
+ it 'should wrap errors from the provider' do
152
+ provider = double 'provider'
153
+ allow(provider).to receive(:call)
154
+ .and_raise(RuntimeError, 'something blew up')
155
+ config_klass.providers[:failing] = provider
156
+
157
+ expect { config[:failing] }
158
+ .to raise_error(Jerry::InstantiationError) do |e|
159
+ expect(e.cause.message).to eq 'something blew up'
163
160
  end
164
161
  end
165
162
 
166
- describe '#components' do
167
- it 'should default to an empty array' do
168
- expect(klass.components).to eq([])
163
+ it 'should set self to config instance for all procs in spec' do
164
+ klass = Class.new do
165
+ attr_reader :thing
166
+ define_method(:initialize) { |thing| @thing = thing }
167
+ end
168
+ config_klass = Class.new(Jerry::Config) do
169
+ define_method(:initialize) { @foobar = 'something private' }
170
+ bind klass, [proc { @foobar }]
169
171
  end
172
+ config = config_klass.new
173
+ config.jerry = jerry
174
+
175
+ instance = config[klass]
176
+ expect(instance.thing).to eq 'something private'
177
+ end
178
+ end
179
+
180
+ describe '#knows?' do
181
+ it 'should be true if key is in providers' do
182
+ key = double 'some key'
183
+ config_klass.providers[key] = double 'some provider'
184
+
185
+ expect(config.knows? key).to be true
186
+ end
187
+
188
+ it 'should be false if key is not known' do
189
+ expect(config.knows? double('some unknown key')).to be false
170
190
  end
171
191
  end
172
- end
192
+ end
@@ -0,0 +1,23 @@
1
+ describe Jerry::Error do
2
+ def raise_error_with_cause
3
+ fail 'Something went wrong'
4
+ rescue RuntimeError
5
+ raise Jerry::Error, 'wrapping error'
6
+ end
7
+
8
+ it 'should record the causing exception when there is one' do
9
+ expect { raise_error_with_cause }.to raise_error(Jerry::Error) do |ex|
10
+ expect(ex.cause.message).to eq 'Something went wrong'
11
+ end
12
+ end
13
+
14
+ it 'should not record the causing exception when there is none' do
15
+ expect { fail Jerry::Error }.to raise_error(Jerry::Error) do |ex|
16
+ expect(ex.cause).to be_nil
17
+ end
18
+ end
19
+
20
+ it 'should have a message' do
21
+ expect { fail Jerry::Error, ':(' }.to raise_error(Jerry::Error, ':(')
22
+ end
23
+ end
@@ -0,0 +1,120 @@
1
+ require 'jerry'
2
+ require 'fixtures/house'
3
+ require 'fixtures/db_app'
4
+ require 'fixtures/shopping_cart'
5
+ require 'fixtures/shopping_cart_config'
6
+ require 'fixtures/multi_db_app'
7
+
8
+ describe Jerry do
9
+ it 'should wire dependencies' do
10
+ HousingModule = Class.new(Jerry::Config) do
11
+ bind House, [Door, Window]
12
+ bind Window, []
13
+ bind Door, []
14
+ end
15
+
16
+ jerry = Jerry.new HousingModule.new
17
+
18
+ house = jerry[House]
19
+
20
+ expect(house).to be_a House
21
+ expect(house.window).to be_a Window
22
+ expect(house.door).to be_a Door
23
+ end
24
+
25
+ it 'should allow the use of procs to wire settings' do
26
+ AppConfig = Class.new Jerry::Config do
27
+ def initialize(database_uri)
28
+ @database_uri = database_uri
29
+ end
30
+
31
+ bind DbApplication, [Database]
32
+ bind Database, [proc { @database_uri }]
33
+ end
34
+
35
+ jerry = Jerry.new AppConfig.new('mongodb://localhost:27017')
36
+ app = jerry[DbApplication]
37
+
38
+ expect(app).to be_a DbApplication
39
+ expect(app.db).to be_a Database
40
+ expect(app.db.uri).to eq 'mongodb://localhost:27017'
41
+ end
42
+
43
+ it 'should support multiple configs' do
44
+ jerry = Jerry.new(
45
+ ShoppingCart::DatabaseConfig.new,
46
+ ShoppingCart::ApplicationConfig.new,
47
+ ShoppingCart::UserConfig.new,
48
+ ShoppingCart::ProductConfig.new,
49
+ ShoppingCart::ShoppingCartConfig.new
50
+ )
51
+
52
+ app = jerry[ShoppingCart::Application]
53
+
54
+ expect(app).to be_a ShoppingCart::Application
55
+
56
+ expect(app.user_controller).to be_a ShoppingCart::UserController
57
+ expect(app.product_controller).to be_a ShoppingCart::ProductController
58
+ expect(app.shopping_cart_controller).to \
59
+ be_a ShoppingCart::ShoppingCartController
60
+
61
+ expect(app.user_controller.user_service).to be_a ShoppingCart::UserService
62
+ expect(app.product_controller.product_service).to \
63
+ be_a ShoppingCart::ProductService
64
+ expect(app.shopping_cart_controller.shopping_cart_service).to \
65
+ be_a ShoppingCart::ShoppingCartService
66
+
67
+ expect(app.user_controller.user_service.db).to be_a ShoppingCart::Database
68
+ expect(app.product_controller.product_service.db).to \
69
+ be_a ShoppingCart::Database
70
+ expect(app.shopping_cart_controller.shopping_cart_service.db).to \
71
+ be_a ShoppingCart::Database
72
+
73
+ shopping_cart_service = app.shopping_cart_controller.shopping_cart_service
74
+ expect(shopping_cart_service.user_service).to be_a ShoppingCart::UserService
75
+ expect(shopping_cart_service.product_service).to \
76
+ be_a ShoppingCart::ProductService
77
+ end
78
+
79
+ it 'should support wiring one class in multiple ways through naming' do
80
+ jerry = Jerry.new MultiDbApp::Config.new('db://foo', 'db://bar')
81
+
82
+ app = jerry[MultiDbApp::Application]
83
+
84
+ expect(app.foo_db.uri).to eq 'db://foo'
85
+ expect(app.bar_db.uri).to eq 'db://bar'
86
+ end
87
+
88
+ it 'should wire the same class multiple times with multiple names' do
89
+ klass = Class.new do
90
+ attr_reader :str
91
+ define_method(:initialize) { |str| @str = str }
92
+ end
93
+ config = Class.new Jerry::Config do
94
+ named_bind :foo, klass, [proc { 'foo' }]
95
+ named_bind :bar, klass, [proc { 'bar' }]
96
+ end
97
+ jerry = Jerry.new config.new
98
+
99
+ foo = jerry[:foo]
100
+ bar = jerry[:bar]
101
+
102
+ expect(foo).to be_a klass
103
+ expect(foo.str).to eq 'foo'
104
+ expect(bar).to be_a klass
105
+ expect(bar.str).to eq 'bar'
106
+ end
107
+
108
+ it 'should always wire the same instance when using singleton' do
109
+ klass = Class.new
110
+ config = Class.new Jerry::Config do
111
+ singleton bind klass
112
+ end
113
+ jerry = Jerry.new config.new
114
+
115
+ alfa = jerry[klass]
116
+ bravo = jerry[klass]
117
+
118
+ expect(alfa).to equal bravo
119
+ end
120
+ end
@@ -0,0 +1,17 @@
1
+ # A database connector that connects to a given uri
2
+ class Database
3
+ attr_reader :uri
4
+
5
+ def initialize(uri)
6
+ @uri = uri
7
+ end
8
+ end
9
+
10
+ # An application that uses a database
11
+ class DbApplication
12
+ attr_reader :db
13
+
14
+ def initialize(db)
15
+ @db = db
16
+ end
17
+ end