dio-rails 0.1.0
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/MIT-LICENSE +20 -0
- data/README.md +189 -0
- data/Rakefile +38 -0
- data/lib/dio.rb +76 -0
- data/lib/dio/container.rb +57 -0
- data/lib/dio/equip.rb +25 -0
- data/lib/dio/injector.rb +87 -0
- data/lib/dio/injector_store.rb +38 -0
- data/lib/dio/load_context.rb +31 -0
- data/lib/dio/loader_factory.rb +84 -0
- data/lib/dio/module_base.rb +124 -0
- data/lib/dio/rails.rb +11 -0
- data/lib/dio/rails/controller.rb +34 -0
- data/lib/dio/rails/model.rb +34 -0
- data/lib/dio/state.rb +28 -0
- data/lib/dio/version.rb +5 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b91abbf754fe6c1191492d8d076ac087e4af4741
|
4
|
+
data.tar.gz: 0eb0c55a1c4b01d2eb3d8825bb2c86738826a580
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d2b917d1b4108953c088f7b4ea6f64fda1cf1895147571154d7cfe5ad6b7cae06875f864c5eca79cdcb6c91e64de0df5c4f910487ad1999f6a83a08f0a0dc78f
|
7
|
+
data.tar.gz: b028759f44f82319115391a8f919251dc709d8c7fd06ef1c265e74d19fc85e988081ed6bd1c541f7079058a0dd3ed2a07cce2aecd32b6ac1140bde9913e82a3d
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 ryym
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# Dio
|
2
|
+
|
3
|
+
[![Build Status][travis-badge]](https://travis-ci.org/ryym/dio)
|
4
|
+
|
5
|
+
[travis-badge]: https://travis-ci.org/ryym/dio.svg?branch=master
|
6
|
+
|
7
|
+
Dio is a gem which allows you to do Dependency Injection (DI) in Ruby.
|
8
|
+
This is mainly made for use with Rails.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
```bash
|
13
|
+
$ gem install dio-rails
|
14
|
+
```
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
### Register injectable objects
|
19
|
+
|
20
|
+
You can declare that a class is injectable.
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
class SomeAPI
|
24
|
+
include Dio
|
25
|
+
|
26
|
+
# Register this class as a injectable dependency.
|
27
|
+
injectable
|
28
|
+
|
29
|
+
# You can also register a factory block.
|
30
|
+
# (If a block is omitted, simply `SomeAPI.new` is registered)
|
31
|
+
injectable :with_special_key do
|
32
|
+
SomeAPI.new(ENV['special-key'])
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(access_key = ENV['access-key'])
|
36
|
+
@access_key = access_key
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch_some
|
40
|
+
# ...
|
41
|
+
end
|
42
|
+
|
43
|
+
# ...
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
Or you can define a provider class.
|
48
|
+
Note that you need to load this class before injection in this case.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class DepsProvider
|
52
|
+
include Dio
|
53
|
+
|
54
|
+
provide :foo do |name|
|
55
|
+
Foo.new(name: name, type: :default)
|
56
|
+
end
|
57
|
+
|
58
|
+
provide :bar do |*args|
|
59
|
+
BarFactory.create(*args)
|
60
|
+
end
|
61
|
+
|
62
|
+
# ...
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
In this provider pattern, you must specify both of a key and a factory block.
|
67
|
+
|
68
|
+
### Configure injection
|
69
|
+
|
70
|
+
In a class that has dependencies, define a `inject` block and load dependencies it needs.
|
71
|
+
`dio.load` runs a factory block registered with the given key.
|
72
|
+
Also you can pass arguments for a factory block like `dio.load(:key, :arg1, :arg2)`.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class SomeAction
|
76
|
+
include Dio
|
77
|
+
|
78
|
+
# The given block is evaluated in each instance scope so that
|
79
|
+
# you can store loaded dependencies as instance variables.
|
80
|
+
inject do |dio|
|
81
|
+
@api = dio.load(SomeAPI)
|
82
|
+
@foo = dio.load(:foo, 'my-foo')
|
83
|
+
|
84
|
+
# @api = dio.load([SomeAPI, :with_special_key])
|
85
|
+
# @api = dio.load(SomeAPI, 'custom-key')
|
86
|
+
end
|
87
|
+
|
88
|
+
def load_some
|
89
|
+
some = @api.fetch_some
|
90
|
+
# ...
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
### Apply injection
|
96
|
+
|
97
|
+
There are two ways to apply injection manually.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
# Create an instance with injection.
|
101
|
+
action = Dio.create(SomeAction)
|
102
|
+
|
103
|
+
# Or inject to an already created instance.
|
104
|
+
Dio.inject(action)
|
105
|
+
```
|
106
|
+
|
107
|
+
### Use with Rails
|
108
|
+
|
109
|
+
#### Controller
|
110
|
+
|
111
|
+
Include `Dio::Rails::Controller` to inject dependencies automatically.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class UsersController < ApplicationController
|
115
|
+
include Dio::Rails::Controller
|
116
|
+
|
117
|
+
attr_reader :user_detail
|
118
|
+
|
119
|
+
inject do |dio|
|
120
|
+
@user_detail = dio.load(UserDetail)
|
121
|
+
end
|
122
|
+
|
123
|
+
def show
|
124
|
+
@user = User.find(params[:id])
|
125
|
+
@detail = user_detail.about(user)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
#### Model
|
131
|
+
|
132
|
+
Include `Dio::Rails::Model` to inject dependencies automatically.
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class User < ApplicationModel
|
136
|
+
include Dio::Rails::Model
|
137
|
+
|
138
|
+
inject do |dio|
|
139
|
+
@age = dio.load(AgeCalculator)
|
140
|
+
end
|
141
|
+
|
142
|
+
def age
|
143
|
+
@age.from_birthday(birthday)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
### Testing
|
149
|
+
|
150
|
+
You can easily replace depdendencies for testing.
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
class UsersControllerTest < ActionDispatch::IntegrationTest
|
154
|
+
setup do
|
155
|
+
Dio.clear_stubs
|
156
|
+
end
|
157
|
+
|
158
|
+
test "should get index" do
|
159
|
+
UserDetailMock = Class.new do
|
160
|
+
def about(user)
|
161
|
+
UserDetail::Data.new("mock", "mock detail")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Pass a hash of key-mock pairs.
|
166
|
+
# When you load the key that is in the hash, a registered mock
|
167
|
+
# is returned instead of the actual dependency object.
|
168
|
+
Dio.stub_deps(UsersController, {
|
169
|
+
UserDetail => UserDetailMock.new
|
170
|
+
})
|
171
|
+
|
172
|
+
get users_url
|
173
|
+
assert_response :success
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
## Motivation
|
179
|
+
|
180
|
+
- I want to separate logics from Rails controllers.
|
181
|
+
- I don't want to instantiate a logic class in controllers.
|
182
|
+
It makes it a bit cumbersome to use mocks in a test.
|
183
|
+
- In many cases I want to use a class rather than mixin modules because:
|
184
|
+
- Mixin pollutes name space of an includer class.
|
185
|
+
- It is difficult to declare a private method in mixin module
|
186
|
+
that can't be seen even from an includer class.
|
187
|
+
- A class allows us to initialize it with some dependencies it needs.
|
188
|
+
|
189
|
+
Dependency injection allows us to separate instantiation of dependencies from a class depending on them.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rdoc/task'
|
10
|
+
require 'bundler/gem_tasks'
|
11
|
+
|
12
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
13
|
+
rdoc.rdoc_dir = 'rdoc'
|
14
|
+
rdoc.title = 'Dio::Rails'
|
15
|
+
rdoc.options << '--line-numbers'
|
16
|
+
rdoc.rdoc_files.include('README.md')
|
17
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rspec/core/rake_task'
|
21
|
+
|
22
|
+
desc 'Run all specs'
|
23
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
24
|
+
t.rspec_opts = %w[--color]
|
25
|
+
t.verbose = false
|
26
|
+
end
|
27
|
+
|
28
|
+
namespace :spec do
|
29
|
+
desc 'Run sample-app tests'
|
30
|
+
task :sample do
|
31
|
+
puts 'Running tests in sample Rails app...'
|
32
|
+
Dir.chdir('spec/sample-app') do
|
33
|
+
system('bin/rails test')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
task default: [:spec, 'spec:sample']
|
data/lib/dio.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'dio/equip'
|
5
|
+
require 'dio/version'
|
6
|
+
|
7
|
+
# Dio provides DI functionality.
|
8
|
+
# Note that most of the methods this module provides are
|
9
|
+
# defined at {Dio::ModuleBase}.
|
10
|
+
#
|
11
|
+
# @see Dio::ModuleBase
|
12
|
+
# @see Dio::ModuleBase::ClassMethods
|
13
|
+
module Dio
|
14
|
+
# Create default global state to allow to use Dio features without any setup.
|
15
|
+
Equip.equip_dio(injector_id: :default, base_module: self)
|
16
|
+
|
17
|
+
# Creates a new Dio module with the specified {Dio::Injector}.
|
18
|
+
# You can use several injectors using this method.
|
19
|
+
#
|
20
|
+
# @param injector_id [Symbol]
|
21
|
+
# @param injector [Dio::Injector, nil]
|
22
|
+
# @return [Dio::ModuleBase] A module extends Dio::ModuleBase.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# class Some
|
26
|
+
# include Dio.use(:another_injector)
|
27
|
+
# # ...
|
28
|
+
# end
|
29
|
+
def self.use(injector_id, injector = nil)
|
30
|
+
Equip.equip_dio(
|
31
|
+
injector_id: injector_id,
|
32
|
+
state: @state,
|
33
|
+
base_module: Module.new,
|
34
|
+
injector: injector,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a default {Dio::Injector}.
|
39
|
+
# By default all dependencies are registered and loaded via this injector.
|
40
|
+
# Its injector ID is `:default`.
|
41
|
+
#
|
42
|
+
# @return [Dio::Injector]
|
43
|
+
def self.default_injector
|
44
|
+
injector
|
45
|
+
end
|
46
|
+
|
47
|
+
# Currently this method does nothing.
|
48
|
+
# This method is intended to be used in the following situation.
|
49
|
+
#
|
50
|
+
# - You use a dependencies provider class
|
51
|
+
# - The provider class is autoloadable (you can't `require` it explicitly)
|
52
|
+
#
|
53
|
+
# In that case, you need to autoload the provider class before using dependencies it provides.
|
54
|
+
# You can use this noop method for that purpose.
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# class MyProvider
|
58
|
+
# include Dio
|
59
|
+
#
|
60
|
+
# provide :foo { Foo.new }
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# # In another file. You can autoload MyProvider naturally like this.
|
64
|
+
# Dio.depends MyProvider
|
65
|
+
#
|
66
|
+
# class UsersController < ApplicationController
|
67
|
+
# include Dio::Rails::Controller
|
68
|
+
#
|
69
|
+
# inject do |dio|
|
70
|
+
# @foo = dio.load(:foo)
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# @see
|
74
|
+
# http://guides.rubyonrails.org/autoloading_and_reloading_constants.html
|
75
|
+
def self.depends(*_args); end
|
76
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dio
|
4
|
+
# Container stores dependency objects.
|
5
|
+
class Container
|
6
|
+
def initialize(factories = {})
|
7
|
+
@factories = factories
|
8
|
+
end
|
9
|
+
|
10
|
+
# Registers a dependency factory with a given key.
|
11
|
+
# If the key is already used, the previous factory is removed.
|
12
|
+
# @param key [Object]
|
13
|
+
# @param factory [Block]
|
14
|
+
# @return [Dio::Container]
|
15
|
+
def register(key, &factory)
|
16
|
+
@factories[key] = factory
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
# Registers all key value pairs of a given hash as dependencies.
|
21
|
+
#
|
22
|
+
# @param deps [Hash]
|
23
|
+
# @return [Dio::Container]
|
24
|
+
# @see Dio::Container#register
|
25
|
+
def register_all(deps)
|
26
|
+
deps.each do |key, factory|
|
27
|
+
register(key, &factory)
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return a given key is taken or not.
|
33
|
+
#
|
34
|
+
# @param key [Object]
|
35
|
+
# @return [boolean]
|
36
|
+
def registered?(key)
|
37
|
+
@factories.key?(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a factory associated with the given key.
|
41
|
+
#
|
42
|
+
# @param key [Object]
|
43
|
+
# @return [Proc]
|
44
|
+
def factory(key)
|
45
|
+
@factories[key]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Executes a factory of the given key and loads an object.
|
49
|
+
#
|
50
|
+
# @param key [Object]
|
51
|
+
# @param args [Array] They are passed to the factory proc as arguments.
|
52
|
+
# @return [Object]
|
53
|
+
def load(key, *args)
|
54
|
+
@factories[key]&.call(*args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/dio/equip.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dio/module_base'
|
4
|
+
require 'dio/state'
|
5
|
+
|
6
|
+
module Dio
|
7
|
+
# @api private
|
8
|
+
# Equip extends a given module to add Dio methods.
|
9
|
+
module Equip
|
10
|
+
def self.equip_dio(
|
11
|
+
injector_id:,
|
12
|
+
state: Dio::State.new,
|
13
|
+
base_module: Module.new,
|
14
|
+
injector: nil
|
15
|
+
)
|
16
|
+
state.register_injector(injector_id, injector)
|
17
|
+
base_module.tap do |m|
|
18
|
+
m.extend(ActiveSupport::Concern)
|
19
|
+
m.extend(Dio::ModuleBase)
|
20
|
+
m.instance_variable_set(:@state, state)
|
21
|
+
m.instance_variable_set(:@injector_id, injector_id)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/dio/injector.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'dio/container'
|
5
|
+
require 'dio/loader_factory'
|
6
|
+
|
7
|
+
module Dio
|
8
|
+
# Injector executes dependency injection.
|
9
|
+
# You can register and inject dependencies using this class.
|
10
|
+
class Injector
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def_delegators :@container, :registered?
|
14
|
+
|
15
|
+
# @!method wrap_load(clazz, &wrapper)
|
16
|
+
# Delagated.
|
17
|
+
# @see Dio::LoaderFactory#wrap_load
|
18
|
+
# @!method stub_deps(clazz, deps)
|
19
|
+
# Delagated.
|
20
|
+
# @see Dio::LoaderFactory#stub_deps
|
21
|
+
# @!method reset_loader(clazz = nil)
|
22
|
+
# Delagated.
|
23
|
+
# @see Dio::LoaderFactory#reset_loader
|
24
|
+
# @!method clear_stubs(clazz = nil)
|
25
|
+
# Delagated.
|
26
|
+
# @see Dio::LoaderFactory#clear_stubs
|
27
|
+
def_delegators :@loader_factory, :wrap_load, :stub_deps, :reset_loader, :clear_stubs
|
28
|
+
|
29
|
+
def initialize(container = Dio::Container.new, loader_factory = Dio::LoaderFactory.new)
|
30
|
+
@container = container
|
31
|
+
@loader_factory = loader_factory
|
32
|
+
@original_container = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Registers a new dependency with the given key.
|
36
|
+
# You can specify either an object or a factory block
|
37
|
+
# that creates an object.
|
38
|
+
#
|
39
|
+
# @param key [Object] Typically a class or a symbol.
|
40
|
+
# @param object [Object]
|
41
|
+
# @yield passed arguments when loading
|
42
|
+
# @return [Dio::Injector] self
|
43
|
+
def register(key, object = nil)
|
44
|
+
assert_register_args_valid(object, block_given?)
|
45
|
+
@container.register(key) do |*args|
|
46
|
+
object = yield(*args) if block_given?
|
47
|
+
injectable?(object) ? inject(object) : object
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Inject dependencies to the given object.
|
53
|
+
#
|
54
|
+
# @param target [Object]
|
55
|
+
# @return target
|
56
|
+
def inject(target)
|
57
|
+
unless injectable?(target)
|
58
|
+
raise ArgumentError, 'The given object does not include Dio module'
|
59
|
+
end
|
60
|
+
loader = @loader_factory.create(@container, target)
|
61
|
+
target.__dio_inject__(loader)
|
62
|
+
target
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates a new instance of the given class.
|
66
|
+
# Dio injects dependencies to the created instance.
|
67
|
+
#
|
68
|
+
# @param clazz [Class]
|
69
|
+
# @param args [Array]
|
70
|
+
# @return Instance of clazz
|
71
|
+
def create(clazz, *args)
|
72
|
+
raise ArgumentError, "#{clazz} is not a class" unless clazz.is_a?(Class)
|
73
|
+
inject(clazz.new(*args))
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def assert_register_args_valid(object, block_given)
|
79
|
+
return if (object || block_given) && !(object && block_given)
|
80
|
+
raise ArgumentError, 'You must specify either an object OR a block'
|
81
|
+
end
|
82
|
+
|
83
|
+
def injectable?(object)
|
84
|
+
object.respond_to?(:__dio_inject__)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dio/injector'
|
4
|
+
|
5
|
+
module Dio
|
6
|
+
# @api private
|
7
|
+
# InjectorStore manages Dio::Injector instances.
|
8
|
+
class InjectorStore
|
9
|
+
def initialize(injectors = {})
|
10
|
+
@injectors = injectors
|
11
|
+
end
|
12
|
+
|
13
|
+
def register(id, injector = nil)
|
14
|
+
if @injectors.key?(id)
|
15
|
+
injector ||= @injectors[id]
|
16
|
+
if injector != @injectors[id]
|
17
|
+
raise "Injector ID #{id} is already used for another injector"
|
18
|
+
end
|
19
|
+
elsif injector.nil?
|
20
|
+
injector = Dio::Injector.new
|
21
|
+
end
|
22
|
+
|
23
|
+
@injectors[id] = injector
|
24
|
+
end
|
25
|
+
|
26
|
+
def load(id)
|
27
|
+
@injectors[id]
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove(id)
|
31
|
+
@injectors.remove(id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def ids
|
35
|
+
@injectors.keys
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dio
|
4
|
+
# LoadContext provides some information about current loading.
|
5
|
+
class LoadContext
|
6
|
+
# @!attribute [r] key
|
7
|
+
# A key of a loaded dependency.
|
8
|
+
# @!attribute [r] target
|
9
|
+
# An instance which is injected to.
|
10
|
+
# @!attribute [r] args
|
11
|
+
# Passed arguments when loaded.
|
12
|
+
attr_reader :key, :target, :args
|
13
|
+
|
14
|
+
def initialize(key, target, args, loader)
|
15
|
+
@key = key
|
16
|
+
@target = target
|
17
|
+
@args = args
|
18
|
+
@loader = loader
|
19
|
+
end
|
20
|
+
|
21
|
+
# Loads a dependency. You can omit arguments because
|
22
|
+
# the LoadContext instance already has arguments for loading.
|
23
|
+
#
|
24
|
+
# @param args [Array]
|
25
|
+
# @return [Object] The dependency object.
|
26
|
+
def load(*args)
|
27
|
+
next_args = args.any? ? args : @args
|
28
|
+
@loader.call(@key, *next_args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dio/load_context'
|
4
|
+
|
5
|
+
module Dio
|
6
|
+
# LoaderFactory creates Loader.
|
7
|
+
# This also allows you to customize dependency loading.
|
8
|
+
class LoaderFactory
|
9
|
+
def initialize
|
10
|
+
@wrappers = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(container, target)
|
14
|
+
loader = loader_for(target, container)
|
15
|
+
Loader.new(loader)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Registers a process which wraps loading.
|
19
|
+
# Wrappers are run only when a given class is a target of injection.
|
20
|
+
#
|
21
|
+
# @param clazz [Class]
|
22
|
+
# @param wrapper [Block]
|
23
|
+
# @yield Dio::LoadContext
|
24
|
+
# @example
|
25
|
+
# Dio.wrap_load(UsersController) do |ctx|
|
26
|
+
# puts "loaded: #{ctx.key}"
|
27
|
+
# ctx.load
|
28
|
+
# end
|
29
|
+
def wrap_load(clazz, &wrapper)
|
30
|
+
@wrappers[clazz] = wrapper
|
31
|
+
end
|
32
|
+
|
33
|
+
# Registers a mock dependencies for a given class.
|
34
|
+
# When a registered dependency is loaded,
|
35
|
+
# the mock is returned instead of the actual one.
|
36
|
+
# This uses {#wrap_load} internally.
|
37
|
+
#
|
38
|
+
# @param clazz [Class]
|
39
|
+
# @param deps [Hash]
|
40
|
+
def stub_deps(clazz, deps)
|
41
|
+
wrap_load(clazz) do |ctx|
|
42
|
+
dep = deps[ctx.key]
|
43
|
+
next ctx.load unless dep
|
44
|
+
dep.respond_to?(:is_a?) && dep.is_a?(Proc) ? dep.call(*ctx.args) : dep
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Removes load wrappers registered via {#wrap_load} or {#stub_deps}.
|
49
|
+
# If you specify a class, only the wrappers for the class are removed.
|
50
|
+
#
|
51
|
+
# @param clazz [Class]
|
52
|
+
def reset_loader(clazz = nil)
|
53
|
+
return @wrappers.delete(clazz) if clazz
|
54
|
+
@wrappers = {}
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
alias clear_stubs reset_loader
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def loader_for(target, container)
|
62
|
+
actual_loader = container.method(:load)
|
63
|
+
wrapper = @wrappers[target.class]
|
64
|
+
return actual_loader unless wrapper
|
65
|
+
|
66
|
+
lambda { |key, *args|
|
67
|
+
context = LoadContext.new(key, target, args, actual_loader)
|
68
|
+
wrapper.call(context)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Loader just loads a dependency.
|
73
|
+
class Loader
|
74
|
+
def initialize(loader)
|
75
|
+
@loader = loader
|
76
|
+
end
|
77
|
+
|
78
|
+
# @see Dio::Container#load
|
79
|
+
def load(key, *args)
|
80
|
+
@loader.call(key, *args)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dio
|
4
|
+
# Dio::ModuleBase provides core methods as a mixin.
|
5
|
+
# {Dio} module extends this module so you can use all of the methods
|
6
|
+
# from Dio like `Dio.injector`.
|
7
|
+
module ModuleBase
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
# NOTE: A module who extends ModuleBase must define
|
11
|
+
# `@state` and @injector_id as class instance variables.
|
12
|
+
|
13
|
+
# Returns a injector associated with this module.
|
14
|
+
# If you call `Dio.injector`, it returns a default injector.
|
15
|
+
# If an ID is given, returns an injector of the ID.
|
16
|
+
#
|
17
|
+
# @param id [Symbol]
|
18
|
+
# @return [Dio::Injector]
|
19
|
+
def injector(id = nil)
|
20
|
+
@state.injector(id || @injector_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Injects dependencies using the injector this module has.
|
24
|
+
#
|
25
|
+
# @see
|
26
|
+
# Dio::Injector#inject
|
27
|
+
def inject(target)
|
28
|
+
injector.inject(target)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create an instance of the given class with injection.
|
32
|
+
#
|
33
|
+
# @see
|
34
|
+
# Dio::Injector#create
|
35
|
+
def create(clazz, *args)
|
36
|
+
injector.create(clazz, *args)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Reset a whole state.
|
40
|
+
# This is used mainly for tests.
|
41
|
+
def reset_state
|
42
|
+
@state.reset(@injector_id => Dio::Injector.new)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @see Dio::Injector#wrap_load
|
46
|
+
def wrap_load(clazz, &wrapper)
|
47
|
+
injector.wrap_load(clazz, &wrapper)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @see Dio::Injector#stub_deps
|
51
|
+
def stub_deps(clazz, deps)
|
52
|
+
injector.stub_deps(clazz, deps)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @see Dio::Injector#reset_loader
|
56
|
+
def reset_loader(clazz = nil)
|
57
|
+
injector.reset_loader(clazz)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @see Dio::Injector#clear_stubs
|
61
|
+
def clear_stubs(clazz = nil)
|
62
|
+
injector.clear_stubs(clazz)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Add some methods to a class which includes Dio module.
|
68
|
+
def included(base)
|
69
|
+
my_injector = injector
|
70
|
+
injector_holder = Module.new do
|
71
|
+
define_method :__dio_injector__ do
|
72
|
+
my_injector
|
73
|
+
end
|
74
|
+
end
|
75
|
+
base.extend(ClassMethods, injector_holder)
|
76
|
+
base.include(InstanceMethods)
|
77
|
+
end
|
78
|
+
|
79
|
+
# InstanceMethods defines instance methods for classes using Dio.
|
80
|
+
module InstanceMethods
|
81
|
+
# @api private
|
82
|
+
def __dio_inject__(loader)
|
83
|
+
injection_proc = self.class.__dio_injection_proc__
|
84
|
+
instance_exec loader, &injection_proc if injection_proc
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# ClassMethods defines class methods for classes using Dio.
|
89
|
+
module ClassMethods
|
90
|
+
# Declares the class is an injectable via Dio.
|
91
|
+
# You can define a factory block.
|
92
|
+
#
|
93
|
+
# @param subkey [Symbol, nil]
|
94
|
+
# @yield passed arguments when loading
|
95
|
+
def injectable(subkey = nil, &block)
|
96
|
+
key = subkey ? [self, subkey] : self
|
97
|
+
factory = block || ->(*args) { new(*args) }
|
98
|
+
__dio_injector__.register(key, &factory)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Registers a factory of a dependency.
|
102
|
+
#
|
103
|
+
# @param key [Symbol]
|
104
|
+
# @yield passed arguments when loading
|
105
|
+
def provide(key, &factory)
|
106
|
+
raise "You must define a factory of #{key}" unless block_given?
|
107
|
+
__dio_injector__.register(key, &factory)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Defines a block to load dependencies from Dio.
|
111
|
+
# A given block is evaluated in each instance context of the class.
|
112
|
+
#
|
113
|
+
# @yield [Dio::LoaderFactory::Loader]
|
114
|
+
def inject(&injector)
|
115
|
+
@__dio_injection_proc__ = injector
|
116
|
+
end
|
117
|
+
|
118
|
+
# @api private
|
119
|
+
def __dio_injection_proc__
|
120
|
+
@__dio_injection_proc__
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/dio/rails.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dio'
|
4
|
+
|
5
|
+
module Dio
|
6
|
+
module Rails
|
7
|
+
# Dio::Rails::Controller enables to inject dependencies to Rails controllers.
|
8
|
+
# Internally, this just add a `before_action` that injects dependencies.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# class UsersController < ApplicationController
|
12
|
+
# include Dio::Rails::Controller
|
13
|
+
#
|
14
|
+
# inject do |dio|
|
15
|
+
# @api = dio.load(UsersAPI)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # ...
|
19
|
+
# end
|
20
|
+
module Controller
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
DioForController = Dio.use(:default)
|
24
|
+
include DioForController
|
25
|
+
private_constant :DioForController
|
26
|
+
|
27
|
+
included do
|
28
|
+
before_action do
|
29
|
+
DioForController.inject(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dio'
|
4
|
+
|
5
|
+
module Dio
|
6
|
+
module Rails
|
7
|
+
# Dio::Rails::Model enables to inject dependencies to Rails models.
|
8
|
+
# Internally, this just add a `after_initialize` that injects dependencies.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# class User < ApplicationRecord
|
12
|
+
# include Dio::Rails::Model
|
13
|
+
#
|
14
|
+
# inject do |dio|
|
15
|
+
# @api = dio.load(UsersAPI)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # ...
|
19
|
+
# end
|
20
|
+
module Model
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
DioForModel = Dio.use(:default)
|
24
|
+
include DioForModel
|
25
|
+
private_constant :DioForModel
|
26
|
+
|
27
|
+
included do
|
28
|
+
after_initialize do |model|
|
29
|
+
DioForModel.inject(model)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/dio/state.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dio/injector_store'
|
4
|
+
|
5
|
+
module Dio
|
6
|
+
# @api private
|
7
|
+
# Dio::State holds some global states.
|
8
|
+
# This is used internally.
|
9
|
+
class State
|
10
|
+
def initialize(injectors = Dio::InjectorStore.new)
|
11
|
+
@injectors = injectors
|
12
|
+
end
|
13
|
+
|
14
|
+
def register_injector(id, injector = nil)
|
15
|
+
@injectors.register(id, injector)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Load an injector from the given ID.
|
19
|
+
def injector(id)
|
20
|
+
@injectors.load(id)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Reset whole states.
|
24
|
+
def reset(injectors = {})
|
25
|
+
@injectors = Dio::InjectorStore.new(injectors)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/dio/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dio-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ryym
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-mocks
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Dio enables you to do Dependency Injection in Rails.
|
112
|
+
email:
|
113
|
+
- ryym.64@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- MIT-LICENSE
|
119
|
+
- README.md
|
120
|
+
- Rakefile
|
121
|
+
- lib/dio.rb
|
122
|
+
- lib/dio/container.rb
|
123
|
+
- lib/dio/equip.rb
|
124
|
+
- lib/dio/injector.rb
|
125
|
+
- lib/dio/injector_store.rb
|
126
|
+
- lib/dio/load_context.rb
|
127
|
+
- lib/dio/loader_factory.rb
|
128
|
+
- lib/dio/module_base.rb
|
129
|
+
- lib/dio/rails.rb
|
130
|
+
- lib/dio/rails/controller.rb
|
131
|
+
- lib/dio/rails/model.rb
|
132
|
+
- lib/dio/state.rb
|
133
|
+
- lib/dio/version.rb
|
134
|
+
homepage: https://github.com/ryym/dio
|
135
|
+
licenses:
|
136
|
+
- MIT
|
137
|
+
metadata: {}
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 2.6.11
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: DI for Rails
|
158
|
+
test_files: []
|