encase 0.1.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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +291 -0
- data/Rakefile +12 -0
- data/encase.gemspec +24 -0
- data/lib/encase.rb +8 -0
- data/lib/encase/container.rb +80 -0
- data/lib/encase/container_item.rb +55 -0
- data/lib/encase/container_item_factory.rb +21 -0
- data/lib/encase/encaseable.rb +22 -0
- data/lib/encase/factory_item.rb +9 -0
- data/lib/encase/object_item.rb +15 -0
- data/lib/encase/singleton_item.rb +12 -0
- data/lib/encase/version.rb +3 -0
- data/portkey.json +10 -0
- data/spec/lib/encase/container_item_factory_spec.rb +49 -0
- data/spec/lib/encase/container_item_spec.rb +89 -0
- data/spec/lib/encase/container_spec.rb +202 -0
- data/spec/lib/encase/encaseable_spec.rb +37 -0
- data/spec/lib/encase/factory_item_spec.rb +39 -0
- data/spec/lib/encase/object_item_spec.rb +30 -0
- data/spec/lib/encase/singleton_item_spec.rb +30 -0
- data/spec/spec_helper.rb +8 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MjY0NjkwNmNhNzFmNjhiY2IyZmVhYjU2YjE4OGU2MTcwMTkxZmY0ZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MjgzYTUxZjNmMTgxNTA1NTVlN2Y4MDA5MWExYmE2NjFkYmNlNGUzNw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MDllY2FhYmFmZTQ2YTUxNWU4NDVlMTZlMjNmMmM4M2QwMTkzNzYzMjQ4Njk3
|
10
|
+
ZWRkZDg4OWU5YzQxNWQ4YTQ0NjliZGY1MWQzNzZhYThmZjAzNzVmZmQxMjM5
|
11
|
+
MzE1ZTY5ZGE3MjFmODRkM2Y3NjJiMzk3MDBjOTg3Y2YxODNmNmY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YjIzNWQ5MmQ2YjM2Y2Y1N2M1M2JmOGNkNmMwNzQ0N2NmMmQ4MDhmZWFiNWZh
|
14
|
+
ZmIzOTdiYzkzMjg0ZDRhYjU3MWY1MjgyN2U3MDk4MWE4NjA5ZmUxMzliZTY4
|
15
|
+
ZGVkMjE4ZGExMGIwOTZhOGViMTk3ZTY5NmI2YmZkYmNjZjMwZjU=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Darshan Sawardekar
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
## Encase [![Build Status][1]][2] [![Code Climate][3]][4] [![Dependency Status][5]][6]
|
2
|
+
|
3
|
+
### Lightweight IOC Container for ruby.
|
4
|
+
|
5
|
+
Encase is a library for using dependency injection within ruby
|
6
|
+
applications. It provides a lightweight IOC Container that manages
|
7
|
+
dependencies between your classes. It was written to assist with wiring
|
8
|
+
of domain objects outside Rails applications to enable faster test
|
9
|
+
suites.
|
10
|
+
|
11
|
+
# Features
|
12
|
+
|
13
|
+
* Stores objects, factories, and singletons
|
14
|
+
* Declarative syntax for specifying dependencies
|
15
|
+
* Simple DSL for configuring the container
|
16
|
+
* Support for nested containers
|
17
|
+
* Support for lazy initialization
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Consider a `Worker` class that has a dependency on a `Logger`. We would
|
22
|
+
like this dependency to be available to the worker when it is created.
|
23
|
+
|
24
|
+
First we create a container object and declare the dependencies.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'encase/container'
|
28
|
+
|
29
|
+
container = Container.new
|
30
|
+
container.configure do
|
31
|
+
object :logger, Logger.new
|
32
|
+
factory :worker, Worker
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
Then we declare the `logger` dependency in the Worker class by including
|
37
|
+
the `Encase` module into it and using the `needs` declaration.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require 'encase'
|
41
|
+
|
42
|
+
class Worker
|
43
|
+
include Encase
|
44
|
+
|
45
|
+
needs :logger
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
That's it! Now we can create a new `Worker` by looking up the `:worker`
|
50
|
+
key on the Container.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
my_worker = container.lookup('worker')
|
54
|
+
my_worker.logger.is_a?(Logger) # true
|
55
|
+
```
|
56
|
+
|
57
|
+
## Container Configuration
|
58
|
+
|
59
|
+
Containers are configured using a mini DSL in a `configure` method.
|
60
|
+
Container items are stored with ruby `symbols` and are used to later lookup the object.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
container.configure do
|
64
|
+
# declarations go here
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
The declarations supported are listed below.
|
69
|
+
|
70
|
+
### Object
|
71
|
+
|
72
|
+
Objects are pre existing entities that have already been created in your
|
73
|
+
system. They are stored and returned as is without any modification. To
|
74
|
+
store objects use the `object` declaration.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
container.configure do
|
78
|
+
object :key, value
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
### Factory
|
83
|
+
|
84
|
+
A Factory can be stored with the container to create instances of a
|
85
|
+
class with it's dependencies auto-injected. On every lookup a new
|
86
|
+
instance of that class will be returned.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
container.configure do
|
90
|
+
factory :key, TheClass
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
### Singleton
|
95
|
+
|
96
|
+
A Singleton is similar to a Factory. However it caches the instance
|
97
|
+
created on the first lookup and returns that instance on every
|
98
|
+
subsequent lookups.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
container.configure do
|
102
|
+
singleton :key, TheSingletonClass
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
## Declaring Dependencies
|
107
|
+
|
108
|
+
To specify dependencies of a class, you use the `needs` declaration. It
|
109
|
+
takes an array of symbols corresponding to the keys of the dependencies
|
110
|
+
stored in the container. Multiple `needs` declarations are also supported.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
require 'encase'
|
114
|
+
|
115
|
+
class Worker
|
116
|
+
include Encase
|
117
|
+
|
118
|
+
needs :a, :b, :c
|
119
|
+
needs :one
|
120
|
+
needs :two
|
121
|
+
needs :three
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
## Lazy Initialization
|
126
|
+
|
127
|
+
Encase allows storage of dependencies lazily. This can be useful if the
|
128
|
+
dependencies aren't ready at the time of container configuration. But
|
129
|
+
will ready before lookup.
|
130
|
+
|
131
|
+
Lazy initialization is done by passing a `block` to the DSL declaration
|
132
|
+
instead of a value. Here `:key`'s block will be evaluated before the
|
133
|
+
first lookup.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
container.configure do
|
137
|
+
object :key do
|
138
|
+
value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
The block can optionally take an argument equal to the container object
|
144
|
+
itself. You can use this to conditionally resolve the value based on
|
145
|
+
other objects in the container or elsewhere.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
container.configure do
|
149
|
+
object :key do |c|
|
150
|
+
value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
## Nested Containers
|
156
|
+
|
157
|
+
Containers can also be nested within other containers. This allows grouping
|
158
|
+
dependencies within different contexts of your application. When looking
|
159
|
+
up keys, parent containers are queried when a key is not found in a
|
160
|
+
child container.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
parent_container = Container.new
|
164
|
+
parent_container.configure do
|
165
|
+
object :logger, Logger.new
|
166
|
+
factory :worker, Worker
|
167
|
+
end
|
168
|
+
|
169
|
+
child_container = parent_container.child
|
170
|
+
child_container.configure do
|
171
|
+
factory :worker, CustomWorker
|
172
|
+
end
|
173
|
+
|
174
|
+
child_container.lookup(:logger) # from parent_container
|
175
|
+
child_container.lookup(:worker) # CustomWorker
|
176
|
+
```
|
177
|
+
|
178
|
+
Here the `child_container` will use `CustomWorker` for resolving
|
179
|
+
`:worker`. While the `:logger` will be looked up from the
|
180
|
+
`parent_container`
|
181
|
+
|
182
|
+
## Accessors
|
183
|
+
|
184
|
+
Encase creates `accessor` methods corresponding to each declared `need`
|
185
|
+
in the class. A `container` accessor is also injected into the class for
|
186
|
+
looking up other dependencies at runtime.
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
class Worker
|
190
|
+
include Encase
|
191
|
+
|
192
|
+
needs :one, :two, :three
|
193
|
+
end
|
194
|
+
|
195
|
+
worker = container.lookup('worker')
|
196
|
+
worker.one
|
197
|
+
worker.two
|
198
|
+
worker.three
|
199
|
+
worker.container
|
200
|
+
```
|
201
|
+
|
202
|
+
## Lifecycle
|
203
|
+
|
204
|
+
An `on_inject` event hook is provided to container items after their
|
205
|
+
dependencies are injected. Any post injection initialization can be
|
206
|
+
carried out here.
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
class Worker
|
210
|
+
include Encase
|
211
|
+
|
212
|
+
needs :logger
|
213
|
+
|
214
|
+
def on_inject
|
215
|
+
logger.log('Worker is ready')
|
216
|
+
end
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
## Testing
|
221
|
+
|
222
|
+
Encase significantly simplifies testability of objects. In the earlier
|
223
|
+
example in order to test that the logger is indeed called by the worker
|
224
|
+
we can register the worker as a `mock`. Then verify that this mock was called.
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
describe Worker do
|
228
|
+
let(:container) {
|
229
|
+
container = Container.new
|
230
|
+
container.configure do
|
231
|
+
factory :worker, Worker
|
232
|
+
end
|
233
|
+
|
234
|
+
container
|
235
|
+
}
|
236
|
+
|
237
|
+
it 'logs message to the logger' do
|
238
|
+
logger = double()
|
239
|
+
logger.should_receive(:log).with(anything())
|
240
|
+
|
241
|
+
container.object logger, double(:log => true)
|
242
|
+
|
243
|
+
worker = container.lookup(:worker)
|
244
|
+
worker.start()
|
245
|
+
end
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
Using the Encase created `accessor` methods it is also possible to
|
250
|
+
manually assign the dependencies. Below `logger` is directly assigned to
|
251
|
+
the `worker` without requiring a container.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
it 'logs message to the logger' do
|
255
|
+
logger = double()
|
256
|
+
logger.should_receive(:log).with(anything())
|
257
|
+
|
258
|
+
worker = Worker.new
|
259
|
+
worker.logger = logger
|
260
|
+
worker.start()
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
264
|
+
## Installation
|
265
|
+
|
266
|
+
Add this line to your application's Gemfile:
|
267
|
+
|
268
|
+
gem 'encase'
|
269
|
+
|
270
|
+
And then execute:
|
271
|
+
|
272
|
+
$ bundle install
|
273
|
+
|
274
|
+
# System Requirements
|
275
|
+
|
276
|
+
Encase has been tested to work on these platforms.
|
277
|
+
|
278
|
+
* ruby 1.9.2
|
279
|
+
* ruby 1.9.3
|
280
|
+
* ruby 2.0.0
|
281
|
+
|
282
|
+
## Contributing
|
283
|
+
|
284
|
+
See contributing guidelines for Portkey.
|
285
|
+
|
286
|
+
[1]: https://travis-ci.org/dsawardekar/encase.png
|
287
|
+
[2]: https://travis-ci.org/dsawardekar/encase
|
288
|
+
[3]: https://codeclimate.com/github/dsawardekar/encase.png
|
289
|
+
[4]: https://codeclimate.com/github/dsawardekar/encase
|
290
|
+
[5]: https://gemnasium.com/dsawardekar/encase.png
|
291
|
+
[6]: https://gemnasium.com/dsawardekar/encase
|
data/Rakefile
ADDED
data/encase.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'encase/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "encase"
|
8
|
+
spec.version = Encase::VERSION
|
9
|
+
spec.authors = ["Darshan Sawardekar"]
|
10
|
+
spec.email = ["darshan@sawardekar.org"]
|
11
|
+
spec.summary = %q{Lightweight IOC Container for ruby.}
|
12
|
+
spec.description = %q{Encase is a library for using dependency injection within ruby applications. It provides a lightweight IOC Container that manages dependencies between your classes. It was written to assist with wiring of domain objects outside Rails applications to enable faster test suites.}
|
13
|
+
spec.homepage = "https://github.com/dsawardekar/encase"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.14.1"
|
24
|
+
end
|
data/lib/encase.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'encase/container_item_factory'
|
2
|
+
|
3
|
+
module Encase
|
4
|
+
class Container
|
5
|
+
attr_accessor :parent
|
6
|
+
|
7
|
+
def initialize(parent = nil)
|
8
|
+
@parent = parent
|
9
|
+
@items = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def contains?(key)
|
13
|
+
@items.key?(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def inject(object)
|
17
|
+
klass = object.class
|
18
|
+
if klass.respond_to?(:needs_to_inject)
|
19
|
+
needs_to_inject = klass.needs_to_inject
|
20
|
+
needs_to_inject.each do |need|
|
21
|
+
object.instance_variable_set(
|
22
|
+
"@#{need}", lookup(need)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
object.container = self if object.respond_to?(:container)
|
27
|
+
object.on_inject() if object.respond_to?(:on_inject)
|
28
|
+
|
29
|
+
true
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def register(type, key, value, block)
|
36
|
+
item = ContainerItemFactory.build(type, self)
|
37
|
+
item.store(key, value || block)
|
38
|
+
@items[key] = item
|
39
|
+
end
|
40
|
+
|
41
|
+
def unregister(key)
|
42
|
+
@items.delete(key)
|
43
|
+
end
|
44
|
+
|
45
|
+
def clear
|
46
|
+
@items.clear
|
47
|
+
end
|
48
|
+
|
49
|
+
def object(key, value = nil, &block)
|
50
|
+
register('object', key, value, block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def factory(key, value = nil, &block)
|
54
|
+
register('factory', key, value, block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def singleton(key, value = nil, &block)
|
58
|
+
register('singleton', key, value, block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def lookup(key)
|
62
|
+
if contains?(key)
|
63
|
+
item = @items[key]
|
64
|
+
item.instance
|
65
|
+
elsif !parent.nil?
|
66
|
+
parent.lookup(key)
|
67
|
+
else
|
68
|
+
raise KeyError.new("Key:#{key} not found in container.")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def configure(&block)
|
73
|
+
instance_exec(&block)
|
74
|
+
end
|
75
|
+
|
76
|
+
def child
|
77
|
+
Container.new(self)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Encase
|
2
|
+
class ContainerItem
|
3
|
+
attr_accessor :container
|
4
|
+
attr_accessor :key
|
5
|
+
attr_accessor :value
|
6
|
+
attr_accessor :reified_value
|
7
|
+
|
8
|
+
def initialize(container)
|
9
|
+
self.container = container
|
10
|
+
end
|
11
|
+
|
12
|
+
def store(key, value)
|
13
|
+
self.key = key
|
14
|
+
self.value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def inject(object)
|
18
|
+
self.container.inject(object)
|
19
|
+
end
|
20
|
+
|
21
|
+
def reify
|
22
|
+
return false if reified?
|
23
|
+
|
24
|
+
if value.is_a? Proc
|
25
|
+
if value.arity == 1
|
26
|
+
self.reified_value = value.call(container)
|
27
|
+
else
|
28
|
+
self.reified_value = value.call
|
29
|
+
end
|
30
|
+
else
|
31
|
+
self.reified_value = value
|
32
|
+
end
|
33
|
+
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def reified?
|
38
|
+
!self.reified_value.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns just the reified value by default
|
42
|
+
def fetch
|
43
|
+
reified_value
|
44
|
+
end
|
45
|
+
|
46
|
+
# public api
|
47
|
+
def instance
|
48
|
+
reify unless reified?
|
49
|
+
object = fetch
|
50
|
+
inject(object)
|
51
|
+
|
52
|
+
object
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Encase
|
2
|
+
require_relative 'object_item'
|
3
|
+
require_relative 'factory_item'
|
4
|
+
require_relative 'singleton_item'
|
5
|
+
|
6
|
+
class ContainerItemFactory
|
7
|
+
def self.build(type, container)
|
8
|
+
container_item_for(type).new(container)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.container_item_for(type)
|
12
|
+
case type
|
13
|
+
when 'object' then ObjectItem
|
14
|
+
when 'factory' then FactoryItem
|
15
|
+
when 'singleton' then SingletonItem
|
16
|
+
else
|
17
|
+
ContainerItem
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Encase
|
2
|
+
module Encaseable
|
3
|
+
def needs(*deps)
|
4
|
+
if @needs_to_inject
|
5
|
+
@needs_to_inject.concat(deps)
|
6
|
+
else
|
7
|
+
@needs_to_inject = deps
|
8
|
+
attr_accessor :container
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor *deps
|
12
|
+
end
|
13
|
+
|
14
|
+
def needs_to_inject
|
15
|
+
@needs_to_inject
|
16
|
+
end
|
17
|
+
|
18
|
+
def needs_to_inject=(value)
|
19
|
+
@needs_to_inject = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/portkey.json
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'encase/container_item_factory'
|
3
|
+
require 'encase/container_item'
|
4
|
+
require 'encase/object_item'
|
5
|
+
require 'encase/factory_item'
|
6
|
+
require 'encase/singleton_item'
|
7
|
+
|
8
|
+
module Encase
|
9
|
+
shared_examples "a container_item_factory" do
|
10
|
+
it 'finds container_item\'s constructor' do
|
11
|
+
expect(
|
12
|
+
ContainerItemFactory.container_item_for(type)
|
13
|
+
).to eq(type_constructor)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'builds container_item' do
|
17
|
+
expect(
|
18
|
+
ContainerItemFactory.build(type, container)
|
19
|
+
).to be_a(type_constructor)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe ContainerItemFactory do
|
24
|
+
let(:container) { double() }
|
25
|
+
|
26
|
+
context 'with object type' do
|
27
|
+
let(:type) { 'object' }
|
28
|
+
let(:type_constructor) { ObjectItem }
|
29
|
+
|
30
|
+
it_behaves_like 'a container_item_factory'
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with factory type' do
|
34
|
+
let(:type) { 'factory' }
|
35
|
+
let(:type_constructor) { FactoryItem }
|
36
|
+
|
37
|
+
it_behaves_like 'a container_item_factory'
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'with singleton type' do
|
41
|
+
let(:type) { 'singleton' }
|
42
|
+
let(:type_constructor) { SingletonItem }
|
43
|
+
|
44
|
+
it_behaves_like 'a container_item_factory'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'encase/container_item'
|
3
|
+
|
4
|
+
module Encase
|
5
|
+
shared_examples 'a container_item' do
|
6
|
+
it 'stores container' do
|
7
|
+
expect(container_item.container).to eq(container)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'stores item key - value pair' do
|
11
|
+
expect(container_item.key).to eq(container_item_key)
|
12
|
+
expect(container_item.value).to eq(container_item_value)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'reifies value' do
|
16
|
+
container_item.reify()
|
17
|
+
expect(container_item.reified_value).to eq(container_item_value_reified)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does not reify if already reified' do
|
21
|
+
container_item.reify()
|
22
|
+
expect(container_item.reified?).to be_true
|
23
|
+
|
24
|
+
expect(container_item.reify()).to be_false
|
25
|
+
expect(container_item.reified?).to be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'applies injection using container' do
|
29
|
+
container_item.reify()
|
30
|
+
expect(container_item.inject(container_item_value_reified)).to be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'applies injection before returning instance' do
|
34
|
+
container.stub(:inject) do |value|
|
35
|
+
expect(value).to eq(container_item_value_reified)
|
36
|
+
end
|
37
|
+
|
38
|
+
container_item.instance()
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns instance for item' do
|
42
|
+
expect(container_item.instance).to eq(container_item_value_reified)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe ContainerItem do
|
47
|
+
let(:container) do
|
48
|
+
c = double()
|
49
|
+
c.stub(:inject).with(anything()) { true }
|
50
|
+
c
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:container_item_key) { :lorem }
|
54
|
+
let(:container_item_value) { 'lorem' }
|
55
|
+
let(:container_item_value_reified) { 'lorem' }
|
56
|
+
let(:container_item) {
|
57
|
+
c = ContainerItem.new(container)
|
58
|
+
c.store(container_item_key, container_item_value)
|
59
|
+
c
|
60
|
+
}
|
61
|
+
|
62
|
+
context 'without proc' do
|
63
|
+
it_behaves_like "a container_item"
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with proc' do
|
67
|
+
context 'without argument' do
|
68
|
+
let(:container_item_value) {
|
69
|
+
Proc.new do
|
70
|
+
'lorem'
|
71
|
+
end
|
72
|
+
}
|
73
|
+
|
74
|
+
it_behaves_like 'a container_item'
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'with argument' do
|
78
|
+
let(:container_item_value) {
|
79
|
+
Proc.new do |c|
|
80
|
+
expect(c).to eq(container)
|
81
|
+
'lorem'
|
82
|
+
end
|
83
|
+
}
|
84
|
+
|
85
|
+
it_behaves_like 'a container_item'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'encase/container'
|
3
|
+
require 'encase'
|
4
|
+
|
5
|
+
module Encase
|
6
|
+
class Lorem
|
7
|
+
end
|
8
|
+
|
9
|
+
class MyBox
|
10
|
+
include Encase
|
11
|
+
|
12
|
+
needs :apples, :mangoes
|
13
|
+
needs :oranges
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Container do
|
17
|
+
let(:container) {
|
18
|
+
Container.new
|
19
|
+
}
|
20
|
+
|
21
|
+
it 'can store objects' do
|
22
|
+
container.object(:lorem, 'lorem')
|
23
|
+
expect(container.lookup(:lorem)).to eq('lorem')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can store factories' do
|
27
|
+
container.factory(:lorem, Lorem)
|
28
|
+
expect(container.lookup(:lorem)).to be_a(Lorem)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can store singletons' do
|
32
|
+
container.singleton(:lorem, Lorem)
|
33
|
+
instance1 = container.lookup(:lorem)
|
34
|
+
instance2 = container.lookup(:lorem)
|
35
|
+
|
36
|
+
expect(instance1).to eq(instance2)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'can be configured with a DSL' do
|
40
|
+
container.configure do
|
41
|
+
object :lorem, 'lorem'
|
42
|
+
factory :lipsum, Lorem
|
43
|
+
singleton :dolor, Lorem
|
44
|
+
end
|
45
|
+
|
46
|
+
expect(container.lookup(:lorem)).to eq('lorem')
|
47
|
+
expect(container.lookup(:lipsum)).to be_a(Lorem)
|
48
|
+
|
49
|
+
instance1 = container.lookup(:dolor)
|
50
|
+
instance2 = container.lookup(:dolor)
|
51
|
+
|
52
|
+
expect(instance1).to eq(instance2)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'raises a KeyError for unknown keys' do
|
56
|
+
expect {
|
57
|
+
container.lookup(:unknown_key)
|
58
|
+
}.to raise_error(KeyError)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'can create nested containers' do
|
62
|
+
child = container.child()
|
63
|
+
grand_child = child.child()
|
64
|
+
|
65
|
+
expect(grand_child.parent).to eq(child)
|
66
|
+
expect(child.parent).to eq(container)
|
67
|
+
expect(container.parent).to be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'can lookup values from nested containers' do
|
71
|
+
child = container.child()
|
72
|
+
grand_child = child.child()
|
73
|
+
|
74
|
+
container.object(:lorem, 'in_container')
|
75
|
+
child.object(:lorem, 'in_child')
|
76
|
+
grand_child.object(:lorem, 'in_grand_child')
|
77
|
+
|
78
|
+
expect(grand_child.lookup(:lorem)).to eq('in_grand_child')
|
79
|
+
|
80
|
+
grand_child.unregister(:lorem)
|
81
|
+
expect(grand_child.lookup(:lorem)).to eq('in_child')
|
82
|
+
|
83
|
+
child.unregister(:lorem)
|
84
|
+
expect(grand_child.lookup(:lorem)).to eq('in_container')
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'can clear the container store' do
|
88
|
+
container.object(:lorem, 'lorem')
|
89
|
+
container.object(:ipsum, 'ipsum')
|
90
|
+
container.object(:dolor, 'dolor')
|
91
|
+
|
92
|
+
container.clear
|
93
|
+
|
94
|
+
expect(container.contains?(:lorem)).to be_false
|
95
|
+
expect(container.contains?(:ipsum)).to be_false
|
96
|
+
expect(container.contains?(:dolor)).to be_false
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'can inject dependencies' do
|
100
|
+
container.configure do
|
101
|
+
object :apples, 10
|
102
|
+
object :mangoes, 20
|
103
|
+
object :oranges, 30
|
104
|
+
factory :box, MyBox
|
105
|
+
end
|
106
|
+
|
107
|
+
box = container.lookup(:box)
|
108
|
+
expect(box.apples).to eq(10)
|
109
|
+
expect(box.mangoes).to eq(20)
|
110
|
+
expect(box.oranges).to eq(30)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'can inject dependencies lazily' do
|
114
|
+
container.configure do
|
115
|
+
object :apples, -> { 10 }
|
116
|
+
object :mangoes, -> { 20 }
|
117
|
+
object :oranges, -> { 30 }
|
118
|
+
factory :box, -> { MyBox }
|
119
|
+
end
|
120
|
+
|
121
|
+
box = container.lookup(:box)
|
122
|
+
expect(box.apples).to eq(10)
|
123
|
+
expect(box.mangoes).to eq(20)
|
124
|
+
expect(box.oranges).to eq(30)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'assigns container on injection' do
|
128
|
+
class DummyObjectToBeInjected
|
129
|
+
include Encase
|
130
|
+
needs :lorem
|
131
|
+
end
|
132
|
+
|
133
|
+
container.object(:lorem, 'lorem')
|
134
|
+
container.factory(:dummy, DummyObjectToBeInjected)
|
135
|
+
|
136
|
+
dummy = container.lookup(:dummy)
|
137
|
+
expect(dummy.container).to eq(container)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'call on_inject hook after injection' do
|
141
|
+
class DummyWithOnInject
|
142
|
+
include Encase
|
143
|
+
needs :lorem
|
144
|
+
attr_accessor :injected
|
145
|
+
|
146
|
+
def on_inject
|
147
|
+
self.injected = true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
container.object(:lorem, 'lorem')
|
152
|
+
container.factory(:dummy, DummyWithOnInject)
|
153
|
+
|
154
|
+
dummy = container.lookup(:dummy)
|
155
|
+
expect(dummy.injected).to be_true
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'Performance', :benchmark => true do
|
159
|
+
require 'benchmark'
|
160
|
+
|
161
|
+
it 'can store 100s of dependencies quickly' do
|
162
|
+
runtime = Benchmark.realtime do
|
163
|
+
container.configure do
|
164
|
+
10000.times do |i|
|
165
|
+
item = "item#{i}"
|
166
|
+
object item.to_sym, item
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
expect(runtime).to be < 0.1
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'can lookup 100s of dependencies quickly' do
|
175
|
+
class ClassWithManyDeps
|
176
|
+
include Encase
|
177
|
+
|
178
|
+
100.times do |i|
|
179
|
+
needs "item#{i}".to_sym
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
container.configure do
|
184
|
+
100.times do |i|
|
185
|
+
item = "item#{i}"
|
186
|
+
object item.to_sym, item
|
187
|
+
end
|
188
|
+
|
189
|
+
factory :lorem, ClassWithManyDeps
|
190
|
+
end
|
191
|
+
|
192
|
+
runtime = Benchmark.realtime do
|
193
|
+
100.times do |i|
|
194
|
+
container.lookup(:lorem)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
expect(runtime).to be < 0.1
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'encase'
|
3
|
+
|
4
|
+
class EmptyBox
|
5
|
+
end
|
6
|
+
|
7
|
+
class Box
|
8
|
+
include Encase
|
9
|
+
|
10
|
+
needs :apples, :mangoes
|
11
|
+
needs :oranges
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'Encaseable' do
|
15
|
+
let(:box) { Box.new }
|
16
|
+
let(:empty_box) { EmptyBox.new }
|
17
|
+
|
18
|
+
it 'creates accessors if needs are specified' do
|
19
|
+
expect(Box).to respond_to(:needs_to_inject)
|
20
|
+
expect(box).to respond_to(:container)
|
21
|
+
expect(box).to respond_to(:apples)
|
22
|
+
expect(box).to respond_to(:mangoes)
|
23
|
+
expect(box).to respond_to(:oranges)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'does not create accessors if needs are not specified' do
|
27
|
+
expect(EmptyBox).to_not respond_to(:needs_to_inject)
|
28
|
+
expect(empty_box).to_not respond_to(:container)
|
29
|
+
expect(empty_box).to_not respond_to(:apples)
|
30
|
+
expect(empty_box).to_not respond_to(:mangoes)
|
31
|
+
expect(empty_box).to_not respond_to(:oranges)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'stores provided dependencies for later lookup' do
|
35
|
+
expect(box.class.needs_to_inject).to match_array([:apples, :mangoes, :oranges])
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'encase/factory_item'
|
3
|
+
|
4
|
+
module Encase
|
5
|
+
class MyClass
|
6
|
+
end
|
7
|
+
|
8
|
+
describe FactoryItem do
|
9
|
+
let(:container) {
|
10
|
+
c = double()
|
11
|
+
c.stub(:inject).with(anything()) { true }
|
12
|
+
c
|
13
|
+
}
|
14
|
+
|
15
|
+
let(:factory_item) {
|
16
|
+
f = FactoryItem.new(container)
|
17
|
+
f.store(:lorem, MyClass)
|
18
|
+
f
|
19
|
+
}
|
20
|
+
|
21
|
+
it 'creates a new instance on every fetch' do
|
22
|
+
factory_item.reify()
|
23
|
+
|
24
|
+
instance1 = factory_item.fetch()
|
25
|
+
instance2 = factory_item.fetch()
|
26
|
+
|
27
|
+
expect(instance1).to_not eq(instance2)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'applies injection on created instance' do
|
31
|
+
container.stub(:inject) do |to_inject|
|
32
|
+
expect(to_inject).to be_a(MyClass)
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
factory_item.instance
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'encase/object_item'
|
3
|
+
|
4
|
+
module Encase
|
5
|
+
describe ObjectItem do
|
6
|
+
let(:container) {
|
7
|
+
c = double()
|
8
|
+
c.stub(:inject).with(anything()) { true }
|
9
|
+
c
|
10
|
+
}
|
11
|
+
|
12
|
+
let(:object_item) {
|
13
|
+
o = ObjectItem.new(container)
|
14
|
+
o.store(:lorem, 'lorem')
|
15
|
+
o
|
16
|
+
}
|
17
|
+
|
18
|
+
it 'applies injection if not injected' do
|
19
|
+
expect(object_item.inject('lorem')).to be_true
|
20
|
+
expect(object_item.injected).to be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'does not apply injection if already injected' do
|
24
|
+
object_item.inject('lorem')
|
25
|
+
|
26
|
+
expect(object_item.inject('lorem')).to be_false
|
27
|
+
expect(object_item.injected).to be_true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'encase/singleton_item'
|
3
|
+
|
4
|
+
module Encase
|
5
|
+
class MySingletonClass
|
6
|
+
end
|
7
|
+
|
8
|
+
describe SingletonItem do
|
9
|
+
let(:container) {
|
10
|
+
c = double()
|
11
|
+
c.stub(:inject).with(anything()) { true }
|
12
|
+
c
|
13
|
+
}
|
14
|
+
|
15
|
+
let(:singleton_item) {
|
16
|
+
s = SingletonItem.new(container)
|
17
|
+
s.store(:lorem, MySingletonClass)
|
18
|
+
s
|
19
|
+
}
|
20
|
+
|
21
|
+
it 'returns the same object on every fetch' do
|
22
|
+
singleton_item.reify()
|
23
|
+
|
24
|
+
instance1 = singleton_item.fetch()
|
25
|
+
instance2 = singleton_item.fetch()
|
26
|
+
|
27
|
+
expect(instance1).to eq(instance2)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: encase
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Darshan Sawardekar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.14.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.14.1
|
55
|
+
description: Encase is a library for using dependency injection within ruby applications.
|
56
|
+
It provides a lightweight IOC Container that manages dependencies between your classes.
|
57
|
+
It was written to assist with wiring of domain objects outside Rails applications
|
58
|
+
to enable faster test suites.
|
59
|
+
email:
|
60
|
+
- darshan@sawardekar.org
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- .rspec
|
67
|
+
- .travis.yml
|
68
|
+
- CHANGELOG.md
|
69
|
+
- Gemfile
|
70
|
+
- LICENSE.txt
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- encase.gemspec
|
74
|
+
- lib/encase.rb
|
75
|
+
- lib/encase/container.rb
|
76
|
+
- lib/encase/container_item.rb
|
77
|
+
- lib/encase/container_item_factory.rb
|
78
|
+
- lib/encase/encaseable.rb
|
79
|
+
- lib/encase/factory_item.rb
|
80
|
+
- lib/encase/object_item.rb
|
81
|
+
- lib/encase/singleton_item.rb
|
82
|
+
- lib/encase/version.rb
|
83
|
+
- portkey.json
|
84
|
+
- spec/lib/encase/container_item_factory_spec.rb
|
85
|
+
- spec/lib/encase/container_item_spec.rb
|
86
|
+
- spec/lib/encase/container_spec.rb
|
87
|
+
- spec/lib/encase/encaseable_spec.rb
|
88
|
+
- spec/lib/encase/factory_item_spec.rb
|
89
|
+
- spec/lib/encase/object_item_spec.rb
|
90
|
+
- spec/lib/encase/singleton_item_spec.rb
|
91
|
+
- spec/spec_helper.rb
|
92
|
+
homepage: https://github.com/dsawardekar/encase
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.2.0
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Lightweight IOC Container for ruby.
|
116
|
+
test_files:
|
117
|
+
- spec/lib/encase/container_item_factory_spec.rb
|
118
|
+
- spec/lib/encase/container_item_spec.rb
|
119
|
+
- spec/lib/encase/container_spec.rb
|
120
|
+
- spec/lib/encase/encaseable_spec.rb
|
121
|
+
- spec/lib/encase/factory_item_spec.rb
|
122
|
+
- spec/lib/encase/object_item_spec.rb
|
123
|
+
- spec/lib/encase/singleton_item_spec.rb
|
124
|
+
- spec/spec_helper.rb
|