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