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.
@@ -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
@@ -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.
@@ -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.
@@ -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']
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dio'
4
+ require 'dio/rails/controller'
5
+ require 'dio/rails/model'
6
+
7
+ module Dio
8
+ # Dio::Rails enables to use Dio in Ruby on Rails.
9
+ module Rails
10
+ end
11
+ end
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dio
4
+ VERSION = '0.1.0'
5
+ end
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: []