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 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
@@ -0,0 +1,5 @@
1
+ ## 0.1 - Initial Release
2
+
3
+ ### Features
4
+
5
+ * Initial Release
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'rspec'
5
+ gem 'pry'
6
+ gem 'pry-byebug'
7
+ gem 'rubocop', '~> 0.40.0', require: false
8
+ gem 'rake'
9
+ gem 'simplecov', require: false
10
+ end
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
+ [![Gem Version](https://badge.fury.io/rb/Codependent.png)](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
@@ -0,0 +1,11 @@
1
+ require 'rubocop/rake_task'
2
+
3
+ RuboCop::RakeTask.new
4
+
5
+ begin
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ rescue LoadError
9
+ end
10
+
11
+ task ci: [:spec, :rubocop]
@@ -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,9 @@
1
+ module Codependent
2
+ module Resolvers
3
+ class ProviderResolver
4
+ def call(state, dependency_hash)
5
+ state[:provider].call(dependency_hash)
6
+ end
7
+ end
8
+ end
9
+ 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,9 @@
1
+ module Codependent
2
+ module Resolvers
3
+ class ValueResolver
4
+ def call(state, _)
5
+ state[:value]
6
+ end
7
+ end
8
+ end
9
+ 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,11 @@
1
+ module Codependent
2
+ module Validators
3
+ class ProviderValidator
4
+ NO_BLOCK_ERROR = 'Provider injectables must have a block.'.freeze
5
+
6
+ def call(_, state, _)
7
+ raise NO_BLOCK_ERROR unless state[:provider]
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -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: []