injectable 0.0.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +291 -142
- data/Rakefile +2 -26
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/injectable.gemspec +28 -0
- data/lib/injectable.rb +57 -46
- data/lib/injectable/class_methods.rb +157 -0
- data/lib/injectable/dependencies_graph.rb +43 -0
- data/lib/injectable/dependencies_proxy.rb +29 -0
- data/lib/injectable/dependency.rb +40 -0
- data/lib/injectable/instance_methods.rb +53 -0
- data/lib/injectable/missing_dependencies_exception.rb +4 -0
- data/lib/injectable/version.rb +1 -2
- metadata +82 -31
- data/LICENSE +0 -20
- data/lib/injectable/container.rb +0 -127
- data/lib/injectable/inflector.rb +0 -30
- data/lib/injectable/macros.rb +0 -60
- data/lib/injectable/registerable.rb +0 -26
- data/lib/injectable/registry.rb +0 -89
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dc9ea4f80413a4e20a767b34804610ef6f6c5e24ae1670e3ee74909a928d4d33
|
4
|
+
data.tar.gz: f52f528a69f32a026258dd36017762256b410ae954950629190845e752a68c06
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 87a1f36da87711d78b9fa8ac6bcc80d6f4fe107e44e0174d786472efe667d5423482c7250f474c92f59bdbca4c780ca4d3eef6403af3da2e9851a0963588c813
|
7
|
+
data.tar.gz: ce7a234c6105ac0e8eeb5804e8d304a13a343c39147d3df782073bb44603a65100666cbad7593f4319f84dc4803c6c5614d2789f933c85a83420fc67a32089cf
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at dev@rubiconmd.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
injectable (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.3)
|
10
|
+
rake (12.3.2)
|
11
|
+
rspec (3.8.0)
|
12
|
+
rspec-core (~> 3.8.0)
|
13
|
+
rspec-expectations (~> 3.8.0)
|
14
|
+
rspec-mocks (~> 3.8.0)
|
15
|
+
rspec-core (3.8.0)
|
16
|
+
rspec-support (~> 3.8.0)
|
17
|
+
rspec-expectations (3.8.3)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.8.0)
|
20
|
+
rspec-mocks (3.8.0)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.8.0)
|
23
|
+
rspec-support (3.8.0)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler (~> 2.0)
|
30
|
+
injectable!
|
31
|
+
rake (~> 12.0)
|
32
|
+
rspec (~> 3.0)
|
33
|
+
|
34
|
+
BUNDLED WITH
|
35
|
+
2.0.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 amrocco
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,209 +1,358 @@
|
|
1
|
-
Injectable
|
2
|
-
========
|
1
|
+
# Injectable
|
3
2
|
|
4
|
-
Injectable is an
|
5
|
-
stupid simple and experimental so don't expect support for now - but may turn out
|
6
|
-
to be something in the future.
|
3
|
+
`Injectable` is an opinionated and declarative [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) library for ruby.
|
7
4
|
|
8
|
-
|
9
|
-
-----
|
5
|
+
It is being used in production (under ruby 2.5.1) in [RubiconMD](https://github.com/rubiconmd) and was extracted from its codebase.
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
can be injected into others must include the `Injectable` module.
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
15
10
|
|
16
11
|
```ruby
|
17
|
-
|
18
|
-
|
12
|
+
gem 'injectable', '>= 1.0.0'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install injectable
|
22
|
+
|
23
|
+
## Motivation
|
24
|
+
|
25
|
+
The main motivation of `Injectable` is to ease compliance with [SOLID's](https://en.wikipedia.org/wiki/SOLID)\*, [SRP](https://en.wikipedia.org/wiki/Single_responsibility_principle)\* and [Dependency Inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) by providing a declarative and very readable [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)\* which avoids lots of bolierplate code and thus encourages good practices.*
|
26
|
+
|
27
|
+
*Sorry about the acronyms, but using an [Ubiquitous Language](https://martinfowler.com/bliki/UbiquitousLanguage.html) is important.
|
28
|
+
|
29
|
+
### Encapsulate domain logic
|
30
|
+
|
31
|
+
Using Ruby on Rails recommended practices as an example, when your application grows enough you usually end up with huge model classes with too many responsibilities.
|
32
|
+
|
33
|
+
It's way better (although it requires effort and discipline) to split those models and extract domain logic into [Service Objects](https://martinfowler.com/bliki/AnemicDomainModel.html) ("SOs" from now on). You can do this without `Injectable`, but `Injectable` will make your SOs way more readable and a pleasure not only to write but also to test, while encouraging general good practices.
|
34
|
+
|
35
|
+
### Avoiding to hardcode dependencies
|
36
|
+
|
37
|
+
If you find occurences of `SomeClass.any_instance.expects(:method)` in your **unit** tests, then you are probably hardcoding dependencies:
|
38
|
+
|
39
|
+
```rb
|
40
|
+
test "MyClass#call"
|
41
|
+
Collaborator.any_instance.expects(:submit!) # hardcoded dependency
|
42
|
+
MyClass.new.call
|
19
43
|
end
|
20
44
|
|
21
|
-
class
|
22
|
-
|
45
|
+
class MyClass
|
46
|
+
attr_reader :collaborator
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@collaborator = Collaborator.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def call
|
53
|
+
collaborator.submit!
|
54
|
+
end
|
23
55
|
end
|
56
|
+
```
|
24
57
|
|
25
|
-
|
26
|
-
|
27
|
-
|
58
|
+
What if you did this instead:
|
59
|
+
|
60
|
+
```rb
|
61
|
+
test "MyClass#call"
|
62
|
+
collaborator = stub('Collaborator')
|
63
|
+
collaborator.expects(:submit!)
|
64
|
+
MyClass.new(collaborator: collaborator).call
|
65
|
+
end
|
66
|
+
|
67
|
+
class MyClass
|
68
|
+
attr_reader :collaborator
|
69
|
+
|
70
|
+
def initialize(collaborator: Collaborator.new) # we will just provide a default
|
71
|
+
@collaborator = collaborator
|
72
|
+
end
|
73
|
+
|
74
|
+
def call
|
75
|
+
collaborator.submit!
|
76
|
+
end
|
28
77
|
end
|
29
78
|
```
|
30
79
|
|
31
|
-
The
|
32
|
-
`FacebookService` as its arguments:
|
80
|
+
The benefits are not only for testing, as now your class is more modular and you can swap collaborators as long as they have the proper interface, in this case they have to `respond_to :submit!`
|
33
81
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
82
|
+
`Injectable` allows you to write the above code like this:
|
83
|
+
|
84
|
+
```rb
|
85
|
+
class MyClass
|
86
|
+
include Injectable
|
87
|
+
|
88
|
+
dependency :collaborator
|
89
|
+
|
90
|
+
def call
|
91
|
+
collaborator.submit!
|
92
|
+
end
|
93
|
+
end
|
38
94
|
```
|
39
95
|
|
40
|
-
|
96
|
+
It might not seem a lot but:
|
41
97
|
|
42
|
-
|
43
|
-
|
44
|
-
|
98
|
+
1. Imagine that you have 4 dependencies. That's a lot of boilerplate.
|
99
|
+
2. `Injectable` is not only this, it has many more features. Please keep reading.
|
100
|
+
|
101
|
+
## Usage example
|
102
|
+
|
103
|
+
`Injectable` is a mixin that you have to include in your class and it will provide several macros.
|
104
|
+
|
105
|
+
This is a real world example:
|
106
|
+
|
107
|
+
```rb
|
108
|
+
class PdfGenerator
|
109
|
+
include Injectable
|
110
|
+
|
111
|
+
dependency :wicked_pdf
|
112
|
+
|
113
|
+
argument :html
|
114
|
+
argument :render_footer, default: false
|
115
|
+
|
116
|
+
def call
|
117
|
+
wicked_pdf.pdf_from_string(html, options)
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def options
|
123
|
+
return {} unless render_footer
|
124
|
+
|
125
|
+
{
|
126
|
+
footer: {
|
127
|
+
left: footer,
|
128
|
+
}
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def footer
|
133
|
+
"Copyright ® #{Time.current.year}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# And you would use it like this:
|
138
|
+
PdfGenerator.call(html: '<some html here>')
|
139
|
+
# Overriding the wicked_pdf dependency:
|
140
|
+
PdfGenerator.new(wicked_pdf: wicked_pdf_replacement).call(html: '<some html>')
|
45
141
|
```
|
46
142
|
|
47
|
-
|
48
|
-
object of a specific type and let the container figure out the dependencies:
|
143
|
+
## Premises
|
49
144
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
145
|
+
In order to understand how (and why) `Injectable` works, you need to know some principles.
|
146
|
+
|
147
|
+
### #1 The `#call` method
|
148
|
+
|
149
|
+
`Injectable` classes **must define a public `#call` method that takes no arguments**.
|
150
|
+
|
151
|
+
This is **the only public method** you will be defining in your `Injectable` classes.
|
152
|
+
|
153
|
+
```rb
|
154
|
+
# Correct ✅
|
155
|
+
def call
|
156
|
+
# do stuff
|
157
|
+
end
|
158
|
+
|
159
|
+
# Wrong ❗️
|
160
|
+
def call(some_argument)
|
161
|
+
# won't work and will raise an exception at runtime
|
162
|
+
end
|
55
163
|
```
|
56
164
|
|
57
|
-
|
165
|
+
If you want your `#call` method to receive arguments, that's what the `#argument` macro is for. BTW, we call those **runtime arguments**.
|
58
166
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
167
|
+
Why `#call`?
|
168
|
+
|
169
|
+
Because it's a ruby idiom. Many things in ruby are `callable`, like lambdas.
|
170
|
+
|
171
|
+
### #2 The `initialize` method
|
172
|
+
|
173
|
+
Injectable classes take their **dependencies as keyword arguments** on the `initialize` method. They can also take **configuration arguments** on `initialize`:
|
174
|
+
|
175
|
+
```rb
|
176
|
+
MyClass.new(some_dep: some_dep_instance, some_config: true).call
|
65
177
|
```
|
66
178
|
|
67
|
-
|
68
|
-
pass them into the container - it will automatically instantiate new ones:
|
179
|
+
`Injectable` instantiates **dependencies that you have declared with the `dependency` macro** for you and passes them to `initialize`, so if you don't want to override those you don't even need to instantiate the class and you can use the provided class method **`#call` shortcut**:
|
69
180
|
|
70
|
-
```
|
71
|
-
|
72
|
-
user = container.get(:user)
|
73
|
-
user_service = container.get(:user_service)
|
181
|
+
```rb
|
182
|
+
Myclass.call # This is calling `initialize` under the hood
|
74
183
|
```
|
75
184
|
|
76
|
-
|
77
|
-
allowing the registration of classes whose instances perform that role. An
|
78
|
-
implementation can be registered on a single container itself as a "one off":
|
185
|
+
If you need to override dependencies or configuration options, just call `new` yourself:
|
79
186
|
|
80
|
-
```
|
81
|
-
|
82
|
-
container.register_implementation(:facebook_service, DifferentFacebookService)
|
83
|
-
user_service = container.get(:user_service)
|
84
|
-
# `user_service`'s facebook_service will be an instance of DifferentFacebookService
|
187
|
+
```rb
|
188
|
+
Myclass.new(some_dep: Override.new, some_config: false).call
|
85
189
|
```
|
86
190
|
|
87
|
-
|
191
|
+
If you do that, **any dependency that you didn't pass will be injected by `Injectable`**.
|
192
|
+
Notice that **configuration arguments**, which are declared with `#initialize_with` behave in the exact same way.
|
88
193
|
|
89
|
-
|
90
|
-
Injectable.configure do |config|
|
91
|
-
config.register_implementation(:facebook_service, DifferentFacebookService)
|
92
|
-
end
|
194
|
+
### #3 Keyword arguments
|
93
195
|
|
94
|
-
|
95
|
-
user_service = container.get(:user_service)
|
96
|
-
# `user_service`'s facebook_service will be an instance of DifferentFacebookService
|
97
|
-
```
|
196
|
+
Both `#initialize` and `#call` take **keyword arguments**.
|
98
197
|
|
99
|
-
|
100
|
-
they will be used are as follows: Check for an already instantiated instance in
|
101
|
-
the container. If one or both exist, take the first. If none exist, then attempt to
|
102
|
-
resolve the dependency with each registered role until one succeeds. An example:
|
198
|
+
### #4 Readers
|
103
199
|
|
104
|
-
|
105
|
-
module Portable
|
106
|
-
def charge
|
107
|
-
# Some logic here.
|
108
|
-
end
|
109
|
-
end
|
200
|
+
All `Injectable` macros define reader methods for you, that's why you define `#call` without arguments, because **you access everything you declare via reader methods**.
|
110
201
|
|
111
|
-
|
112
|
-
include Injectable
|
113
|
-
include Portable
|
114
|
-
end
|
202
|
+
## The `#dependency` macro
|
115
203
|
|
116
|
-
|
117
|
-
|
118
|
-
|
204
|
+
This is the main reason why you want to use this library in the first place.
|
205
|
+
|
206
|
+
There are several ways of declaring a `#dependency`:
|
207
|
+
|
208
|
+
### Bare dependency name
|
209
|
+
|
210
|
+
```rb
|
211
|
+
class ReportPdfRenderer
|
212
|
+
include Injectable
|
213
|
+
|
214
|
+
dependency :some_dependency
|
119
215
|
end
|
216
|
+
```
|
217
|
+
|
218
|
+
1. `Injectable` first tries to find the `SomeDependency` constant in `ReportPdfRenderer`namespace.
|
219
|
+
2. If it doesn't find it, then tries without namespace (`::SomeDependency`).
|
220
|
+
|
221
|
+
Notice that this happens **at runtime**, not when defining your class.
|
120
222
|
|
121
|
-
class
|
223
|
+
### Explicit, inline class:
|
224
|
+
|
225
|
+
```rb
|
226
|
+
class MyInjectable
|
122
227
|
include Injectable
|
123
|
-
|
228
|
+
|
229
|
+
dependency :client, class: Aws::S3::Client
|
230
|
+
dependency :parser, class: VeryLongClassNameForMyParser
|
124
231
|
end
|
232
|
+
```
|
233
|
+
|
234
|
+
Nothing fancy here, you are explicitly telling `Injectable` which class to instantiate for you.
|
235
|
+
|
236
|
+
You will want to use this style for example if the class is namespaced somewhere else or if you want a different name other than the class', like for example if it's too long.
|
125
237
|
|
126
|
-
|
127
|
-
|
238
|
+
Notice that this approach sets the class when ruby interprets the class, **not at runtime**.
|
239
|
+
|
240
|
+
### With a block:
|
241
|
+
|
242
|
+
```rb
|
243
|
+
dependency :complex_client do
|
244
|
+
instance = ThirdPartyLib.new(:foo, bar: 'goo')
|
245
|
+
instance.set_config(:name, 'value')
|
246
|
+
instance
|
128
247
|
end
|
248
|
+
```
|
249
|
+
|
250
|
+
It's important to understand that `Injectable` won't call `#new` on whatever you return from this block.
|
251
|
+
|
252
|
+
You probably want to use this when your dependency has a complex setup. We use it a lot when wrapping third party libraries which aren't reused elsewhere.
|
253
|
+
|
254
|
+
If you want to wrap a third party library and you need to reuse it, then we recommend that you write a specific `Injectable` class for it, so it adheres to its principles and is easier to use.
|
255
|
+
|
256
|
+
### `#dependency` options
|
257
|
+
|
258
|
+
#### `:with`
|
129
259
|
|
130
|
-
|
131
|
-
application = container.get(:application)
|
132
|
-
application.portable #=> Returns a new Phone.
|
260
|
+
If the dependency takes arguments, you can set them with :with
|
133
261
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
262
|
+
```rb
|
263
|
+
# Arrays will be splatted: WithNormalArguments.new(1, 2, 3)
|
264
|
+
dependency :with_normal_arguments, with: [1, 2, 3]
|
265
|
+
# Hashes will be passed as-is: WithKeywordArguments.new(foo: 'bar)
|
266
|
+
dependency :with_keyword_arguments, with: { foo: 'bar' }
|
138
267
|
```
|
139
268
|
|
140
|
-
|
269
|
+
### `:depends_on`
|
141
270
|
|
142
|
-
|
143
|
-
------------------------
|
271
|
+
It allows you to share **memoized instances** of dependencies and supports both a single dependency or multiples as an Array:
|
144
272
|
|
145
|
-
|
273
|
+
```rb
|
274
|
+
dependency :client # this will be instantiated just once and will be shared
|
275
|
+
dependency :reporter, depends_on: :client
|
276
|
+
dependency :mailer, depends_on: %i[client reporter]
|
277
|
+
```
|
146
278
|
|
147
|
-
|
148
|
-
|
149
|
-
|
279
|
+
Dependencies of dependencies will be passed as keyword arguments using the same name they were declared with. In the example above, `Injectable` will instantiate a `Mailer` class passing `{ client: client, reporter: reporter }` to `#initialize`.
|
280
|
+
|
281
|
+
If you have a dependency that is defined with a block which also depends_on other dependencies, you'll receive those as keyword arguments:
|
282
|
+
|
283
|
+
```rb
|
284
|
+
dependency :my_dependency, depends_on: :client do |client:|
|
285
|
+
MyDependency.new(client)
|
150
286
|
end
|
287
|
+
```
|
151
288
|
|
152
|
-
|
153
|
-
include Injectable
|
289
|
+
### `:call`
|
154
290
|
|
155
|
-
|
156
|
-
|
157
|
-
|
291
|
+
Sometimes you have a class that doesn't adhere to `Injectable` principles:
|
292
|
+
|
293
|
+
```rb
|
294
|
+
dependency :renderer
|
295
|
+
|
296
|
+
def call
|
297
|
+
renderer.render # this class does not respond to `call`
|
158
298
|
end
|
299
|
+
```
|
159
300
|
|
160
|
-
|
161
|
-
include Injectable
|
162
|
-
dependencies :user, :facebook_service
|
301
|
+
`:call` is a way of wrapping such dependency so it behaves like an `Injectable`:
|
163
302
|
|
164
|
-
|
165
|
-
|
166
|
-
|
303
|
+
```rb
|
304
|
+
dependency :renderer, call: :render
|
305
|
+
|
306
|
+
def call
|
307
|
+
renderer.call
|
167
308
|
end
|
168
309
|
```
|
169
310
|
|
170
|
-
|
171
|
-
and then ask the container to do all the other work. This is more closely
|
172
|
-
related to what one might do in a real application.
|
311
|
+
It's important to understand that **you can mix and match all dependency configurations and options** described above.
|
173
312
|
|
174
|
-
|
175
|
-
class UsersController < ApplicationController
|
313
|
+
## `#initialize_with` macro
|
176
314
|
|
177
|
-
|
178
|
-
container.get(:user_service).post_to_wall(params[:message])
|
179
|
-
respond_with container.get(:user)
|
180
|
-
end
|
315
|
+
This macro is meant for **configuration arguments** passed to `initialize`:
|
181
316
|
|
182
|
-
|
317
|
+
```rb
|
318
|
+
initialize_with :debug, default: false
|
319
|
+
```
|
183
320
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
321
|
+
If you don't pass the `:default` option the argument will be required.
|
322
|
+
|
323
|
+
## `#argument` macro
|
324
|
+
|
325
|
+
`#argument` allows you to define **runtime arguments** passed to `#call`
|
326
|
+
|
327
|
+
```rb
|
328
|
+
argument :browser, default: 'Unknown'
|
188
329
|
```
|
189
330
|
|
190
|
-
|
331
|
+
If you don't pass the `:default` option the argument will be required.
|
332
|
+
|
333
|
+
|
334
|
+
## Development
|
335
|
+
|
336
|
+
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.
|
337
|
+
|
338
|
+
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).
|
339
|
+
|
340
|
+
## Contributing
|
341
|
+
|
342
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rubiconmd/injectable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
343
|
+
|
344
|
+
## License
|
345
|
+
|
346
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
347
|
+
|
348
|
+
## Code of Conduct
|
191
349
|
|
192
|
-
|
193
|
-
a copy of this software and associated documentation files (the
|
194
|
-
"Software"), to deal in the Software without restriction, including
|
195
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
196
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
197
|
-
permit persons to whom the Software is furnished to do so, subject to
|
198
|
-
the following conditions:
|
350
|
+
Everyone interacting in the Injectable project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
199
351
|
|
200
|
-
|
201
|
-
included in all copies or substantial portions of the Software.
|
352
|
+
## Credits
|
202
353
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
209
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
354
|
+
- [RubiconMD](https://github.com/rubiconmd) allowed extracting this gem from its codebase and release it as open source.
|
355
|
+
- [Durran Jordan](https://github.com/durran) allowed the usage of the gem name at rubygems.org.
|
356
|
+
- [David Marchante](https://github.com/iovis9) brainstormed the `initialize`/`call` approach, did all code reviews and provided lots of insightful feedback and suggestions. He also wrote the inline documentation.
|
357
|
+
- [Julio Antequera](https://github.com/jantequera), [Jimmi Carney](https://github.com/ayoformayo) and [Anthony Rocco](https://github.com/amrocco) had the patience to use it and report many bugs. Also most of the features in this gem came up when reviewing their usage of it. Anthony also made the effort of extracting the code from RubiconMD's codebase.
|
358
|
+
- [Rodrigo Álvarez](https://github.com/Papipo) had the idea for the DSL and actually wrote the library.
|