codependent 0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []