injectable 0.0.5 → 1.0.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/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +291 -142
- data/Rakefile +2 -26
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/injectable.gemspec +28 -0
- data/lib/injectable.rb +57 -46
- data/lib/injectable/class_methods.rb +157 -0
- data/lib/injectable/dependencies_graph.rb +43 -0
- data/lib/injectable/dependencies_proxy.rb +29 -0
- data/lib/injectable/dependency.rb +40 -0
- data/lib/injectable/instance_methods.rb +53 -0
- data/lib/injectable/missing_dependencies_exception.rb +4 -0
- data/lib/injectable/version.rb +1 -2
- metadata +82 -31
- data/LICENSE +0 -20
- data/lib/injectable/container.rb +0 -127
- data/lib/injectable/inflector.rb +0 -30
- data/lib/injectable/macros.rb +0 -60
- data/lib/injectable/registerable.rb +0 -26
- data/lib/injectable/registry.rb +0 -89
@@ -0,0 +1,53 @@
|
|
1
|
+
module Injectable
|
2
|
+
module InstanceMethods
|
3
|
+
# Initialize the service with the dependencies injected
|
4
|
+
def initialize(args = {})
|
5
|
+
check_missing_arguments!(self.class.required_initialize_arguments, args)
|
6
|
+
variables_for!(self.class.initialize_arguments, args)
|
7
|
+
variables_from_dependencies!(args)
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
# Entry point of the service.
|
12
|
+
# Arguments for this method should be declared explicitly with '.argument'
|
13
|
+
# and declare this method without arguments
|
14
|
+
def call(args = {})
|
15
|
+
check_call_definition!
|
16
|
+
check_missing_arguments!(self.class.required_call_arguments, args)
|
17
|
+
variables_for!(self.class.call_arguments, args)
|
18
|
+
super()
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def check_call_definition!
|
24
|
+
return if (self.class.ancestors - [Injectable::InstanceMethods]).any? do |ancestor|
|
25
|
+
ancestor.instance_methods(false).include?(:call)
|
26
|
+
end
|
27
|
+
raise NoMethodError, "A #call method with zero arity must be defined in #{self.class}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_missing_arguments!(expected, args)
|
31
|
+
missing = expected - args.keys
|
32
|
+
return if missing.empty?
|
33
|
+
raise ArgumentError, "missing keywords: #{missing.join(',')}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def variables_for!(subject, args)
|
37
|
+
subject.each do |arg, options|
|
38
|
+
instance_variable_set("@#{arg}", args.fetch(arg) { options[:default] })
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def variables_from_dependencies!(args)
|
43
|
+
self.class.dependencies.names.each do |name|
|
44
|
+
next if self.class.initialize_arguments.key?(name)
|
45
|
+
instance_variable_set("@#{name}", args[name]) if args.key?(name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def dependencies_proxy
|
50
|
+
@dependencies_proxy ||= self.class.dependencies.proxy
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/injectable/version.rb
CHANGED
metadata
CHANGED
@@ -1,61 +1,112 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: injectable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
|
-
-
|
7
|
+
- Papipo
|
8
|
+
- iovis9
|
9
|
+
- jantequera
|
10
|
+
- amrocco
|
9
11
|
autorequire:
|
10
|
-
bindir:
|
12
|
+
bindir: exe
|
11
13
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
|
14
|
+
date: 2019-05-30 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: bundler
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2.0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - "~>"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '12.0'
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '12.0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rspec
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - "~>"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '3.0'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '3.0'
|
58
|
+
description:
|
15
59
|
email:
|
16
|
-
-
|
60
|
+
- rodrigo@rubiconmd.com
|
61
|
+
- david.marchante@rubiconmd.com
|
62
|
+
- julio@rubiconmd.com
|
63
|
+
- anthony@rubiconmd.com
|
17
64
|
executables: []
|
18
65
|
extensions: []
|
19
66
|
extra_rdoc_files: []
|
20
67
|
files:
|
21
|
-
-
|
22
|
-
-
|
23
|
-
-
|
24
|
-
-
|
25
|
-
-
|
26
|
-
-
|
27
|
-
-
|
68
|
+
- ".gitignore"
|
69
|
+
- ".rspec"
|
70
|
+
- ".travis.yml"
|
71
|
+
- CODE_OF_CONDUCT.md
|
72
|
+
- Gemfile
|
73
|
+
- Gemfile.lock
|
74
|
+
- LICENSE.txt
|
28
75
|
- README.md
|
29
|
-
- LICENSE
|
30
76
|
- Rakefile
|
31
|
-
|
32
|
-
|
77
|
+
- bin/console
|
78
|
+
- bin/setup
|
79
|
+
- injectable.gemspec
|
80
|
+
- lib/injectable.rb
|
81
|
+
- lib/injectable/class_methods.rb
|
82
|
+
- lib/injectable/dependencies_graph.rb
|
83
|
+
- lib/injectable/dependencies_proxy.rb
|
84
|
+
- lib/injectable/dependency.rb
|
85
|
+
- lib/injectable/instance_methods.rb
|
86
|
+
- lib/injectable/missing_dependencies_exception.rb
|
87
|
+
- lib/injectable/version.rb
|
88
|
+
homepage: https://github.com/rubiconmd/injectable
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
33
92
|
post_install_message:
|
34
93
|
rdoc_options: []
|
35
94
|
require_paths:
|
36
95
|
- lib
|
37
96
|
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
-
none: false
|
39
97
|
requirements:
|
40
|
-
- -
|
98
|
+
- - ">="
|
41
99
|
- !ruby/object:Gem::Version
|
42
100
|
version: '0'
|
43
|
-
segments:
|
44
|
-
- 0
|
45
|
-
hash: -1355353312328350373
|
46
101
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
-
none: false
|
48
102
|
requirements:
|
49
|
-
- -
|
103
|
+
- - ">="
|
50
104
|
- !ruby/object:Gem::Version
|
51
105
|
version: '0'
|
52
|
-
segments:
|
53
|
-
- 0
|
54
|
-
hash: -1355353312328350373
|
55
106
|
requirements: []
|
56
107
|
rubyforge_project:
|
57
|
-
rubygems_version:
|
108
|
+
rubygems_version: 2.7.6
|
58
109
|
signing_key:
|
59
|
-
specification_version:
|
60
|
-
summary:
|
110
|
+
specification_version: 4
|
111
|
+
summary: A library to help you build nice service objects with dependency injection.
|
61
112
|
test_files: []
|
data/LICENSE
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
Copyright (c) 2012 Durran Jordan
|
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/lib/injectable/container.rb
DELETED
@@ -1,127 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require "injectable/registerable"
|
3
|
-
|
4
|
-
module Injectable
|
5
|
-
|
6
|
-
# A simple container that can resolve dependencies.
|
7
|
-
#
|
8
|
-
# @since 0.0.0
|
9
|
-
class Container
|
10
|
-
include Registerable
|
11
|
-
|
12
|
-
# Get an instance of an object from the container with the provided class.
|
13
|
-
#
|
14
|
-
# @example Get an instance of an object for class UserService.
|
15
|
-
# container.get(:user_service)
|
16
|
-
#
|
17
|
-
# @param [ Symbol ] name the role which the returned object should perform.
|
18
|
-
#
|
19
|
-
# @return [ Object ] The instantiated object.
|
20
|
-
#
|
21
|
-
# @raise [ Injectable::RoleNotRegistered ] if queried for a role which is not
|
22
|
-
# registered
|
23
|
-
#
|
24
|
-
# @since 0.0.0
|
25
|
-
def get(name)
|
26
|
-
classes = registered_implementations(name)
|
27
|
-
if klass = instantiated_class(classes)
|
28
|
-
instantiated_objects[klass]
|
29
|
-
else
|
30
|
-
object = instantiate(classes)
|
31
|
-
instantiated_objects[object.class] = object
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Create a new container with the objects needed to resolve dependencies
|
36
|
-
# and create new objects.
|
37
|
-
#
|
38
|
-
# @example Create the new container.
|
39
|
-
# Injectable::Container.new(user, user_finder)
|
40
|
-
#
|
41
|
-
# @param [ Array<Object> ] objects The dependencies.
|
42
|
-
#
|
43
|
-
# @since 0.0.0
|
44
|
-
def initialize(*objects)
|
45
|
-
objects.each do |object|
|
46
|
-
instantiated_objects[object.class] = object
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Put objects into the container. Can be a single or multiple instances.
|
51
|
-
#
|
52
|
-
# @example Put an object in the container.
|
53
|
-
# container.put(user)
|
54
|
-
#
|
55
|
-
# @example Put multiple objects in the container.
|
56
|
-
# container.put(user, user_finder)
|
57
|
-
#
|
58
|
-
# @param [ *Object ] objects The objects to put in the container.
|
59
|
-
#
|
60
|
-
# @return [ Injectable::Container ] The container itself.
|
61
|
-
#
|
62
|
-
# @since 0.0.0
|
63
|
-
def put(*objects)
|
64
|
-
objects.flatten.each do |object|
|
65
|
-
instantiated_objects[object.class] = object
|
66
|
-
end and self
|
67
|
-
end
|
68
|
-
|
69
|
-
# This error is raised when asking for an object out of the container that
|
70
|
-
# cannot be resolved.
|
71
|
-
#
|
72
|
-
# @since 0.0.4
|
73
|
-
class Unresolvable < Exception
|
74
|
-
|
75
|
-
# @attribute [r] classes The classes that were attempted to instantiate.
|
76
|
-
attr_reader :classes
|
77
|
-
|
78
|
-
# Initialize the new error.
|
79
|
-
#
|
80
|
-
# @example Initialize the error.
|
81
|
-
# Unresolvable.new([ Persistable, Saveable ])
|
82
|
-
#
|
83
|
-
# @param [ Array<Class> ] classes The classes that were attempted to
|
84
|
-
# instantiate with.
|
85
|
-
#
|
86
|
-
# @since 0.0.4
|
87
|
-
def initialize(classes)
|
88
|
-
@classes = classes
|
89
|
-
super(
|
90
|
-
"Could not instantiate an object for any of: #{classes.join(", ")}. " +
|
91
|
-
"Please ensure all required dependencies are in the container."
|
92
|
-
)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
def dependencies(klass)
|
99
|
-
(Registry.signature(klass) || []).map do |dependency|
|
100
|
-
get(dependency)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def registered_implementations(name)
|
105
|
-
implementations[name] || Registry.implementation(name)
|
106
|
-
end
|
107
|
-
|
108
|
-
def instantiate(classes)
|
109
|
-
classes.each do |klass|
|
110
|
-
begin
|
111
|
-
return klass.new(*dependencies(klass))
|
112
|
-
rescue ArgumentError; end
|
113
|
-
end
|
114
|
-
raise Unresolvable.new(classes)
|
115
|
-
end
|
116
|
-
|
117
|
-
def instantiated_objects
|
118
|
-
@instantiated_objects ||= {}
|
119
|
-
end
|
120
|
-
|
121
|
-
def instantiated_class(classes)
|
122
|
-
classes.detect do |klass|
|
123
|
-
instantiated_objects[klass]
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
data/lib/injectable/inflector.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Injectable
|
3
|
-
|
4
|
-
# Used so we don't need active support around to perform inflections.
|
5
|
-
#
|
6
|
-
# @since 0.0.4
|
7
|
-
module Inflector
|
8
|
-
|
9
|
-
class << self
|
10
|
-
|
11
|
-
# Underscore a string. This is partially taken from Active Support, but
|
12
|
-
# dumbed down for our purposes - we don't handle namespacing.
|
13
|
-
#
|
14
|
-
# @example Underscore a string.
|
15
|
-
# Inflector.underscore("Band")
|
16
|
-
#
|
17
|
-
# @param [ String ] string The string to underscore.
|
18
|
-
#
|
19
|
-
# @since 0.0.4
|
20
|
-
#
|
21
|
-
# @return [ String ] The underscored string.
|
22
|
-
def underscore(string)
|
23
|
-
value = string.dup
|
24
|
-
value.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
25
|
-
value.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
26
|
-
value.downcase!
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
data/lib/injectable/macros.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Injectable
|
3
|
-
|
4
|
-
# Provides class level macros for setting up dependencies.
|
5
|
-
#
|
6
|
-
# @since 0.0.0
|
7
|
-
module Macros
|
8
|
-
|
9
|
-
# Sets up the dependencies for the class.
|
10
|
-
#
|
11
|
-
# @example Define a UserService that has two dependencies.
|
12
|
-
# class User; end
|
13
|
-
# class UserFinder; end
|
14
|
-
#
|
15
|
-
# class UserService
|
16
|
-
# include Injectable
|
17
|
-
# dependencies :user, :user_finder
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# @note A constructor will get created for the object that takes the same
|
21
|
-
# number or arguments as provided to the dependencies macro. The types
|
22
|
-
# of these arguments must match the "classified" name of the provided
|
23
|
-
# symbol. For example :user would be a User class, :user_finder would be
|
24
|
-
# a UserFinder class. Order matters.
|
25
|
-
#
|
26
|
-
# @param [ Array<Symbol> ] injectables The dependency list.
|
27
|
-
#
|
28
|
-
# @since 0.0.0
|
29
|
-
def dependencies(*injectables)
|
30
|
-
define_constructor(*injectables)
|
31
|
-
define_readers(*injectables)
|
32
|
-
Registry.register_signature(self, injectables)
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def define_constructor(*injectables)
|
38
|
-
names = names(*injectables)
|
39
|
-
class_eval <<-CONST
|
40
|
-
def initialize(#{names})
|
41
|
-
#{ivars(*injectables)} = #{names}
|
42
|
-
end
|
43
|
-
CONST
|
44
|
-
end
|
45
|
-
|
46
|
-
def define_readers(*injectables)
|
47
|
-
injectables.each do |dependency|
|
48
|
-
attr_reader(dependency)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def ivars(*injectables)
|
53
|
-
injectables.map { |name| "@#{name}" }.join(",")
|
54
|
-
end
|
55
|
-
|
56
|
-
def names(*injectables)
|
57
|
-
injectables.join(",")
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|