dry-auto_inject 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 04bd4b08425b83733006cac8ef6612068304a720a63f8896e8f0369e1d26b0cc
4
+ data.tar.gz: 38b6073a686faa24ec5b628e70125ae97ea2c402403c410608acd57506a7482c
5
+ SHA512:
6
+ metadata.gz: bd8267844c208234397d5b8a62113bc5bc665daad2c3410d27342489b5894b24b92e6dec1df721028e5967a0f2f7c24465e52bac7b4e469ad0e6e98d65bb0c25
7
+ data.tar.gz: 64be14b7a6797e064690838ec867c0d2056101387e5a652cf20213ef40f5a86822420f50a42d8f85288bc66af30f710c05b2c8dde4e4ca0952b2d3cd71b0a3d5
@@ -0,0 +1,6 @@
1
+ engines:
2
+ rubocop:
3
+ enabled: true
4
+ ratings:
5
+ paths:
6
+ - lib/**
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /vendor/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,19 @@
1
+ # Generated by `rubocop --auto-gen-config`
2
+ inherit_from: .rubocop_todo.yml
3
+
4
+ Metrics/LineLength:
5
+ Max: 110
6
+
7
+ Lint/HandleExceptions:
8
+ Exclude:
9
+ - rakelib/*.rake
10
+
11
+ Style/LambdaCall:
12
+ EnforcedStyle: braces
13
+
14
+ Style/Documentation:
15
+ Enabled: false
16
+
17
+ Style/FileName:
18
+ Exclude:
19
+ - lib/dry-pipeline.rb
@@ -0,0 +1,6 @@
1
+ # This configuration was generated by `rubocop --auto-gen-config`
2
+ # on 2015-08-19 22:11:28 +0100 using RuboCop version 0.32.0.
3
+ # The point is for the user to remove these configuration records
4
+ # one by one as the offenses are removed from the code base.
5
+ # Note that changes in the inspected code, or installation of new
6
+ # versions of RuboCop, may require this file to be generated again.
@@ -0,0 +1,26 @@
1
+ language: ruby
2
+ cache: bundler
3
+ bundler_args: --without tools
4
+ after_success:
5
+ - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
6
+ rvm:
7
+ - 2.6.2
8
+ - 2.5.5
9
+ - 2.4.5
10
+ - 2.3.8
11
+ - jruby-9.2.6.0
12
+ - truffleruby
13
+ matrix:
14
+ allow_failures:
15
+ - rvm: truffleruby
16
+ env:
17
+ global:
18
+ - COVERAGE=true
19
+ notifications:
20
+ email: false
21
+ webhooks:
22
+ urls:
23
+ - https://webhooks.gitter.im/e/19098b4253a72c9796db
24
+ on_success: change # options: [always|never|change] default: always
25
+ on_failure: always # options: [always|never|change] default: always
26
+ on_start: false # default: false
@@ -0,0 +1,243 @@
1
+ # 0.6.1 / 2019-04-16
2
+
3
+ ### Fixed
4
+
5
+ - Allow explicit injection of falsey values (timriley in [#58](https://github.com/dry-rb/dry-auto_inject/pull/58))
6
+
7
+ [Compare v0.6.0...v0.6.1](https://github.com/dry-rb/dry-auto_inject/compare/v0.6.0...v0.6.1)
8
+
9
+ # 0.6.0 / 2018-11-29
10
+
11
+ ### Changed
12
+
13
+ - [BREAKING] 0.6.0 supports Ruby 2.3 and above. If you're on 2.3 keep in mind its EOL is scheduled at the end of March, 2019
14
+
15
+ ### Added
16
+
17
+ - Enhanced support for integrating with existing constructors. The kwargs strategy will now pass dependencies up to the next constructor if it accepts an arbitrary number of arguments with `*args`. Note that this change may break existing code though we think it's unlikely to happen. If something doesn't work for you please report and we'll try to sort it out (flash-gordon + timriley in [#48](https://github.com/dry-rb/dry-auto_inject/pull/48))
18
+
19
+ ### Fixed
20
+
21
+ - A couple of regressions were fixed along the way, see [#46](https://github.com/dry-rb/dry-auto_inject/issues/46) and [#49](https://github.com/dry-rb/dry-auto_inject/issues/49) (flash-gordon + timriley in [#48](https://github.com/dry-rb/dry-auto_inject/pull/48))
22
+
23
+ [Compare v0.5.0...v0.6.0](https://github.com/dry-rb/dry-auto_inject/compare/v0.5.0...v0.6.0)
24
+
25
+ # 0.5.0 / 2018-11-09
26
+
27
+ ### Changed
28
+
29
+ - Only assign `nil` dependency instance variables from generated `#initialize` if the instance variable has not been previously defined. This improves compatibility with objects initialized in non-conventional ways (see example below) (timriley in [#47](https://github.com/dry-rb/dry-auto_inject/pull/47))
30
+
31
+ ```ruby
32
+ module SomeFramework
33
+ class Action
34
+ def self.new(configuration:, **args)
35
+ # Do some trickery so `#initialize` on subclasses don't need to worry
36
+ # about handling a configuration kwarg and passing it to super
37
+ allocate.tap do |obj|
38
+ obj.instance_variable_set :@configuration, configuration
39
+ obj.send :initialize, **args
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ module MyApp
46
+ class Action < SomeFramework::Action
47
+ # Inject the configuration object, which is passed to
48
+ # SomeFramework::Action.new but not all the way through to any subsequent
49
+ # `#initialize` calls
50
+ include Import[configuration: "web.action.configuration"]
51
+ end
52
+
53
+ class SomeAction < Action
54
+ # Subclasses of MyApp::Action don't need to concern themselves with
55
+ # `configuration` dependency
56
+ include Import["some_repo"]
57
+ end
58
+ end
59
+ ```
60
+
61
+ [Compare v0.4.6...v0.5.0](https://github.com/dry-rb/dry-auto_inject/compare/v0.4.6...v0.5.0)
62
+
63
+ # 0.4.6 / 2018-03-27
64
+
65
+ ### Changed
66
+
67
+ - In injector-generated `#initialize` methods, set dependency instance variables before calling `super` (timriley)
68
+
69
+ [Compare v0.4.5...v0.4.6](https://github.com/dry-rb/dry-auto_inject/compare/v0.4.5...v0.4.6)
70
+
71
+ # 0.4.5 / 2018-01-02
72
+
73
+ ### Added
74
+
75
+ - Improved handling of kwargs being passed to #initialize’s super method (timriley)
76
+
77
+ [Compare v0.4.4...v0.4.5](https://github.com/dry-rb/dry-auto_inject/compare/v0.4.4...v0.4.5)
78
+
79
+ # 0.4.4 / 2017-09-14
80
+
81
+ ### Added
82
+
83
+ - Determine name for dependencies by splitting identifiers on any invalid local variable name characters (e.g. "/", "?", "!"), instead of splitting on dots only (raventid in [#39](https://github.com/dry-rb/dry-auto_inject/pull/39))
84
+
85
+ # 0.4.3 / 2017-05-27
86
+
87
+ ### Added
88
+
89
+ - Push sequential arguments along with keywords in the kwargs strategy (hbda + vladra in [#32](https://github.com/dry-rb/dry-auto_inject/pull/32))
90
+
91
+ [Compare v0.4.2...v0.4.3](https://github.com/dry-rb/dry-auto_inject/compare/v0.4.2...v0.4.3)
92
+
93
+ # 0.4.2 / 2016-10-10
94
+
95
+ ### Fixed
96
+
97
+ - Fixed issue where injectors for different containers could not be used on different classes in an inheritance hierarchy (timriley in [#31](https://github.com/dry-rb/dry-auto_inject/pull/31))
98
+
99
+ [Compare v0.4.1...v0.4.2](https://github.com/dry-rb/dry-auto_inject/compare/v0.4.1...v0.4.2)
100
+
101
+ # 0.4.1 / 2016-08-14
102
+
103
+ ### Changed
104
+
105
+ - Loosened version dependency on dry-container (AMHOL)
106
+
107
+ [Compare v0.4.0...v0.4.1](https://github.com/dry-rb/dry-auto_inject/compare/v0.4.0...v0.4.1)
108
+
109
+ # 0.4.0 / 2016-07-26
110
+
111
+ ### Added
112
+
113
+ - Support for strategy chaining, which is helpful in opting for alternatives to an application's normal strategy (timriley in [#25](https://github.com/dry-rb/dry-auto_inject/pull/25))
114
+
115
+ ```ruby
116
+ # Define the application's injector with a non-default
117
+ MyInject = Dry::AutoInject(MyContainer).hash
118
+
119
+ # Opt for a different strategy in a particular class
120
+ class MyClass
121
+ include MyInject.args["foo"]
122
+ end
123
+
124
+ # You can chain as long as you want (silly example to demonstrate the flexibility)
125
+ class OtherClass
126
+ include MyInject.args.hash.kwargs.args["foo"]
127
+ end
128
+ ```
129
+
130
+ ### Changed
131
+
132
+ - Use a `BasicObject`-based environment for the injector builder API instead of the previous `define_singleton_method`-based approach, which had negative performance characteristics (timriley in [#26](https://github.com/dry-rb/dry-auto_inject/pull/26))
133
+
134
+ ### Fixed
135
+
136
+ - Fixed issue with kwargs injectors used at multiple points in a class inheritance heirarchy (flash-gordon in [#27](https://github.com/dry-rb/dry-auto_inject/pull/27))
137
+
138
+ [Compare v0.3.0...v0.4.0](https://github.com/dry-rb/dry-auto_inject/compare/v0.3.0...v0.4.0)
139
+
140
+ # 0.3.0, 2016-06-02
141
+
142
+ ### Added
143
+
144
+ * Support for new `kwargs` and `hash` injection strategies
145
+
146
+ These strategies can be accessed via methods on the main builder object:
147
+
148
+ ```ruby
149
+ MyInject = Dry::AutoInject(my_container)
150
+
151
+ class MyClass
152
+ include MyInject.hash["my_dep"]
153
+ end
154
+ ```
155
+ * Support for user-provided injection strategies
156
+
157
+ All injection strategies are now held in their own `Dry::AutoInject::Strategies` container. You can add register your own strategies to this container, or choose to provide a strategies container of your own:
158
+
159
+ ```ruby
160
+ class CustomStrategy < Module
161
+ # Your strategy code goes here :)
162
+ end
163
+
164
+ # Registering your own strategy (globally)
165
+ Dry::AutoInject::Strategies.register :custom, CustomStrategy
166
+
167
+ MyInject = Dry::AutoInject(my_container)
168
+
169
+ class MyClass
170
+ include MyInject.custom["my_dep"]
171
+ end
172
+
173
+ # Providing your own container (keeping the existing strategies in place)
174
+ class MyStrategies < Dry::AutoInject::Strategies
175
+ register :custom, CustomStrategy
176
+ end
177
+
178
+ MyInject = Dry::AutoInject(my_container, strategies: MyStrategies)
179
+
180
+ class MyClass
181
+ include MyInject.custom["my_dep"]
182
+ end
183
+
184
+ # Proiding a completely separated container
185
+ class MyStrategies
186
+ extend Dry::Container::Mixin
187
+ register :custom, CustomStrategy
188
+ end
189
+
190
+ MyInject = Dry::AutoInject(my_container, strategies: MyStrategies)
191
+
192
+ class MyClass
193
+ include MyInject.custom["my_dep"]
194
+ end
195
+ ```
196
+ * User-specified aliases for dependencies
197
+
198
+ These aliases enable you to specify your own name for dependencies, both for their local readers and their keys in the kwargs- and hash-based initializers. Specify aliases by passing a hash of names:
199
+
200
+ ```ruby
201
+ MyInject = Dry::AutoInject(my_container)
202
+
203
+ class MyClass
204
+ include MyInject[my_dep: "some_other.dep"]
205
+
206
+ # Refer to the dependency as `my_dep` inside the class
207
+ end
208
+
209
+ # Pass your own replacements using the `my_dep` initializer key
210
+ my_obj = MyClass.new(my_dep: something_else)
211
+ ```
212
+
213
+ A mix of both regular and aliased dependencies can also be injected:
214
+
215
+ ```ruby
216
+ include MyInject["some_dep", another_dep: "some_other.dep"]
217
+ ```
218
+
219
+ * Inspect the `super` method of the including class’s `#initialize` and send it arguments that will match its own arguments list/arity. This allows auto_inject to be used more easily in existing class inheritance heirarchies.
220
+
221
+ ### Changed
222
+
223
+ * `kwargs` is the new default injection strategy
224
+ * Rubinius support is not available for the `kwargs` strategy (see [#18](https://github.com/dry-rb/dry-auto_inject/issues/18))
225
+
226
+ [Compare v0.2.0...v0.3.0](https://github.com/dry-rb/dry-auto_inject/compare/v0.2.0...v0.3.0)
227
+
228
+ # v0.2.0 2016-02-09
229
+
230
+ ### Added
231
+
232
+ * Support for hashes as constructor arguments via `Import.hash` interface (solnic)
233
+
234
+ [Compare v0.1.0...v0.2.0](https://github.com/dry-rb/dry-auto_inject/compare/v0.1.0...v0.2.0)
235
+
236
+ # v0.1.0 2015-11-12
237
+
238
+ Changed interface from `Dry::AutoInject.new { container(some_container) }` to
239
+ `Dry::AutoInject(some_container)`.
240
+
241
+ # v0.0.1 2015-08-20
242
+
243
+ First public release \o/
@@ -0,0 +1,29 @@
1
+ # Issue Guidelines
2
+
3
+ ## Reporting bugs
4
+
5
+ If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
6
+
7
+ ## Reporting feature requests
8
+
9
+ Report a feature request **only after discussing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
10
+
11
+ ## Reporting questions, support requests, ideas, concerns etc.
12
+
13
+ **PLEASE DON'T** - use [discourse.dry-rb.org](https://discourse.dry-rb.org) instead.
14
+
15
+ # Pull Request Guidelines
16
+
17
+ A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
18
+
19
+ Other requirements:
20
+
21
+ 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
22
+ 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
23
+ 3) Add API documentation if it's a new feature
24
+ 4) Update API documentation if it changes an existing feature
25
+ 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
26
+
27
+ # Asking for help
28
+
29
+ If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org).
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in dry-auto_inject.gemspec
6
+ gemspec
7
+
8
+ group :test do
9
+ gem "simplecov"
10
+ gem "codeclimate-test-reporter", require: nil
11
+ end
12
+
13
+ group :tools do
14
+ gem 'byebug', platforms: :mri
15
+ gem 'pry'
16
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015-2016 Piotr Solnica
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ [gem]: https://rubygems.org/gems/dry-auto_inject
2
+ [travis]: https://travis-ci.org/dry-rb/dry-auto_inject
3
+ [codeclimate]: https://codeclimate.com/github/dry-rb/dry-auto_inject
4
+ [coveralls]: https://coveralls.io/r/dry-rb/dry-auto_inject
5
+ [inchpages]: http://inch-ci.org/github/dry-rb/dry-auto_inject
6
+ [chat]: https://dry-rb.zulipchat.com
7
+
8
+ # dry-auto_inject [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
9
+
10
+ [![Gem Version](https://badge.fury.io/rb/dry-auto_inject.svg)][gem]
11
+ [![Build Status](https://travis-ci.org/dry-rb/dry-auto_inject.svg?branch=master)][travis]
12
+ [![Code Climate](https://codeclimate.com/github/dry-rb/dry-auto_inject/badges/gpa.svg)][codeclimate]
13
+ [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-auto_inject/badges/coverage.svg)][codeclimate]
14
+ [![Inline docs](http://inch-ci.org/github/dry-rb/dry-auto_inject.svg?branch=master)][inchpages]
15
+ ![No monkey-patches](https://img.shields.io/badge/monkey--patches-0-brightgreen.svg)
16
+
17
+ A simple extension which allows you to automatically inject dependencies to your
18
+ object constructors from a configured container.
19
+
20
+ It does 3 things:
21
+
22
+ - Defines a constructor which accepts dependencies
23
+ - Defines attribute readers for dependencies
24
+ - Injects dependencies automatically to the constructor with overridden `.new`
25
+
26
+ ## Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem 'dry-auto_inject'
32
+ ```
33
+
34
+ And then execute:
35
+
36
+ ```sh
37
+ $ bundle
38
+ ```
39
+
40
+ Or install it yourself as:
41
+ ```sh
42
+ $ gem install dry-auto_inject
43
+ ```
44
+
45
+ ## Links
46
+
47
+ * [Documentation](http://dry-rb.org/gems/dry-auto_inject/)
48
+
49
+ ## Development
50
+
51
+ 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.
52
+
53
+ 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).
54
+
55
+ ## Contributing
56
+
57
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dry-rb/dry-auto_inject.
58
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/gem_tasks'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
7
+
8
+ require 'rspec/core'
9
+ require 'rspec/core/rake_task'
10
+
11
+ task default: :spec
12
+
13
+ desc 'Run all specs in spec directory'
14
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'dry-auto_inject'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require 'pry'
11
+ Pry.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'dry/auto_inject/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'dry-auto_inject'
9
+ spec.version = Dry::AutoInject::VERSION.dup
10
+ spec.authors = ['Piotr Solnica']
11
+ spec.email = ['piotr.solnica@gmail.com']
12
+ spec.license = 'MIT'
13
+
14
+ spec.summary = 'Container-agnostic automatic constructor injection'
15
+ spec.homepage = 'https://github.com/dry-rb/dry-auto_inject'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.required_ruby_version = '>= 2.3.0'
23
+
24
+ spec.add_runtime_dependency 'dry-container', '>= 0.3.4'
25
+
26
+ spec.add_development_dependency 'bundler'
27
+ spec.add_development_dependency 'rake'
28
+ spec.add_development_dependency 'rspec', '~> 3.8'
29
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/auto_inject'
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/auto_inject/builder'
4
+
5
+ module Dry
6
+ # Configure an auto-injection module
7
+ #
8
+ # @example
9
+ # module MyApp
10
+ # # set up your container
11
+ # container = Dry::Container.new
12
+ #
13
+ # container.register(:data_store, -> { DataStore.new })
14
+ # container.register(:user_repository, -> { container[:data_store][:users] })
15
+ # container.register(:persist_user, -> { PersistUser.new })
16
+ #
17
+ # # set up your auto-injection function
18
+ # AutoInject = Dry::AutoInject(container)
19
+ #
20
+ # # define your injection function
21
+ # def self.Inject(*keys)
22
+ # AutoInject[*keys]
23
+ # end
24
+ # end
25
+ #
26
+ # # then simply include it in your class providing which dependencies should be
27
+ # # injected automatically from the configured container
28
+ # class PersistUser
29
+ # include MyApp::Inject(:user_repository)
30
+ #
31
+ # def call(user)
32
+ # user_repository << user
33
+ # end
34
+ # end
35
+ #
36
+ # persist_user = container[:persist_user]
37
+ #
38
+ # persist_user.call(name: 'Jane')
39
+ #
40
+ # @return [Proc] calling the returned proc builds an auto-injection module
41
+ #
42
+ # @api public
43
+ def self.AutoInject(container, options = {})
44
+ AutoInject::Builder.new(container, options)
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/auto_inject/strategies'
4
+ require 'dry/auto_inject/injector'
5
+
6
+ module Dry
7
+ module AutoInject
8
+ class Builder < BasicObject
9
+ # @api private
10
+ attr_reader :container
11
+
12
+ # @api private
13
+ attr_reader :strategies
14
+
15
+ def initialize(container, options = {})
16
+ @container = container
17
+ @strategies = options.fetch(:strategies) { Strategies }
18
+ end
19
+
20
+ # @api public
21
+ def [](*dependency_names)
22
+ default[*dependency_names]
23
+ end
24
+
25
+ def respond_to?(name, include_private = false)
26
+ Builder.public_instance_methods.include?(name) || strategies.key?(name)
27
+ end
28
+
29
+ private
30
+
31
+ def method_missing(name, *args, &block)
32
+ if strategies.key?(name)
33
+ Injector.new(container, strategies[name], builder: self)
34
+ else
35
+ super
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module AutoInject
5
+ DuplicateDependencyError = Class.new(StandardError)
6
+ DependencyNameInvalid = Class.new(StandardError)
7
+
8
+ VALID_NAME = /([a-z_][a-zA-Z_0-9]*)$/
9
+
10
+ class DependencyMap
11
+ def initialize(*dependencies)
12
+ @map = {}
13
+
14
+ dependencies = dependencies.dup
15
+ aliases = dependencies.last.is_a?(Hash) ? dependencies.pop : {}
16
+
17
+ dependencies.each do |identifier|
18
+ name = name_for(identifier)
19
+ add_dependency(name, identifier)
20
+ end
21
+
22
+ aliases.each do |name, identifier|
23
+ add_dependency(name, identifier)
24
+ end
25
+ end
26
+
27
+ def inspect
28
+ @map.inspect
29
+ end
30
+
31
+ def names
32
+ @names ||= @map.keys
33
+ end
34
+
35
+ def to_h
36
+ @map.dup
37
+ end
38
+ alias_method :to_hash, :to_h
39
+
40
+ private
41
+
42
+ def name_for(identifier)
43
+ matched = VALID_NAME.match(identifier.to_s)
44
+ raise DependencyNameInvalid, "name +#{identifier}+ is not a valid Ruby identifier" unless matched
45
+ matched[0]
46
+ end
47
+
48
+ def add_dependency(name, identifier)
49
+ name = name.to_sym
50
+ raise DuplicateDependencyError, "name +#{name}+ is already used" if @map.key?(name)
51
+ @map[name] = identifier
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/auto_inject/strategies'
4
+
5
+ module Dry
6
+ module AutoInject
7
+ class Injector < BasicObject
8
+ # @api private
9
+ attr_reader :container
10
+
11
+ # @api private
12
+ attr_reader :strategy
13
+
14
+ # @api private
15
+ attr_reader :builder
16
+
17
+ # @api private
18
+ def initialize(container, strategy, builder:)
19
+ @container = container
20
+ @strategy = strategy
21
+ @builder = builder
22
+ end
23
+
24
+ def [](*dependency_names)
25
+ strategy.new(container, *dependency_names)
26
+ end
27
+
28
+ def respond_to?(name, include_private = false)
29
+ Injector.instance_methods.include?(name) || builder.respond_to?(name)
30
+ end
31
+
32
+ private
33
+
34
+ def method_missing(name, *args, &block)
35
+ builder.__send__(name)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,92 @@
1
+ require 'set'
2
+
3
+ module Dry
4
+ module AutoInject
5
+ # @api private
6
+ class MethodParameters
7
+ PASS_THROUGH = [[:rest]]
8
+
9
+ if RUBY_VERSION >= '2.4.4.' && !defined? JRUBY_VERSION
10
+ def self.of(obj, name)
11
+ Enumerator.new do |y|
12
+ begin
13
+ method = obj.instance_method(name)
14
+ rescue NameError
15
+ end
16
+
17
+ loop do
18
+ break if method.nil?
19
+
20
+ y << MethodParameters.new(method.parameters)
21
+ method = method.super_method
22
+ end
23
+ end
24
+ end
25
+ else
26
+ # see https://bugs.ruby-lang.org/issues/13973
27
+ def self.of(obj, name)
28
+ Enumerator.new do |y|
29
+ ancestors = obj.ancestors
30
+
31
+ loop do
32
+ klass = ancestors.shift
33
+ break if klass.nil?
34
+
35
+ begin
36
+ method = klass.instance_method(name)
37
+
38
+ next unless method.owner.equal?(klass)
39
+ rescue NameError
40
+ next
41
+ end
42
+
43
+ y << MethodParameters.new(method.parameters)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ attr_reader :parameters
50
+
51
+ def initialize(parameters)
52
+ @parameters = parameters
53
+ end
54
+
55
+ def splat?
56
+ return @splat if defined? @splat
57
+ @splat = parameters.any? { |type, _| type == :rest }
58
+ end
59
+
60
+ def sequential_arguments?
61
+ return @sequential_arguments if defined? @sequential_arguments
62
+ @sequential_arguments = parameters.any? { |type, _|
63
+ type == :req || type == :opt
64
+ }
65
+ end
66
+
67
+ def keyword_names
68
+ @keyword_names ||= parameters.each_with_object(Set.new) { |(type, name), names|
69
+ names << name if type == :key || type == :keyreq
70
+ }
71
+ end
72
+
73
+ def keyword?(name)
74
+ keyword_names.include?(name)
75
+ end
76
+
77
+ def empty?
78
+ parameters.empty?
79
+ end
80
+
81
+ def length
82
+ parameters.length
83
+ end
84
+
85
+ def pass_through?
86
+ parameters.eql?(PASS_THROUGH)
87
+ end
88
+
89
+ EMPTY = new([])
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-container'
4
+
5
+ module Dry
6
+ module AutoInject
7
+ class Strategies
8
+ extend Dry::Container::Mixin
9
+
10
+ # @api public
11
+ def self.register_default(name, strategy)
12
+ register name, strategy
13
+ register :default, strategy
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ require 'dry/auto_inject/strategies/args'
20
+ require 'dry/auto_inject/strategies/hash'
21
+ require 'dry/auto_inject/strategies/kwargs'
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/auto_inject/strategies/constructor'
4
+ require 'dry/auto_inject/method_parameters'
5
+
6
+ module Dry
7
+ module AutoInject
8
+ class Strategies
9
+ # @api private
10
+ class Args < Constructor
11
+ private
12
+
13
+ def define_new
14
+ class_mod.class_exec(container, dependency_map) do |container, dependency_map|
15
+ define_method :new do |*args|
16
+ deps = dependency_map.to_h.values.map.with_index { |identifier, i|
17
+ args[i] || container[identifier]
18
+ }
19
+
20
+ super(*deps, *args[deps.size..-1])
21
+ end
22
+ end
23
+ end
24
+
25
+ def define_initialize(klass)
26
+ super_parameters = MethodParameters.of(klass, :initialize).each do |ps|
27
+ # Look upwards past `def foo(*)` methods until we get an explicit list of parameters
28
+ break ps unless ps.pass_through?
29
+ end
30
+
31
+ if super_parameters.empty?
32
+ define_initialize_with_params
33
+ else
34
+ define_initialize_with_splat(super_parameters)
35
+ end
36
+ end
37
+
38
+ def define_initialize_with_params
39
+ initialize_args = dependency_map.names.join(', ')
40
+
41
+ instance_mod.class_eval <<-RUBY, __FILE__, __LINE__ + 1
42
+ def initialize(#{initialize_args})
43
+ #{dependency_map.names.map { |name| "@#{name} = #{name}" }.join("\n")}
44
+ super()
45
+ end
46
+ RUBY
47
+ end
48
+
49
+ def define_initialize_with_splat(super_parameters)
50
+ super_pass = if super_parameters.splat?
51
+ '*args'
52
+ else
53
+ "*args.take(#{super_parameters.length})"
54
+ end
55
+
56
+ instance_mod.class_eval <<-RUBY, __FILE__, __LINE__ + 1
57
+ def initialize(*args)
58
+ #{dependency_map.names.map.with_index { |name, i| "@#{name} = args[#{i}]" }.join("\n")}
59
+ super(#{super_pass})
60
+ end
61
+ RUBY
62
+ end
63
+ end
64
+
65
+ register :args, Args
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/auto_inject/dependency_map'
4
+
5
+ module Dry
6
+ module AutoInject
7
+ class Strategies
8
+ class Constructor < Module
9
+ ClassMethods = Class.new(Module)
10
+ InstanceMethods = Class.new(Module)
11
+
12
+ attr_reader :container
13
+ attr_reader :dependency_map
14
+ attr_reader :instance_mod
15
+ attr_reader :class_mod
16
+
17
+ def initialize(container, *dependency_names)
18
+ @container = container
19
+ @dependency_map = DependencyMap.new(*dependency_names)
20
+ @instance_mod = InstanceMethods.new
21
+ @class_mod = ClassMethods.new
22
+ end
23
+
24
+ # @api private
25
+ def included(klass)
26
+ define_readers
27
+
28
+ define_new
29
+ define_initialize(klass)
30
+
31
+ klass.send(:include, instance_mod)
32
+ klass.extend(class_mod)
33
+
34
+ super
35
+ end
36
+
37
+ private
38
+
39
+ def define_readers
40
+ instance_mod.class_eval <<-RUBY, __FILE__, __LINE__ + 1
41
+ attr_reader #{dependency_map.names.map { |name| ":#{name}" }.join(', ')}
42
+ RUBY
43
+ self
44
+ end
45
+
46
+ def define_new
47
+ raise NotImplementedError, "must be implemented by a subclass"
48
+ end
49
+
50
+ def define_initialize(klass)
51
+ raise NotImplementedError, "must be implemented by a subclass"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/auto_inject/strategies/constructor'
4
+ require 'dry/auto_inject/method_parameters'
5
+
6
+ module Dry
7
+ module AutoInject
8
+ class Strategies
9
+ # @api private
10
+ class Hash < Constructor
11
+ private
12
+
13
+ def define_new
14
+ class_mod.class_exec(container, dependency_map) do |container, dependency_map|
15
+ define_method :new do |options = {}|
16
+ deps = dependency_map.to_h.each_with_object({}) { |(name, identifier), obj|
17
+ obj[name] = options[name] || container[identifier]
18
+ }.merge(options)
19
+
20
+ super(deps)
21
+ end
22
+ end
23
+ end
24
+
25
+ def define_initialize(klass)
26
+ super_params = MethodParameters.of(klass, :initialize).first
27
+ super_pass = super_params.empty? ? '' : 'options'
28
+
29
+ instance_mod.class_eval <<-RUBY, __FILE__, __LINE__ + 1
30
+ def initialize(options)
31
+ #{dependency_map.names.map { |name| "@#{name} = options[:#{name}] unless !options.key?(#{name}) && instance_variable_defined?(:'@#{name}')" }.join("\n")}
32
+ super(#{super_pass})
33
+ end
34
+ RUBY
35
+ end
36
+ end
37
+
38
+ register :hash, Hash
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/auto_inject/strategies/constructor'
4
+ require 'dry/auto_inject/method_parameters'
5
+
6
+ module Dry
7
+ module AutoInject
8
+ class Strategies
9
+ # @api private
10
+ class Kwargs < Constructor
11
+ private
12
+
13
+ def define_new
14
+ class_mod.class_exec(container, dependency_map) do |container, dependency_map|
15
+ map = dependency_map.to_h
16
+
17
+ define_method :new do |*args, **kwargs|
18
+ map.each do |name, identifier|
19
+ kwargs[name] = container[identifier] unless kwargs.key?(name)
20
+ end
21
+
22
+ super(*args, **kwargs)
23
+ end
24
+ end
25
+ end
26
+
27
+ def define_initialize(klass)
28
+ super_parameters = MethodParameters.of(klass, :initialize).each do |ps|
29
+ # Look upwards past `def foo(*)` methods until we get an explicit list of parameters
30
+ break ps unless ps.pass_through?
31
+ end
32
+
33
+ if super_parameters.splat? || super_parameters.sequential_arguments?
34
+ define_initialize_with_splat(super_parameters)
35
+ else
36
+ define_initialize_with_keywords(super_parameters)
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ def define_initialize_with_keywords(super_parameters)
43
+ assign_dependencies = method(:assign_dependencies)
44
+ slice_kwargs = method(:slice_kwargs)
45
+
46
+ instance_mod.class_exec do
47
+ define_method :initialize do |**kwargs|
48
+ assign_dependencies.(kwargs, self)
49
+
50
+ super_kwargs = slice_kwargs.(kwargs, super_parameters)
51
+
52
+ if super_kwargs.any?
53
+ super(super_kwargs)
54
+ else
55
+ super()
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def define_initialize_with_splat(super_parameters)
62
+ assign_dependencies = method(:assign_dependencies)
63
+ slice_kwargs = method(:slice_kwargs)
64
+
65
+ instance_mod.class_exec do
66
+ define_method :initialize do |*args, **kwargs|
67
+ assign_dependencies.(kwargs, self)
68
+
69
+ if super_parameters.splat?
70
+ super(*args, kwargs)
71
+ else
72
+ super_kwargs = slice_kwargs.(kwargs, super_parameters)
73
+
74
+ if super_kwargs.any?
75
+ super(*args, super_kwargs)
76
+ else
77
+ super(*args)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def assign_dependencies(kwargs, destination)
85
+ dependency_map.names.each do |name|
86
+ # Assign instance variables, but only if the ivar is not
87
+ # previously defined (this improves compatibility with objects
88
+ # initialized in unconventional ways)
89
+ if kwargs.key?(name) || !destination.instance_variable_defined?(:"@#{name}")
90
+ destination.instance_variable_set :"@#{name}", kwargs[name]
91
+ end
92
+ end
93
+ end
94
+
95
+ def slice_kwargs(kwargs, super_parameters)
96
+ kwargs.select do |key|
97
+ !dependency_map.names.include?(key) || super_parameters.keyword?(key)
98
+ end
99
+ end
100
+ end
101
+
102
+ register_default :kwargs, Kwargs
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module AutoInject
5
+ VERSION = '0.6.1'
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'rubocop/rake_task'
5
+
6
+ Rake::Task[:default].enhance [:rubocop]
7
+
8
+ RuboCop::RakeTask.new do |task|
9
+ task.options << '--display-cop-names'
10
+ end
11
+
12
+ namespace :rubocop do
13
+ desc 'Generate a configuration file acting as a TODO list.'
14
+ task :auto_gen_config do
15
+ exec 'bundle exec rubocop --auto-gen-config'
16
+ end
17
+ end
18
+
19
+ rescue LoadError
20
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-auto_inject
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.1
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Solnica
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-container
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.8'
69
+ description:
70
+ email:
71
+ - piotr.solnica@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".codeclimate.yml"
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - ".rubocop_todo.yml"
81
+ - ".travis.yml"
82
+ - CHANGELOG.md
83
+ - CONTRIBUTING.md
84
+ - Gemfile
85
+ - LICENSE
86
+ - README.md
87
+ - Rakefile
88
+ - bin/console
89
+ - bin/setup
90
+ - dry-auto_inject.gemspec
91
+ - lib/dry-auto_inject.rb
92
+ - lib/dry/auto_inject.rb
93
+ - lib/dry/auto_inject/builder.rb
94
+ - lib/dry/auto_inject/dependency_map.rb
95
+ - lib/dry/auto_inject/injector.rb
96
+ - lib/dry/auto_inject/method_parameters.rb
97
+ - lib/dry/auto_inject/strategies.rb
98
+ - lib/dry/auto_inject/strategies/args.rb
99
+ - lib/dry/auto_inject/strategies/constructor.rb
100
+ - lib/dry/auto_inject/strategies/hash.rb
101
+ - lib/dry/auto_inject/strategies/kwargs.rb
102
+ - lib/dry/auto_inject/version.rb
103
+ - rakelib/rubocop.rake
104
+ homepage: https://github.com/dry-rb/dry-auto_inject
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 2.3.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.0.3
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Container-agnostic automatic constructor injection
127
+ test_files: []