carb-inject 2.0.1
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 +7 -0
- data/.gitignore +40 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.md +25 -0
- data/README.md +251 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/carb-inject.gemspec +29 -0
- data/lib/carb-inject.rb +1 -0
- data/lib/carb/inject.rb +11 -0
- data/lib/carb/inject/auto_injectable.rb +20 -0
- data/lib/carb/inject/callable_container.rb +35 -0
- data/lib/carb/inject/delegate_container.rb +44 -0
- data/lib/carb/inject/dependency_list.rb +96 -0
- data/lib/carb/inject/dependency_list_cache_name.rb +5 -0
- data/lib/carb/inject/dependency_missing_error.rb +13 -0
- data/lib/carb/inject/error_container.rb +23 -0
- data/lib/carb/inject/injectable.rb +19 -0
- data/lib/carb/inject/injector.rb +99 -0
- data/lib/carb/inject/store_dependencies.rb +47 -0
- data/lib/carb/inject/version.rb +5 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6e7e8fdd6dec580c4ea33d02cfb2796409c964c9
|
4
|
+
data.tar.gz: 941f73b2350fc7f498d50d09e0dafdb5760a7f2c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0e22b75b3e7d5790ced046f8637ea6bbaa0431a04e18bfaea3a686921dd00685a343e11c38241fa3227300b4101b0f59d5206ab03e38e9df7aa169f31c154c93
|
7
|
+
data.tar.gz: 2d8bfe0487510f41c98f1883e838d395c4c48cc29b1530452ddf359a18fa4410d33cac3be340bff71109c70f545a1f79a19d0972a7553746e57bf9c10504a951
|
data/.gitignore
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/Gemfile.lock
|
4
|
+
/_yardoc/
|
5
|
+
/coverage/
|
6
|
+
/doc/
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/tmp/
|
10
|
+
|
11
|
+
.project
|
12
|
+
.directory
|
13
|
+
*.swp
|
14
|
+
*~
|
15
|
+
.DS_Store
|
16
|
+
.idea
|
17
|
+
.envrc
|
18
|
+
.nvmrc
|
19
|
+
.ruby-gemset
|
20
|
+
.ruby-version
|
21
|
+
.rspec-local
|
22
|
+
public
|
23
|
+
*.backup
|
24
|
+
npm-debug.log
|
25
|
+
.rspec_status
|
26
|
+
|
27
|
+
# Ignore bundler config
|
28
|
+
/.bundle
|
29
|
+
/vendor/bundle
|
30
|
+
|
31
|
+
# Ignore all logfiles and tempfiles.
|
32
|
+
/log/*.log
|
33
|
+
/tmp
|
34
|
+
/binstubs/
|
35
|
+
|
36
|
+
# Ignore user import data
|
37
|
+
/data
|
38
|
+
|
39
|
+
# YARD
|
40
|
+
.yardoc
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Copyright © `2016` `Predictable Revenue, Inc.`
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
7
|
+
obtaining a copy of this software and associated documentation
|
8
|
+
files (the “Software”), to deal in the Software without
|
9
|
+
restriction, including without limitation the rights to use,
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
copies of the Software, and to permit persons to whom the
|
12
|
+
Software is furnished to do so, subject to the following
|
13
|
+
conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
# carb-inject
|
2
|
+
|
3
|
+
`carb-inject` is an utility library for automated dependency injection.
|
4
|
+
Together with a generic container (even a simple `Hash`!), it will cover all
|
5
|
+
the needs for an IoC container.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'carb-inject'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install carb-inject
|
22
|
+
|
23
|
+
## Glossary
|
24
|
+
|
25
|
+
<table>
|
26
|
+
<tr>
|
27
|
+
<th>Term</th>
|
28
|
+
<th>Meaning</th>
|
29
|
+
</tr>
|
30
|
+
<tr>
|
31
|
+
<th>Dependency</th>
|
32
|
+
<td>
|
33
|
+
The actual Object a dependency is (a number for example). Can be
|
34
|
+
extracted from the container with <code>container[dependency_name]</code>
|
35
|
+
</td>
|
36
|
+
</tr>
|
37
|
+
<tr>
|
38
|
+
<th>Dependency name</th>
|
39
|
+
<td>
|
40
|
+
An object which allows extracting a <code>Dependency</code> from the
|
41
|
+
container
|
42
|
+
</td>
|
43
|
+
</tr>
|
44
|
+
<tr>
|
45
|
+
<th>Dependency alias</th>
|
46
|
+
<td>
|
47
|
+
A symbol representing <code>Dependency name</code>, must be a valid method
|
48
|
+
name
|
49
|
+
</td>
|
50
|
+
</tr>
|
51
|
+
<tr>
|
52
|
+
<th>Array of dependency names</th>
|
53
|
+
<td>
|
54
|
+
An array of <code>Dependency name</code>. When passed to the injector,
|
55
|
+
every object must support <code>to_s</code> and the returned
|
56
|
+
<code>String</code> must be a valid method name
|
57
|
+
</td>
|
58
|
+
</tr>
|
59
|
+
<tr>
|
60
|
+
<th>Hash of dependency aliases (or hash of aliases)</th>
|
61
|
+
<td>
|
62
|
+
A hash consisting of <code>Dependency alias => Dependency name</code>
|
63
|
+
</td>
|
64
|
+
</tr>
|
65
|
+
</table>
|
66
|
+
|
67
|
+
## Usage
|
68
|
+
|
69
|
+
First you'll need a container object.
|
70
|
+
[dry-container](https://github.com/dry-rb/dry-container) will do the trick,
|
71
|
+
otherwise just check the implementation of
|
72
|
+
[Carb::SimpleContainer](https://github.com/Carburetor/carb-inject/blob/b3e9fea68672284aff53c8b78ba0064474d94021/spec/support/simple_container.rb) in
|
73
|
+
tests
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
container = { name: "john", age: 30 }
|
77
|
+
```
|
78
|
+
|
79
|
+
Create an injector that you'll use across your application. Usually you want to
|
80
|
+
put this in a constant
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
require "carb-inject"
|
84
|
+
Inject = Carb::Inject::Injector.new(container)
|
85
|
+
```
|
86
|
+
|
87
|
+
Then, create a class you want dependencies injected automatically
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class JohnPerson
|
91
|
+
include Inject[:name, :age]
|
92
|
+
|
93
|
+
def initialize(**deps)
|
94
|
+
inject_dependencies!(deps)
|
95
|
+
end
|
96
|
+
|
97
|
+
def hello
|
98
|
+
"Hello I'm #{name}, #{age} years old"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
And finally, use the class!
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
john = JohnPerson.new
|
107
|
+
john.hello # => Hello I'm john, 30 years old
|
108
|
+
```
|
109
|
+
|
110
|
+
You can overwrite dependencies on the fly
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
john = JohnPerson.new(age: 20)
|
114
|
+
john.hello # => Hello I'm john, 20 years old
|
115
|
+
```
|
116
|
+
|
117
|
+
You can still require different arguments in the constructor
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
class JohnPerson
|
121
|
+
include Inject[:name, :age]
|
122
|
+
|
123
|
+
def initialize(last_name, **dependencies)
|
124
|
+
inject_dependencies!(dependencies)
|
125
|
+
@last_name = last_name
|
126
|
+
end
|
127
|
+
|
128
|
+
def hello
|
129
|
+
"Hello I'm #{name} #{@last_name}, #{age} years old"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
john = JohnPerson.new("snow", age: 20)
|
134
|
+
john.hello # => Hello I'm john snow, 20 years old
|
135
|
+
```
|
136
|
+
|
137
|
+
Finally, you can alias dependencies
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class JohnPerson
|
141
|
+
include Inject[special_name: :name, a_number: :age]
|
142
|
+
|
143
|
+
def initialize(**deps)
|
144
|
+
inject_dependencies!(deps)
|
145
|
+
end
|
146
|
+
|
147
|
+
def hello
|
148
|
+
"special_name is #{special_name}, a_number is #{a_number}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
john = JohnPerson.new(a_number: 20)
|
153
|
+
john.hello # => special_name is john, a_number is 20
|
154
|
+
```
|
155
|
+
|
156
|
+
Be aware, you can't pass on-the-fly dependencies that were not defined on that
|
157
|
+
class. If you do, you must be the one taking care of them!
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class JohnPerson
|
161
|
+
include Inject[:name]
|
162
|
+
|
163
|
+
def initialize(**deps)
|
164
|
+
inject_dependencies!(deps)
|
165
|
+
end
|
166
|
+
|
167
|
+
def hello
|
168
|
+
"Hello I'm #{name}, #{age} years old"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
john = JohnPerson.new(age: 20)
|
173
|
+
john.hello # => NameError: undefined local variable or method `age'
|
174
|
+
```
|
175
|
+
|
176
|
+
### Auto invoke inject_dependencies!
|
177
|
+
|
178
|
+
Instead of manually calling `inject_dependencies!`, you can invoke the
|
179
|
+
injector with `true` as second argument. This has the downsides of including
|
180
|
+
a module which creates an initializer, with all the consequences it creates
|
181
|
+
(some issues with inheritance). It's not recommended, but if you don't use
|
182
|
+
inheritance, it does the trick.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
require "carb-inject"
|
186
|
+
|
187
|
+
container = { name: "john", age: 30 }
|
188
|
+
Inject = Carb::Inject::Injector.new(container, true)
|
189
|
+
|
190
|
+
class JohnPerson
|
191
|
+
include Inject[:name, :age]
|
192
|
+
|
193
|
+
def hello
|
194
|
+
"Hello I'm #{name}, #{age} years old"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
john = JohnPerson.new
|
199
|
+
john.hello # => Hello I'm john, 30 years old
|
200
|
+
```
|
201
|
+
|
202
|
+
### Passing lambdas
|
203
|
+
|
204
|
+
There is an alternative way to use the library in a _containerless_ fashion.
|
205
|
+
You will pass a list of dependency as usual, but instead of aliasing them,
|
206
|
+
pass a lambda and it will be resolved when used
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
require "carb-inject"
|
210
|
+
|
211
|
+
container = { name: "John", last_name: "Snow" }
|
212
|
+
Inject = Carb::Inject::Injector.new(container, true)
|
213
|
+
|
214
|
+
class JohnPerson
|
215
|
+
include Inject[:last_name, foo: :name, age: -> { 30 }]
|
216
|
+
|
217
|
+
def hello
|
218
|
+
"Hello I'm #{foo} #{last_name}, #{age} years old"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
john = JohnPerson.new
|
223
|
+
john.hello # => Hello I'm John Snow, 30 years old
|
224
|
+
```
|
225
|
+
|
226
|
+
## Gotchas
|
227
|
+
|
228
|
+
- Alias hash **must have symbols as keys**
|
229
|
+
- Straight dependency names, when used in array and not as values for alias
|
230
|
+
hash, must support `to_s` and the resulting `String` must be a valid method
|
231
|
+
name (an exception is raised otherwise)
|
232
|
+
|
233
|
+
## Features
|
234
|
+
|
235
|
+
- Supports inheritance (as long as you call `super`)
|
236
|
+
- Can write your own injector if you don't like the syntax of the existing one
|
237
|
+
- Can alias dependencies
|
238
|
+
- Supports any container which responds to `[]`
|
239
|
+
- Can write your own initializer with your own arguments (as long as you call
|
240
|
+
`super`)
|
241
|
+
|
242
|
+
## Development
|
243
|
+
|
244
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
245
|
+
|
246
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
247
|
+
|
248
|
+
## Contributing
|
249
|
+
|
250
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Carburetor/carb-inject.
|
251
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "carb-inject"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "pry-byebug"
|
14
|
+
Pry.start
|
data/bin/setup
ADDED
data/carb-inject.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "carb/inject/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "carb-inject"
|
8
|
+
spec.version = Carb::Inject::VERSION
|
9
|
+
spec.authors = ["Fire-Dragon-DoL"]
|
10
|
+
spec.email = ["francesco.belladonna@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Utility for automatic dependency injection}
|
13
|
+
spec.description = %q{Utility for automatic dependency injection with support for aliases}
|
14
|
+
spec.homepage = "https://github.com/Carburetor/carb-inject"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "carb-core", ">= 1.0.0"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
26
|
+
spec.add_development_dependency "rake", "~> 11.0"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
+
spec.add_development_dependency "pry-byebug"
|
29
|
+
end
|
data/lib/carb-inject.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "carb/inject"
|
data/lib/carb/inject.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "carb/inject/version"
|
2
|
+
require "carb/inject/dependency_list_cache_name"
|
3
|
+
require "carb/inject/dependency_list"
|
4
|
+
require "carb/inject/injectable"
|
5
|
+
require "carb/inject/injector"
|
6
|
+
require "carb/inject/dependency_missing_error"
|
7
|
+
|
8
|
+
module Carb
|
9
|
+
module Inject
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "carb"
|
2
|
+
|
3
|
+
module Carb::Inject
|
4
|
+
# Provides an initializer which sets instance variables with names of
|
5
|
+
# dependencies and as value the dependency itself.
|
6
|
+
# Requires {#inject_dependencies!} to be available
|
7
|
+
module AutoInjectable
|
8
|
+
# Initializes the object with passed dependencies
|
9
|
+
# @param dependencies [Hash] map where key is the name of the dependency
|
10
|
+
# and value is the actual dependency being injected
|
11
|
+
# @raise [TypeError] raises if doesn't respond to {#inject_dependencies!}
|
12
|
+
def initialize(**dependencies)
|
13
|
+
unless respond_to?(:inject_dependencies!, true)
|
14
|
+
raise TypeError, "#{ self.class } must be injectable"
|
15
|
+
end
|
16
|
+
|
17
|
+
inject_dependencies!(**dependencies)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "carb"
|
2
|
+
require "carb/inject/dependency_missing_error"
|
3
|
+
|
4
|
+
module Carb::Inject
|
5
|
+
# Simple container for holding dependency hashmap with Lambda callable values
|
6
|
+
class CallableContainer
|
7
|
+
private
|
8
|
+
|
9
|
+
attr_reader :dependencies
|
10
|
+
|
11
|
+
public
|
12
|
+
|
13
|
+
# @param dependencies [Hash{Object => Proc}] dependency name with proc
|
14
|
+
# as value, which will be `call`ed to extract the dependency
|
15
|
+
def initialize(dependencies)
|
16
|
+
@dependencies = dependencies
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param name [Object] dependency name
|
20
|
+
# @return [Object] dependency for given name, obtained by calling lambda
|
21
|
+
# @raise [DependencyMissingError]
|
22
|
+
def [](name)
|
23
|
+
return dependencies[name].call if has_key?(name)
|
24
|
+
|
25
|
+
error_class = ::Carb::Inject::DependencyMissingError
|
26
|
+
raise error_class.new(name), format(error_class::MESSAGE, name.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param name [Object] dependency name
|
30
|
+
# @return [Boolean] true if dependency is present, false otherwise
|
31
|
+
def has_key?(name)
|
32
|
+
dependencies.has_key?(name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "carb"
|
2
|
+
require "carb/inject/dependency_missing_error"
|
3
|
+
|
4
|
+
module Carb::Inject
|
5
|
+
# Container which requests dependency in sequence to a list of containers
|
6
|
+
# otherwise and if none returns, it raises
|
7
|
+
class DelegateContainer
|
8
|
+
private
|
9
|
+
|
10
|
+
attr_reader :containers
|
11
|
+
|
12
|
+
public
|
13
|
+
|
14
|
+
# @param containers [Array<#[], #has_key?>] Must have at least one
|
15
|
+
# container
|
16
|
+
def initialize(*containers)
|
17
|
+
@containers = containers
|
18
|
+
|
19
|
+
if containers.size < 1
|
20
|
+
raise ArgumentError, "At least one container is required"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param name [Object] dependency name
|
25
|
+
# @return [Object] dependency for given name if present in any container
|
26
|
+
# (only the first in sequence is returned), otherwise raises
|
27
|
+
# @raise [DependencyMissingError]
|
28
|
+
def [](name)
|
29
|
+
containers.each do |container|
|
30
|
+
return container[name] if container.has_key?(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
error_class = ::Carb::Inject::DependencyMissingError
|
34
|
+
raise error_class.new(name), format(error_class::MESSAGE, name.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param name [Object] dependency name
|
38
|
+
# @return [Boolean] true if dependency is present in any container, false
|
39
|
+
# otherwise
|
40
|
+
def has_key?(name)
|
41
|
+
containers.any? { |container| container.has_key?(name) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "carb"
|
2
|
+
require "carb/inject/injectable"
|
3
|
+
require "carb/inject/auto_injectable"
|
4
|
+
require "carb/inject/dependency_list_cache_name"
|
5
|
+
|
6
|
+
module Carb::Inject
|
7
|
+
# Provides the list of dependencies required by the object to be initialized
|
8
|
+
class DependencyList < Module
|
9
|
+
private
|
10
|
+
|
11
|
+
attr_reader :container
|
12
|
+
attr_reader :auto_inject
|
13
|
+
attr_reader :dependencies
|
14
|
+
|
15
|
+
public
|
16
|
+
|
17
|
+
# Uses the passed container to resolve listed dependencies
|
18
|
+
# @param container [#[]] resolve dependencies by using the value of
|
19
|
+
# `dependencies` hash passed to this initializer
|
20
|
+
# @param auto_inject [Boolean] if true, includes
|
21
|
+
# {::Carb::Inject::AutoInjectable} which provides an initializer that
|
22
|
+
# inject dependencies automatically
|
23
|
+
# @param dependencies [Hash{Symbol => Object}] a hash representing
|
24
|
+
# required dependencies for the object. The key represent the alias used
|
25
|
+
# to access the real dependency name, which is the value of the hash.
|
26
|
+
# If you want to access the dependency using its real name, just set the
|
27
|
+
# alias to the real name. Example: `{ alias: :real_name }`
|
28
|
+
def initialize(container, auto_inject = true, **dependencies)
|
29
|
+
ensure_correct_types!(container, dependencies)
|
30
|
+
|
31
|
+
@container = container
|
32
|
+
@auto_inject = auto_inject
|
33
|
+
@dependencies = dependencies
|
34
|
+
end
|
35
|
+
|
36
|
+
def included(klass)
|
37
|
+
memoize_dependency_list(klass)
|
38
|
+
include_injectable(klass)
|
39
|
+
define_readers(klass)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Loops over each available dependency and yields it using its alias and
|
43
|
+
# the dependency itself
|
44
|
+
# @yieldparam name [Object] alias of the dependency for this dependency_list
|
45
|
+
# @yieldparam dependency [Object] the dependency object for this container
|
46
|
+
def each
|
47
|
+
dependencies.each do |key, value|
|
48
|
+
yield(key, container[value])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def dependency_names
|
55
|
+
dependencies.keys
|
56
|
+
end
|
57
|
+
|
58
|
+
def memoize_dependency_list(klass)
|
59
|
+
if klass.instance_variable_defined?(DependencyListCacheName)
|
60
|
+
raise TypeError, "class already injecting"
|
61
|
+
end
|
62
|
+
|
63
|
+
klass.instance_variable_set(DependencyListCacheName, self)
|
64
|
+
end
|
65
|
+
|
66
|
+
def include_injectable(klass)
|
67
|
+
klass.include(::Carb::Inject::Injectable)
|
68
|
+
klass.include(::Carb::Inject::AutoInjectable) if auto_inject
|
69
|
+
end
|
70
|
+
|
71
|
+
def define_readers(klass)
|
72
|
+
dependencies.each do |name, _|
|
73
|
+
define_reader(klass, name.to_s)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def define_reader(klass, name)
|
78
|
+
unless klass.method_defined?(name)
|
79
|
+
klass.send(:attr_reader, name)
|
80
|
+
klass.send(:protected, name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def ensure_correct_types!(container, dependencies)
|
85
|
+
raise TypeError, "container can't be nil" if container.nil?
|
86
|
+
unless container.respond_to?(:[])
|
87
|
+
raise TypeError, "container doesn't respond to #[]"
|
88
|
+
end
|
89
|
+
|
90
|
+
raise TypeError, "dependencies can't be nil" if dependencies.nil?
|
91
|
+
unless dependencies.respond_to?(:[])
|
92
|
+
raise TypeError, "dependencies doesn't respond to #[]"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "carb"
|
2
|
+
require "carb/inject/dependency_missing_error"
|
3
|
+
|
4
|
+
module Carb::Inject
|
5
|
+
# Container which holds no dependency and will raise every time
|
6
|
+
# one is being fetched
|
7
|
+
class ErrorContainer
|
8
|
+
# This method will always raise an error
|
9
|
+
# @param name [Object] dependency name
|
10
|
+
# @raise [::Carb::Inject::DependencyMissingError] raised
|
11
|
+
# whenever a dependency is being fetched from this container
|
12
|
+
def [](name)
|
13
|
+
error_class = ::Carb::Inject::DependencyMissingError
|
14
|
+
raise error_class.new(name), format(error_class::MESSAGE, name.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param name [Object] dependency name
|
18
|
+
# @return [false]
|
19
|
+
def has_key?(name)
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "carb"
|
2
|
+
require "carb/inject/dependency_list_cache_name"
|
3
|
+
require "carb/inject/store_dependencies"
|
4
|
+
|
5
|
+
module Carb::Inject
|
6
|
+
# Provides a method which sets instance variables with names of
|
7
|
+
# dependencies and as value the dependency itself
|
8
|
+
module Injectable
|
9
|
+
protected
|
10
|
+
|
11
|
+
# Inject passed dependencies
|
12
|
+
# @param dependencies [Hash] map where key is the name of the dependency
|
13
|
+
# and value is the actual dependency being injected
|
14
|
+
def inject_dependencies!(**dependencies)
|
15
|
+
store_dependencies = StoreDependencies.new
|
16
|
+
store_dependencies.(self, **dependencies)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "carb"
|
2
|
+
require "carb/inject/dependency_list"
|
3
|
+
require "carb/inject/error_container"
|
4
|
+
require "carb/inject/delegate_container"
|
5
|
+
require "carb/inject/callable_container"
|
6
|
+
|
7
|
+
module Carb::Inject
|
8
|
+
# Creates an injector with the specified container
|
9
|
+
class Injector
|
10
|
+
private
|
11
|
+
|
12
|
+
attr_reader :container
|
13
|
+
attr_reader :auto_inject
|
14
|
+
|
15
|
+
public
|
16
|
+
|
17
|
+
# Initialize an injector that can be attached to a constant with the given
|
18
|
+
# container
|
19
|
+
# @param container [#[], #has_key?] must return dependencies based on
|
20
|
+
# dependency name
|
21
|
+
# @param auto_inject [Boolean] if true, provides an initializer that auto
|
22
|
+
# injects dependencies by including {::Carb::Inject::AutoInjectable},
|
23
|
+
# false by default
|
24
|
+
def initialize(container = nil, auto_inject = false)
|
25
|
+
@container = container || ErrorContainer.new
|
26
|
+
@auto_inject = auto_inject
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param dependencies [Array<#to_s>] Array of dependency names, which will
|
30
|
+
# be converted using {Object#to_s}, make sure the string version is a
|
31
|
+
# valid method name or it will raise, it will be used to create
|
32
|
+
# attr_readers on the object
|
33
|
+
# @param aliased [Hash{Symbol => Object, Proc}] if value is an {Object},
|
34
|
+
# alias => dependency name hash, the alias must be a valid method name
|
35
|
+
# or it will raise. The aliases will be used to create attr_readers
|
36
|
+
# which will return the dependency from the container.
|
37
|
+
# If value is {Proc}, an attr_reader with the key as bane is created and
|
38
|
+
# value the output of invoking the {Proc}
|
39
|
+
# @return [DependencyList] module which can be included and will take care
|
40
|
+
# of automatically injecting not-supplied dependency
|
41
|
+
# @raise [ArgumentError] if passed dependencies or aliased_dependencies
|
42
|
+
# contain objects not convertible to valid method names
|
43
|
+
def [](*dependencies, **aliased)
|
44
|
+
deps, lambdas = merge_dependencies(dependencies, aliased)
|
45
|
+
wrapper = container
|
46
|
+
wrapper = build_delegate(container, lambdas) unless lambdas.empty?
|
47
|
+
|
48
|
+
Carb::Inject::DependencyList.new(wrapper, auto_inject, **deps)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def build_delegate(container, lambdas)
|
54
|
+
callable = CallableContainer.new(lambdas)
|
55
|
+
DelegateContainer.new(callable, container)
|
56
|
+
end
|
57
|
+
|
58
|
+
def merge_dependencies(dependencies, aliased_dependencies)
|
59
|
+
deps = {}
|
60
|
+
|
61
|
+
dependencies.each { |name| deps[name] = name }
|
62
|
+
lambdas = add_aliased_dependencies(deps, aliased_dependencies)
|
63
|
+
|
64
|
+
[clean_names(deps), lambdas]
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_aliased_dependencies(deps, aliased_deps)
|
68
|
+
aliased_deps.each_with_object({}) do |(alias_name, value), lambdas|
|
69
|
+
lambdas[alias_name] = value if value.is_a?(::Proc)
|
70
|
+
deps[alias_name] = alias_name if value.is_a?(::Proc)
|
71
|
+
deps[alias_name] = value unless value.is_a?(::Proc)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def clean_names(dependencies)
|
76
|
+
deps = {}
|
77
|
+
|
78
|
+
dependencies.each do |aliased, name|
|
79
|
+
deps[clean!(aliased.to_s)] = name
|
80
|
+
end
|
81
|
+
|
82
|
+
deps
|
83
|
+
end
|
84
|
+
|
85
|
+
def clean!(name)
|
86
|
+
clean_name = name.gsub(".", "_").to_sym
|
87
|
+
|
88
|
+
unless method_name?(clean_name)
|
89
|
+
raise ArgumentError, "Invalid dependency name #{ name }"
|
90
|
+
end
|
91
|
+
|
92
|
+
clean_name
|
93
|
+
end
|
94
|
+
|
95
|
+
def method_name?(name)
|
96
|
+
/[@$"]/ !~ name.inspect
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "carb"
|
2
|
+
require "carb/inject/dependency_list_cache_name"
|
3
|
+
|
4
|
+
module Carb::Inject
|
5
|
+
# Store dependencies on the specified object, setting instance variables
|
6
|
+
# having as name the key of the hash and as value the value of the hash
|
7
|
+
# @api private
|
8
|
+
class StoreDependencies
|
9
|
+
def call(injectable, **dependencies)
|
10
|
+
@injectable = injectable
|
11
|
+
@dependencies = dependencies
|
12
|
+
|
13
|
+
inject_on(injectable.class)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :injectable
|
19
|
+
attr_reader :dependencies
|
20
|
+
|
21
|
+
def inject_on(klass)
|
22
|
+
return unless dependency_list_present?(klass)
|
23
|
+
|
24
|
+
parent_class = klass.superclass
|
25
|
+
inject_on(klass.superclass) if dependency_list_present?(parent_class)
|
26
|
+
|
27
|
+
list = dependency_list_for(klass)
|
28
|
+
store_using_dependency_list(list)
|
29
|
+
end
|
30
|
+
|
31
|
+
def store_using_dependency_list(list)
|
32
|
+
list.each do |name, dependency|
|
33
|
+
# Ensure `nil` is an acceptable dependency
|
34
|
+
dependency = dependencies[name] if dependencies.has_key?(name)
|
35
|
+
injectable.instance_variable_set(:"@#{ name }", dependency)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def dependency_list_present?(klass)
|
40
|
+
klass.instance_variable_defined?(DependencyListCacheName)
|
41
|
+
end
|
42
|
+
|
43
|
+
def dependency_list_for(klass)
|
44
|
+
klass.instance_variable_get(DependencyListCacheName)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: carb-inject
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Fire-Dragon-DoL
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: carb-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '11.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '11.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Utility for automatic dependency injection with support for aliases
|
84
|
+
email:
|
85
|
+
- francesco.belladonna@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.md
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- carb-inject.gemspec
|
100
|
+
- lib/carb-inject.rb
|
101
|
+
- lib/carb/inject.rb
|
102
|
+
- lib/carb/inject/auto_injectable.rb
|
103
|
+
- lib/carb/inject/callable_container.rb
|
104
|
+
- lib/carb/inject/delegate_container.rb
|
105
|
+
- lib/carb/inject/dependency_list.rb
|
106
|
+
- lib/carb/inject/dependency_list_cache_name.rb
|
107
|
+
- lib/carb/inject/dependency_missing_error.rb
|
108
|
+
- lib/carb/inject/error_container.rb
|
109
|
+
- lib/carb/inject/injectable.rb
|
110
|
+
- lib/carb/inject/injector.rb
|
111
|
+
- lib/carb/inject/store_dependencies.rb
|
112
|
+
- lib/carb/inject/version.rb
|
113
|
+
homepage: https://github.com/Carburetor/carb-inject
|
114
|
+
licenses: []
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 2.6.10
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: Utility for automatic dependency injection
|
136
|
+
test_files: []
|