dio-rails 0.1.0

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