codependent 0.2
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 +51 -0
- data/.rubocop.yml +17 -0
- data/.rubocop_todo.yml +18 -0
- data/.ruby-version +1 -0
- data/Changelog.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +63 -0
- data/LICENSE +21 -0
- data/README.md +170 -0
- data/Rakefile +11 -0
- data/codependent.gemspec +11 -0
- data/lib/codependent/container.rb +60 -0
- data/lib/codependent/injectable.rb +39 -0
- data/lib/codependent/injectable_builder.rb +67 -0
- data/lib/codependent/manager.rb +69 -0
- data/lib/codependent/resolvers/deferred_type_resolver.rb +21 -0
- data/lib/codependent/resolvers/eager_type_resolver.rb +17 -0
- data/lib/codependent/resolvers/provider_resolver.rb +9 -0
- data/lib/codependent/resolvers/root_resolver.rb +84 -0
- data/lib/codependent/resolvers/value_resolver.rb +9 -0
- data/lib/codependent/validators/constructor_injection_validator.rb +45 -0
- data/lib/codependent/validators/provider_validator.rb +11 -0
- data/lib/codependent/validators/setter_injection_validator.rb +26 -0
- data/lib/codependent/validators/value_validator.rb +18 -0
- data/lib/codependent.rb +15 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9346a0d2621442d6fba3e69e244895fb5a4c22dc
|
4
|
+
data.tar.gz: ff00bf9084921b52e50dbeba357ff06a8eadcaa7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4d9d0327496277dad897ce11b73abd6548ed3f97d107d17e1bc844958e35bb42191ecd5321c17a6c3c9ecd2ab04f5d4888de3224bcdb3026910f0f283fa33765
|
7
|
+
data.tar.gz: 3a8a641f957db178008be0848fde2813c10c15430cd79d3fa970962d95b5426efeaf3dda1feca61031f5c9ebe7c1122def7d9cf4c756816ce6f0e3bdb9d3384d
|
data/.gitignore
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
.DS_Store
|
13
|
+
|
14
|
+
# Used by dotenv library to load environment variables.
|
15
|
+
# .env
|
16
|
+
|
17
|
+
## Specific to RubyMotion:
|
18
|
+
.dat*
|
19
|
+
.repl_history
|
20
|
+
build/
|
21
|
+
*.bridgesupport
|
22
|
+
build-iPhoneOS/
|
23
|
+
build-iPhoneSimulator/
|
24
|
+
|
25
|
+
## Specific to RubyMotion (use of CocoaPods):
|
26
|
+
#
|
27
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
28
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
29
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
30
|
+
#
|
31
|
+
# vendor/Pods/
|
32
|
+
|
33
|
+
## Documentation cache and generated files:
|
34
|
+
/.yardoc/
|
35
|
+
/_yardoc/
|
36
|
+
/doc/
|
37
|
+
/rdoc/
|
38
|
+
|
39
|
+
## Environment normalization:
|
40
|
+
/.bundle/
|
41
|
+
/vendor/bundle
|
42
|
+
/lib/bundler/man/
|
43
|
+
|
44
|
+
# for a library or gem, you might want to ignore these files since the code is
|
45
|
+
# intended to run in multiple environments; otherwise, check them in:
|
46
|
+
# Gemfile.lock
|
47
|
+
# .ruby-version
|
48
|
+
# .ruby-gemset
|
49
|
+
|
50
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
51
|
+
.rvmrc
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: 2.3
|
5
|
+
Exclude:
|
6
|
+
- '**/Rakefile'
|
7
|
+
- '**/**/spec_helper.rb'
|
8
|
+
- '**/*.gemspec'
|
9
|
+
|
10
|
+
Documentation:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Style/FrozenStringLiteralComment:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Style/LambdaCall:
|
17
|
+
Enabled: false
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2017-02-23 08:14:38 -0800 using RuboCop version 0.40.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 10
|
10
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
|
11
|
+
# URISchemes: http, https
|
12
|
+
Metrics/LineLength:
|
13
|
+
Max: 120
|
14
|
+
|
15
|
+
# Offense count: 1
|
16
|
+
# Configuration parameters: CountComments.
|
17
|
+
Metrics/MethodLength:
|
18
|
+
Max: 11
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/Changelog.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
ast (2.3.0)
|
5
|
+
byebug (9.0.5)
|
6
|
+
coderay (1.1.1)
|
7
|
+
diff-lcs (1.3)
|
8
|
+
docile (1.1.5)
|
9
|
+
json (2.0.2)
|
10
|
+
method_source (0.8.2)
|
11
|
+
parser (2.3.1.2)
|
12
|
+
ast (~> 2.2)
|
13
|
+
powerpack (0.1.1)
|
14
|
+
pry (0.10.4)
|
15
|
+
coderay (~> 1.1.0)
|
16
|
+
method_source (~> 0.8.1)
|
17
|
+
slop (~> 3.4)
|
18
|
+
pry-byebug (3.4.0)
|
19
|
+
byebug (~> 9.0)
|
20
|
+
pry (~> 0.10)
|
21
|
+
rainbow (2.1.0)
|
22
|
+
rake (11.2.2)
|
23
|
+
rspec (3.5.0)
|
24
|
+
rspec-core (~> 3.5.0)
|
25
|
+
rspec-expectations (~> 3.5.0)
|
26
|
+
rspec-mocks (~> 3.5.0)
|
27
|
+
rspec-core (3.5.4)
|
28
|
+
rspec-support (~> 3.5.0)
|
29
|
+
rspec-expectations (3.5.0)
|
30
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
31
|
+
rspec-support (~> 3.5.0)
|
32
|
+
rspec-mocks (3.5.0)
|
33
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
+
rspec-support (~> 3.5.0)
|
35
|
+
rspec-support (3.5.0)
|
36
|
+
rubocop (0.40.0)
|
37
|
+
parser (>= 2.3.1.0, < 3.0)
|
38
|
+
powerpack (~> 0.1)
|
39
|
+
rainbow (>= 1.99.1, < 3.0)
|
40
|
+
ruby-progressbar (~> 1.7)
|
41
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
42
|
+
ruby-progressbar (1.8.1)
|
43
|
+
simplecov (0.12.0)
|
44
|
+
docile (~> 1.1.0)
|
45
|
+
json (>= 1.8, < 3)
|
46
|
+
simplecov-html (~> 0.10.0)
|
47
|
+
simplecov-html (0.10.0)
|
48
|
+
slop (3.6.0)
|
49
|
+
unicode-display_width (1.1.0)
|
50
|
+
|
51
|
+
PLATFORMS
|
52
|
+
ruby
|
53
|
+
|
54
|
+
DEPENDENCIES
|
55
|
+
pry
|
56
|
+
pry-byebug
|
57
|
+
rake
|
58
|
+
rspec
|
59
|
+
rubocop (~> 0.40.0)
|
60
|
+
simplecov
|
61
|
+
|
62
|
+
BUNDLED WITH
|
63
|
+
1.14.3
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) [year] [fullname]
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# Codependent
|
2
|
+
|
3
|
+
Codependent is a simple, lightweight dependency injection library for Ruby.
|
4
|
+
|
5
|
+
[](https://badge.fury.io/rb/Codependent)
|
6
|
+
|
7
|
+
## Codependent by Example
|
8
|
+
|
9
|
+
### Installation
|
10
|
+
|
11
|
+
```
|
12
|
+
gem install codependent
|
13
|
+
```
|
14
|
+
|
15
|
+
...or add Codependent to your Gemfile:
|
16
|
+
|
17
|
+
```
|
18
|
+
source 'https://rubygems.org'
|
19
|
+
|
20
|
+
gem 'codependent'
|
21
|
+
```
|
22
|
+
|
23
|
+
### Basic Usage
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# Let's define some injectable types.
|
27
|
+
Logger = Struct.new(:writer)
|
28
|
+
|
29
|
+
class UserRepository
|
30
|
+
def initialize(logger:)
|
31
|
+
@logger = logger
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :logger
|
35
|
+
end
|
36
|
+
|
37
|
+
class AccountRepository
|
38
|
+
attr_accessor :logger, :user_repository
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create & configure a container by passing a block to the constructor:
|
42
|
+
container = Codependent::Container.new do
|
43
|
+
# Transient or "instance" dependencies are configured with a simple
|
44
|
+
# DSL.
|
45
|
+
instance :logger do
|
46
|
+
# You can inject simple values:
|
47
|
+
from_value Logger.new
|
48
|
+
end
|
49
|
+
|
50
|
+
# You can inject via the constructor:
|
51
|
+
singleton :user_repository do
|
52
|
+
from_type UserRepository
|
53
|
+
depends_on :logger
|
54
|
+
end
|
55
|
+
|
56
|
+
# Codependent will pass the dependencies to the type's constructor.
|
57
|
+
# The constructor needs to have keyword arguments that match the
|
58
|
+
# type's dependencies. Codependent will raise an error if the keyword
|
59
|
+
# arguments don't match the dependencies.
|
60
|
+
|
61
|
+
# You can also inject via setters, useful to handle circular dependencies:
|
62
|
+
singleton :account_repository do
|
63
|
+
from_type AccountRepository
|
64
|
+
inject_setters
|
65
|
+
|
66
|
+
# Multiple dependencies are supported:
|
67
|
+
depends_on :logger, :user_repository
|
68
|
+
end
|
69
|
+
|
70
|
+
# Codependent will call the setters to fill in the dependencies, and will
|
71
|
+
# raise an error if there aren't setters for each dependency.
|
72
|
+
|
73
|
+
# If you have an object with complex constructor requirements, you can
|
74
|
+
# use a provider function:
|
75
|
+
instance :db_connection do
|
76
|
+
# Codependent will pass the dependencies to the provider block in a Hash.
|
77
|
+
from_provider do |deps|
|
78
|
+
DB.open(deps[:connection_string])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# You can disable Codependent's type checking:
|
83
|
+
instance :unchecked_dependency
|
84
|
+
from_type MetaprogammingIsCool
|
85
|
+
skip_checks
|
86
|
+
end
|
87
|
+
|
88
|
+
# Now that we've got our IoC container all wired up, we can ask it to give
|
89
|
+
# us instances of our types:
|
90
|
+
a_logger = container.resolve :logger # => a new instance of Logger
|
91
|
+
a_user_repo = container.resolve :user_repository # => An instance of UserRepository.
|
92
|
+
an_account_repo = container.resolve :account_repository # => An instance of Account Repository
|
93
|
+
|
94
|
+
# The user repository is defined as a singleton, so the references should be
|
95
|
+
# pointing at the same object.
|
96
|
+
expect(an_account_repo.user_repository).to eq(a_user_repo)
|
97
|
+
|
98
|
+
# The logger is a new instance each time it's resolved, so these won't be the
|
99
|
+
# same reference.
|
100
|
+
expect(a_user_repo.logger).not_to eq(a_logger)
|
101
|
+
```
|
102
|
+
|
103
|
+
## Advanced Usage
|
104
|
+
|
105
|
+
### Managing Containers
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# If you don't want to deal with managing the container yourself, Codependent
|
109
|
+
# can do it for you.
|
110
|
+
|
111
|
+
# You can create globally-accessible containers by giving the manager a name
|
112
|
+
# and configuring the container:
|
113
|
+
Codependent::Manager.container :my_container do
|
114
|
+
# This block is passed to a new container instance and uses the same syntax
|
115
|
+
# we've already seen.
|
116
|
+
instance :logger do
|
117
|
+
with_constructor { Logger.new }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Access the container through the manager:
|
122
|
+
a_logger = Codependent::Manager[:my_container].resolve(:logger)
|
123
|
+
|
124
|
+
# Test to see if a container is defined:
|
125
|
+
Codependent::Manager.container?(:my_container) # => True
|
126
|
+
|
127
|
+
# You can reset a container you've defined back to it's original configuration:
|
128
|
+
Codependent::Manager.reset_container!(:my_container)
|
129
|
+
|
130
|
+
# ...or just remove all of them:
|
131
|
+
Codependent::Manager.reset!
|
132
|
+
```
|
133
|
+
|
134
|
+
### Class Definition Syntax
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
# Not everyone wants to configure the container up front. You can define your
|
138
|
+
# dependencies in your class definitions with the Codependent Helper.
|
139
|
+
class UserRepository
|
140
|
+
extend Codependent::Helper
|
141
|
+
|
142
|
+
# Tell Codependent what to call this injectable and how it should be resolved:
|
143
|
+
singleton :user_repository do
|
144
|
+
# The syntax here is the same as before.
|
145
|
+
with_constructor { self.new }
|
146
|
+
depends_on :logger
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Logger
|
151
|
+
extend Codependent::Helper
|
152
|
+
|
153
|
+
# You can also specify the managed container into which this type should be
|
154
|
+
# defined:
|
155
|
+
instance :logger, in_container: :my_container do
|
156
|
+
with_constructor { self.new }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
## Developing Codependent
|
162
|
+
|
163
|
+
### Building from source
|
164
|
+
|
165
|
+
1. Clone the repo.
|
166
|
+
2. Install dependencies: `bundle install`
|
167
|
+
3. Run the tests: `bundle exec rake ci`
|
168
|
+
4. Build a local copy of the gem: `gem build codependent.gemspec`
|
169
|
+
5. Install the gem locally: `gem install ./Codependent-0.1.gem`
|
170
|
+
6. Don't forget to version-bump the gemspec before publishing!
|
data/Rakefile
ADDED
data/codependent.gemspec
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'codependent'
|
3
|
+
s.version = '0.2'
|
4
|
+
s.date = '2017-02-23'
|
5
|
+
s.summary = "A simple dependency injection library for Ruby."
|
6
|
+
s.authors = ["Joshua Tompkins"]
|
7
|
+
s.email = 'josh@joshtompkins.com'
|
8
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
9
|
+
s.homepage = 'https://github.com/jtompkins/codependent'
|
10
|
+
s.license = 'MIT'
|
11
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'codependent/injectable'
|
2
|
+
require 'codependent/resolvers/root_resolver'
|
3
|
+
|
4
|
+
module Codependent
|
5
|
+
class Container
|
6
|
+
CONFIG_BLOCK_MISSING_ERROR = 'You must provide a config block.'.freeze
|
7
|
+
|
8
|
+
def initialize(&block)
|
9
|
+
@injectables = {}
|
10
|
+
|
11
|
+
instance_eval(&block) if block
|
12
|
+
end
|
13
|
+
|
14
|
+
def instance(id, &config_block)
|
15
|
+
validate_config_arguments(config_block)
|
16
|
+
|
17
|
+
add_injectable!(id, :instance, config_block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def singleton(id, &config_block)
|
21
|
+
validate_config_arguments(config_block)
|
22
|
+
|
23
|
+
add_injectable!(id, :singleton, config_block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def injectable(id)
|
27
|
+
injectables[id]
|
28
|
+
end
|
29
|
+
|
30
|
+
def injectable?(id)
|
31
|
+
injectables.key?(id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def resolve(id)
|
35
|
+
return unless injectable?(id)
|
36
|
+
|
37
|
+
resolver.resolve(id)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :injectables, :singleton_values
|
43
|
+
|
44
|
+
def resolver
|
45
|
+
Resolvers::RootResolver.new(injectables)
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_config_arguments(config_block)
|
49
|
+
raise ArgumentError, CONFIG_BLOCK_MISSING_ERROR unless config_block
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_injectable!(id, type, config_block)
|
53
|
+
builder = Codependent::InjectableBuilder.new(type)
|
54
|
+
|
55
|
+
builder.instance_eval(&config_block)
|
56
|
+
|
57
|
+
injectables[id] = builder.build
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Codependent
|
2
|
+
class Injectable
|
3
|
+
def initialize(type, dependencies, state, resolver)
|
4
|
+
@type = type
|
5
|
+
@dependencies = dependencies
|
6
|
+
@state = state
|
7
|
+
@resolver = resolver
|
8
|
+
@singleton_value = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :dependencies, :state, :resolver
|
12
|
+
|
13
|
+
def depends_on?(dependency_id)
|
14
|
+
@dependencies.include?(dependency_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def singleton?
|
18
|
+
@type == :singleton
|
19
|
+
end
|
20
|
+
|
21
|
+
def instance?
|
22
|
+
@type == :instance
|
23
|
+
end
|
24
|
+
|
25
|
+
def value(dependencies)
|
26
|
+
singleton? ? singleton_value(dependencies) : instance_value(dependencies)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def singleton_value(dependencies)
|
32
|
+
@singleton_value ||= instance_value(dependencies)
|
33
|
+
end
|
34
|
+
|
35
|
+
def instance_value(dependencies)
|
36
|
+
resolver.new.(state, dependencies)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'codependent/injectable'
|
2
|
+
|
3
|
+
require 'codependent/validators/value_validator'
|
4
|
+
require 'codependent/validators/provider_validator'
|
5
|
+
require 'codependent/validators/constructor_injection_validator'
|
6
|
+
require 'codependent/validators/setter_injection_validator'
|
7
|
+
|
8
|
+
require 'codependent/resolvers/eager_type_resolver'
|
9
|
+
require 'codependent/resolvers/deferred_type_resolver'
|
10
|
+
require 'codependent/resolvers/provider_resolver'
|
11
|
+
require 'codependent/resolvers/value_resolver'
|
12
|
+
|
13
|
+
module Codependent
|
14
|
+
class InjectableBuilder
|
15
|
+
def initialize(type)
|
16
|
+
@type = type
|
17
|
+
@dependencies = []
|
18
|
+
@state = {}
|
19
|
+
@skip_checks = false
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :type, :dependencies, :state, :validator, :resolver
|
23
|
+
|
24
|
+
def from_value(value)
|
25
|
+
@state = { value: value }
|
26
|
+
@validator = Validators::ValueValidator
|
27
|
+
@resolver = Resolvers::ValueResolver
|
28
|
+
end
|
29
|
+
|
30
|
+
def from_provider(&block)
|
31
|
+
@state = { provider: block }
|
32
|
+
@validator = Validators::ProviderValidator
|
33
|
+
@resolver = Resolvers::ProviderResolver
|
34
|
+
end
|
35
|
+
|
36
|
+
def from_type(klass, additional_args = nil)
|
37
|
+
@state = {
|
38
|
+
type: klass,
|
39
|
+
additional_args: additional_args
|
40
|
+
}
|
41
|
+
|
42
|
+
@validator = Validators::ConstructorInjectionValidator
|
43
|
+
@resolver = Resolvers::EagerTypeResolver
|
44
|
+
end
|
45
|
+
|
46
|
+
def inject_setters
|
47
|
+
@validator = Validators::SetterInjectionValidator
|
48
|
+
@resolver = Resolvers::DeferredTypeResolver
|
49
|
+
end
|
50
|
+
|
51
|
+
def skip_checks
|
52
|
+
@skip_checks = true
|
53
|
+
end
|
54
|
+
|
55
|
+
def depends_on(*dependencies)
|
56
|
+
@dependencies.concat(dependencies)
|
57
|
+
end
|
58
|
+
|
59
|
+
def build
|
60
|
+
return unless @validator
|
61
|
+
|
62
|
+
@validator.new.(@type, @state, @dependencies) unless @skip_checks
|
63
|
+
|
64
|
+
Injectable.new(@type, @dependencies, @state, @resolver)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'codependent/container'
|
2
|
+
|
3
|
+
module Codependent
|
4
|
+
class Manager
|
5
|
+
class << self
|
6
|
+
@instance = nil
|
7
|
+
|
8
|
+
def instance
|
9
|
+
@instance ||= Manager.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset!
|
13
|
+
@instance = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method, *args, &block)
|
17
|
+
super unless instance.respond_to?(method)
|
18
|
+
|
19
|
+
@instance.send(method, *args, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def container?(container_id)
|
24
|
+
containers.key?(container_id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset_container!(container_id)
|
28
|
+
return unless container?(container_id)
|
29
|
+
|
30
|
+
config_block = containers[container_id][:config]
|
31
|
+
containers[container_id] = build_container(config_block)
|
32
|
+
|
33
|
+
get_container(container_id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def container(container_id, &config_block)
|
37
|
+
unless container?(container_id)
|
38
|
+
containers[container_id] = build_container(config_block)
|
39
|
+
end
|
40
|
+
|
41
|
+
get_container(container_id)
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](container_id)
|
45
|
+
get_container(container_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
def global
|
49
|
+
get_container(:global)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def build_container(config_block = nil)
|
55
|
+
{
|
56
|
+
container: Codependent::Container.new(&config_block),
|
57
|
+
config: config_block
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_container(container_id)
|
62
|
+
containers[container_id][:container]
|
63
|
+
end
|
64
|
+
|
65
|
+
def containers
|
66
|
+
@containers ||= { global: build_container }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Codependent
|
2
|
+
module Resolvers
|
3
|
+
class DeferredTypeResolver
|
4
|
+
def call(state, _)
|
5
|
+
state[:type].new
|
6
|
+
end
|
7
|
+
|
8
|
+
def apply(value, dependencies)
|
9
|
+
dependencies.each do |dep_id, dep|
|
10
|
+
value.send(to_setter(dep_id), dep)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def to_setter(id)
|
17
|
+
"#{id}=".to_sym
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Codependent
|
2
|
+
module Resolvers
|
3
|
+
class EagerTypeResolver
|
4
|
+
def call(state, dependency_hash)
|
5
|
+
constructor_args = dependency_hash.merge(state[:additional_args] || {})
|
6
|
+
|
7
|
+
type = state[:type]
|
8
|
+
|
9
|
+
if !constructor_args.empty?
|
10
|
+
type.new(**constructor_args)
|
11
|
+
else
|
12
|
+
type.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'codependent/resolvers/deferred_type_resolver'
|
2
|
+
|
3
|
+
module Codependent
|
4
|
+
module Resolvers
|
5
|
+
class RootResolver
|
6
|
+
def initialize(injectables)
|
7
|
+
@injectables = injectables
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :injectables
|
11
|
+
|
12
|
+
def resolve(id)
|
13
|
+
dependencies = build_dependency_graph(id)
|
14
|
+
|
15
|
+
resolved = resolve_eager_dependencies(dependencies)
|
16
|
+
resolve_deferred_dependencies!(resolved)
|
17
|
+
|
18
|
+
resolved[id]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def deferred?(injectable)
|
24
|
+
injectable.resolver == DeferredTypeResolver
|
25
|
+
end
|
26
|
+
|
27
|
+
def resolve_eager_dependencies(injectable_ids)
|
28
|
+
injectable_ids.reduce({}) do |acc, id|
|
29
|
+
acc.merge(id => resolve_value(id, acc))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def resolve_deferred_dependencies!(available_dependencies)
|
34
|
+
available_dependencies.keys.each do |id|
|
35
|
+
injectable = injectables[id]
|
36
|
+
|
37
|
+
next unless deferred?(injectable)
|
38
|
+
|
39
|
+
resolver = injectable.resolver.new
|
40
|
+
|
41
|
+
resolver.apply(
|
42
|
+
available_dependencies[id],
|
43
|
+
required_dependencies(available_dependencies, injectable)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def resolve_value(id, available_dependencies)
|
49
|
+
injectable = injectables[id]
|
50
|
+
dependencies = {}
|
51
|
+
|
52
|
+
unless deferred?(injectable)
|
53
|
+
dependencies = required_dependencies(
|
54
|
+
available_dependencies,
|
55
|
+
injectable
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
injectable.value(dependencies)
|
60
|
+
end
|
61
|
+
|
62
|
+
def required_dependencies(all_dependencies, injectable)
|
63
|
+
all_dependencies.select { |k, _| injectable.dependencies.include?(k) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_dependency_graph(id)
|
67
|
+
stack = [id]
|
68
|
+
dependencies = [id]
|
69
|
+
|
70
|
+
until stack.empty?
|
71
|
+
current = injectables[stack.pop]
|
72
|
+
|
73
|
+
current.dependencies.each do |dep_id|
|
74
|
+
next if dependencies.include?(dep_id)
|
75
|
+
dependencies.unshift(dep_id)
|
76
|
+
stack.push(dep_id)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
dependencies
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Codependent
|
2
|
+
module Validators
|
3
|
+
class ConstructorInjectionValidator
|
4
|
+
MISSING_TYPE_ERROR = 'Constructor injection requires a type to be specified.'.freeze
|
5
|
+
MISSING_DEPENDENCY_KEYWORDS_ERROR = 'All dependencies must appear as keyword arguments to the constructor.'.freeze
|
6
|
+
NO_ARGS_WITH_DEPENDENCIES_ERROR = 'Constructor injection requires the constructor to receive arguments.'.freeze
|
7
|
+
|
8
|
+
def call(_, state, dependencies)
|
9
|
+
raise MISSING_TYPE_ERROR unless state[:type]
|
10
|
+
|
11
|
+
return unless dependencies.count > 0
|
12
|
+
|
13
|
+
validate_constructor_params(state[:type], dependencies)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def all_keywords?(params)
|
19
|
+
params.all? { |p| p[0] == :key || p[0] == :keyreq }
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract_param_names(params)
|
23
|
+
params.map { |p| p[1] }
|
24
|
+
end
|
25
|
+
|
26
|
+
def params_for_all_dependencies?(dependencies, parameter_names)
|
27
|
+
(dependencies - parameter_names).empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_constructor_params(klass, dependencies)
|
31
|
+
params = klass.instance_method(:initialize).parameters
|
32
|
+
|
33
|
+
raise NO_ARGS_WITH_DEPENDENCIES_ERROR if params.count == 0
|
34
|
+
|
35
|
+
return unless all_keywords?(params)
|
36
|
+
|
37
|
+
parameter_names = extract_param_names(params)
|
38
|
+
|
39
|
+
return if params_for_all_dependencies?(dependencies, parameter_names)
|
40
|
+
|
41
|
+
raise MISSING_DEPENDENCY_KEYWORDS_ERROR
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Codependent
|
2
|
+
module Validators
|
3
|
+
class SetterInjectionValidator
|
4
|
+
MISSING_TYPE_ERROR = 'Setter injection requires a type to be specified.'.freeze
|
5
|
+
MISSING_ACCESSOR_KEYWORDS_ERROR = 'All dependencies must appear as accessors on the class.'.freeze
|
6
|
+
|
7
|
+
def call(_, state, dependencies)
|
8
|
+
raise MISSING_TYPE_ERROR unless state[:type]
|
9
|
+
|
10
|
+
return unless dependencies.count > 0
|
11
|
+
|
12
|
+
validate_setters(state[:type], dependencies)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate_setters(klass, dependencies)
|
18
|
+
dependencies.each do |dep_id|
|
19
|
+
unless klass.method_defined? "#{dep_id}=".to_sym
|
20
|
+
raise MISSING_ACCESSOR_KEYWORDS_ERROR
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Codependent
|
2
|
+
module Validators
|
3
|
+
class ValueValidator
|
4
|
+
SINGLETON_ERROR = 'Value injectables are only allowed on singletons.'.freeze
|
5
|
+
NIL_VALUE_ERROR = 'Value injectables must not be nil.'.freeze
|
6
|
+
NO_DEPENDENCIES_ERROR = 'Value injectables may not have dependencies'.freeze
|
7
|
+
|
8
|
+
def call(type, state, dependencies)
|
9
|
+
raise SINGLETON_ERROR unless type == :singleton
|
10
|
+
raise NIL_VALUE_ERROR unless state[:value]
|
11
|
+
|
12
|
+
no_dependencies = !dependencies || dependencies.count != 0
|
13
|
+
|
14
|
+
raise NO_DEPENDENCIES_ERROR if no_dependencies
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/codependent.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'codependent/manager'
|
2
|
+
require 'codependent/container'
|
3
|
+
require 'codependent/injectable'
|
4
|
+
require 'codependent/injectable_builder'
|
5
|
+
|
6
|
+
require 'codependent/resolvers/root_resolver'
|
7
|
+
require 'codependent/resolvers/eager_type_resolver'
|
8
|
+
require 'codependent/resolvers/deferred_type_resolver'
|
9
|
+
require 'codependent/resolvers/provider_resolver'
|
10
|
+
require 'codependent/resolvers/value_resolver'
|
11
|
+
|
12
|
+
require 'codependent/validators/constructor_injection_validator'
|
13
|
+
require 'codependent/validators/setter_injection_validator'
|
14
|
+
require 'codependent/validators/provider_validator'
|
15
|
+
require 'codependent/validators/value_validator'
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: codependent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joshua Tompkins
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-02-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: josh@joshtompkins.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".gitignore"
|
20
|
+
- ".rubocop.yml"
|
21
|
+
- ".rubocop_todo.yml"
|
22
|
+
- ".ruby-version"
|
23
|
+
- Changelog.md
|
24
|
+
- Gemfile
|
25
|
+
- Gemfile.lock
|
26
|
+
- LICENSE
|
27
|
+
- README.md
|
28
|
+
- Rakefile
|
29
|
+
- codependent.gemspec
|
30
|
+
- lib/codependent.rb
|
31
|
+
- lib/codependent/container.rb
|
32
|
+
- lib/codependent/injectable.rb
|
33
|
+
- lib/codependent/injectable_builder.rb
|
34
|
+
- lib/codependent/manager.rb
|
35
|
+
- lib/codependent/resolvers/deferred_type_resolver.rb
|
36
|
+
- lib/codependent/resolvers/eager_type_resolver.rb
|
37
|
+
- lib/codependent/resolvers/provider_resolver.rb
|
38
|
+
- lib/codependent/resolvers/root_resolver.rb
|
39
|
+
- lib/codependent/resolvers/value_resolver.rb
|
40
|
+
- lib/codependent/validators/constructor_injection_validator.rb
|
41
|
+
- lib/codependent/validators/provider_validator.rb
|
42
|
+
- lib/codependent/validators/setter_injection_validator.rb
|
43
|
+
- lib/codependent/validators/value_validator.rb
|
44
|
+
homepage: https://github.com/jtompkins/codependent
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.5.1
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: A simple dependency injection library for Ruby.
|
68
|
+
test_files: []
|