callme 0.5.0 → 0.6.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +1 -1
- data/README.md +18 -15
- data/lib/callme.rb +15 -0
- data/lib/callme/const_loaders/active_support.rb +1 -0
- data/lib/callme/const_loaders/native.rb +1 -0
- data/lib/callme/container.rb +45 -54
- data/lib/callme/contract_validator.rb +39 -0
- data/lib/callme/dep_factory.rb +119 -0
- data/lib/callme/{bean_metadata.rb → dep_metadata.rb} +18 -13
- data/lib/callme/deps_metadata_storage.rb +32 -0
- data/lib/callme/errors.rb +17 -3
- data/lib/callme/inject.rb +22 -21
- data/lib/callme/scopes/prototype_scope.rb +14 -14
- data/lib/callme/scopes/request_scope.rb +19 -18
- data/lib/callme/scopes/singleton_scope.rb +19 -18
- data/lib/callme/version.rb +1 -1
- data/spec/callme/container_spec.rb +45 -41
- data/spec/callme/contract_spec.rb +72 -0
- data/spec/callme/inject_spec.rb +7 -4
- metadata +9 -5
- data/lib/callme/bean_factory.rb +0 -115
- data/lib/callme/beans_metadata_storage.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f73aca55d583eee628366c833117fbb17be432f
|
4
|
+
data.tar.gz: e057e62c608d928460886bf84ccb8d3ebb31f0dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc3406c637301c9813dfbe08cbc5c874778439f63e51800d2b9593f2fd4b8690b957463a73cce7f43d84a968db339e8244412b50c31dc53a1e35deaf0f814f61
|
7
|
+
data.tar.gz: 4352b39b8c1224621e30a58d28e3797b62ec7c519183090441aacf072b70c29f0227399d85768d09632852d3c671c6c197fb8ffe447eba1267463719d01c0682
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
## 0.6 (2017-06-27)
|
2
|
+
|
3
|
+
Features:
|
4
|
+
|
5
|
+
- rename `beans` to `deps` (names are important!)
|
6
|
+
- no more global `Object.inject` method, use `include Callme::Inject` instead
|
7
|
+
|
8
|
+
|
9
|
+
## 0.5 (2017-06-26)
|
10
|
+
|
11
|
+
Features:
|
12
|
+
|
13
|
+
- rename project from `ioc_rb` to `callme`, to pick up the stalled development
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Callme [](https://travis-ci.org/mindreframer/callme) [](https://codeclimate.com/github/mindreframer/callme)
|
2
2
|
|
3
3
|
|
4
4
|
|
@@ -30,11 +30,11 @@ logger.info('some message')
|
|
30
30
|
```
|
31
31
|
|
32
32
|
Callme eliminates the manual injection step and injects dependencies by itself.
|
33
|
-
To use it you need to instantiate Callme::Container and pass dependency definitions(we call them
|
33
|
+
To use it you need to instantiate Callme::Container and pass dependency definitions(we call them deps) to it:
|
34
34
|
```ruby
|
35
35
|
container = Callme::Container.new do |c|
|
36
|
-
c.
|
37
|
-
c.
|
36
|
+
c.dep(:appender, class: Appender)
|
37
|
+
c.dep(:logger, class: Logger) do
|
38
38
|
attr :appender, ref: :appender
|
39
39
|
end
|
40
40
|
end
|
@@ -48,6 +48,7 @@ logger.info('some message')
|
|
48
48
|
To simplify injection Callme allows you specify dependencies inside of your class:
|
49
49
|
```ruby
|
50
50
|
class Logger
|
51
|
+
include Callme::Inject
|
51
52
|
inject :appender
|
52
53
|
|
53
54
|
def info(message)
|
@@ -58,11 +59,11 @@ end
|
|
58
59
|
class Appender
|
59
60
|
end
|
60
61
|
```
|
61
|
-
With `inject` keyword you won't need to specify class dependencies in
|
62
|
+
With `inject` keyword you won't need to specify class dependencies in dep definition:
|
62
63
|
```ruby
|
63
64
|
container = Callme::Container.new do |c|
|
64
|
-
c.
|
65
|
-
c.
|
65
|
+
c.dep(:appender, class: Appender)
|
66
|
+
c.dep(:logger, class: Logger)
|
66
67
|
end
|
67
68
|
```
|
68
69
|
|
@@ -70,14 +71,16 @@ end
|
|
70
71
|
|
71
72
|
## Inheriting from other containers
|
72
73
|
Quite often you will want to selectively override some parts of the system, use `Callme::Container.with_parent` to
|
73
|
-
create a new container with all the
|
74
|
+
create a new container with all the deps copied from the parent container.
|
74
75
|
|
75
76
|
```ruby
|
76
77
|
class ContactBook
|
78
|
+
include Callme::Inject
|
77
79
|
inject :contacts_repository
|
78
80
|
inject :validator, ref: :contact_validator
|
79
81
|
end
|
80
82
|
class ContactBookService
|
83
|
+
include Callme::Inject
|
81
84
|
inject :contacts_repository
|
82
85
|
inject :validator, ref: :contact_validator
|
83
86
|
end
|
@@ -92,22 +95,22 @@ class AnotherTestContactValidator
|
|
92
95
|
end
|
93
96
|
|
94
97
|
parent = Callme::Container.new do |c|
|
95
|
-
c.
|
96
|
-
c.
|
97
|
-
c.
|
98
|
-
c.
|
98
|
+
c.dep(:contacts_repository, class: ContactsRepository)
|
99
|
+
c.dep(:contact_validator, class: ContactValidator)
|
100
|
+
c.dep(:contact_book, class: ContactBook)
|
101
|
+
c.dep(:contact_book_service, class: "ContactBookService")
|
99
102
|
end
|
100
103
|
puts parent[:contact_book_service].validator.class
|
101
104
|
#=> ContactValidator
|
102
105
|
|
103
106
|
testcontainer = Callme::Container.with_parent(parent) do |c|
|
104
|
-
c.
|
107
|
+
c.dep(:contact_validator, class: TestContactValidator)
|
105
108
|
end
|
106
109
|
puts testcontainer[:contact_book_service].validator.class
|
107
110
|
#=> TestContactValidator
|
108
111
|
|
109
112
|
third = Callme::Container.with_parent(parent) do |c|
|
110
|
-
c.
|
113
|
+
c.dep(:contact_validator, class: AnotherTestContactValidator)
|
111
114
|
end
|
112
115
|
|
113
116
|
puts third[:contact_book_service].validator.class
|
@@ -139,7 +142,7 @@ Or install it yourself as:
|
|
139
142
|
|
140
143
|
# TODO
|
141
144
|
1. Constructor based injection
|
142
|
-
2. Scope registration, refactor
|
145
|
+
2. Scope registration, refactor DepFactory. Callme:Container.register_scope(SomeScope)
|
143
146
|
3. Write documentation with more examples
|
144
147
|
|
145
148
|
## Author
|
data/lib/callme.rb
CHANGED
@@ -1,4 +1,19 @@
|
|
1
1
|
require 'ext/vendored_activesupport'
|
2
2
|
require 'callme/version'
|
3
|
+
|
4
|
+
require 'callme/const_loaders/native'
|
5
|
+
require 'callme/args_validator'
|
6
|
+
require 'callme/contract_validator'
|
7
|
+
require 'callme/errors'
|
3
8
|
require 'callme/inject'
|
4
9
|
require 'callme/container'
|
10
|
+
|
11
|
+
require 'callme/dep_metadata'
|
12
|
+
require 'callme/deps_metadata_storage'
|
13
|
+
|
14
|
+
require 'callme/scopes'
|
15
|
+
require 'callme/scopes/singleton_scope'
|
16
|
+
require 'callme/scopes/prototype_scope'
|
17
|
+
require 'callme/scopes/request_scope'
|
18
|
+
|
19
|
+
require 'callme/dep_factory'
|
data/lib/callme/container.rb
CHANGED
@@ -1,34 +1,27 @@
|
|
1
|
-
require 'callme/errors'
|
2
|
-
require 'callme/args_validator'
|
3
|
-
require 'callme/bean_metadata'
|
4
|
-
require 'callme/beans_metadata_storage'
|
5
|
-
require 'callme/bean_factory'
|
6
|
-
require 'callme/const_loaders/native'
|
7
|
-
|
8
1
|
module Callme
|
9
2
|
|
10
3
|
# Callme::Container is the central data store for registering objects
|
11
4
|
# used for dependency injection. Users register classes by
|
12
|
-
# providing a name and a class to create the object(we call them
|
5
|
+
# providing a name and a class to create the object(we call them deps). Deps
|
13
6
|
# may be retrieved by asking for them by name (via the [] operator)
|
14
7
|
class Container
|
15
8
|
DEFAULT_CONST_LOADER = Callme::ConstLoaders::Native
|
16
9
|
|
17
10
|
# Constructor
|
18
|
-
# @param resources [Array] array of procs with container's
|
19
|
-
# @param &block [Proc] optional proc with container's
|
11
|
+
# @param resources [Array] array of procs with container's deps definitions
|
12
|
+
# @param &block [Proc] optional proc with container's deps definitions
|
20
13
|
def initialize(const_loader = DEFAULT_CONST_LOADER, &block)
|
21
|
-
@const_loader
|
22
|
-
@
|
23
|
-
@
|
14
|
+
@const_loader = const_loader
|
15
|
+
@deps_metadata_storage = Callme::DepsMetadataStorage.new
|
16
|
+
@dep_factory = Callme::DepFactory.new(const_loader, @deps_metadata_storage)
|
24
17
|
|
25
18
|
block.call(self) if block_given?
|
26
19
|
end
|
27
20
|
|
28
21
|
# Evaluates the given array of blocks on the container instance
|
29
|
-
# what adds new
|
30
|
-
# @param resources [Array] array of procs with container's
|
31
|
-
def self.
|
22
|
+
# what adds new dep definitions to the container
|
23
|
+
# @param resources [Array] array of procs with container's deps definitions
|
24
|
+
def self.new_with_deps(resources, const_loader = DEFAULT_CONST_LOADER)
|
32
25
|
Callme::ArgsValidator.is_array!(resources, :resources)
|
33
26
|
|
34
27
|
self.new(const_loader).tap do |container|
|
@@ -39,69 +32,67 @@ module Callme
|
|
39
32
|
end
|
40
33
|
|
41
34
|
def self.with_parent(parent_container, &block)
|
42
|
-
const_loader
|
43
|
-
|
44
|
-
container
|
35
|
+
const_loader = parent_container.instance_variable_get("@const_loader")
|
36
|
+
deps_metadata_storage = parent_container.instance_variable_get("@deps_metadata_storage").copy
|
37
|
+
container = self.new(const_loader)
|
45
38
|
container.instance_eval do
|
46
|
-
@
|
47
|
-
@
|
39
|
+
@deps_metadata_storage = deps_metadata_storage
|
40
|
+
@dep_factory = Callme::DepFactory.new(const_loader, deps_metadata_storage)
|
48
41
|
end
|
49
42
|
block.call(container) if block_given?
|
50
43
|
container
|
51
44
|
end
|
52
45
|
|
53
|
-
# Registers new
|
54
|
-
# @param
|
55
|
-
# @param options [Hash] includes
|
56
|
-
# @param &block [Proc] the block which describes
|
57
|
-
# see more in the
|
58
|
-
def
|
59
|
-
Callme::ArgsValidator.is_symbol!(
|
46
|
+
# Registers new dep in container
|
47
|
+
# @param dep_name [Symbol] dep name
|
48
|
+
# @param options [Hash] includes dep class and dep scope
|
49
|
+
# @param &block [Proc] the block which describes dep dependencies,
|
50
|
+
# see more in the DepMetadata
|
51
|
+
def dep(dep_name, options, &block)
|
52
|
+
Callme::ArgsValidator.is_symbol!(dep_name, :dep_name)
|
60
53
|
Callme::ArgsValidator.is_hash!(options, :options)
|
61
54
|
|
62
|
-
|
63
|
-
@
|
55
|
+
dep = Callme::DepMetadata.new(dep_name, options, &block)
|
56
|
+
@deps_metadata_storage.put(dep)
|
64
57
|
end
|
65
58
|
|
66
|
-
# Registers new
|
67
|
-
# @param
|
68
|
-
# @param options [Hash] includes
|
69
|
-
# @param &block [Proc] the block which describes
|
70
|
-
# see more in the
|
71
|
-
def
|
72
|
-
if @
|
73
|
-
@
|
59
|
+
# Registers new dep in container and replace existing instance if it's instantiated
|
60
|
+
# @param dep_name [Symbol] dep name
|
61
|
+
# @param options [Hash] includes dep class and dep scope
|
62
|
+
# @param &block [Proc] the block which describes dep dependencies,
|
63
|
+
# see more in the DepMetadata
|
64
|
+
def replace_dep(dep_name, options, &block)
|
65
|
+
if @dep_factory.get_dep(dep_name)
|
66
|
+
@dep_factory.delete_dep(dep_name)
|
74
67
|
end
|
75
|
-
|
68
|
+
dep(dep_name, options, &block)
|
76
69
|
end
|
77
70
|
|
78
71
|
def reset!
|
79
|
-
@
|
72
|
+
@dep_factory = Callme::DepFactory.new(@const_loader, @deps_metadata_storage)
|
80
73
|
end
|
81
74
|
|
82
|
-
# Returns
|
83
|
-
# by the specified
|
84
|
-
# @param name [Symbol]
|
85
|
-
# @return
|
75
|
+
# Returns dep instance from the container
|
76
|
+
# by the specified dep name
|
77
|
+
# @param name [Symbol] dep name
|
78
|
+
# @return dep instance
|
86
79
|
def [](name)
|
87
|
-
Callme::ArgsValidator.is_symbol!(name, :
|
88
|
-
return @
|
80
|
+
Callme::ArgsValidator.is_symbol!(name, :dep_name)
|
81
|
+
return @dep_factory.get_dep(name)
|
89
82
|
end
|
90
83
|
|
91
84
|
def keys
|
92
|
-
@
|
85
|
+
@deps_metadata_storage.keys
|
93
86
|
end
|
94
87
|
|
95
|
-
# Load defined in
|
88
|
+
# Load defined in dep classes
|
96
89
|
# this is needed for production usage
|
97
90
|
# for eager loading
|
98
|
-
def
|
99
|
-
@
|
100
|
-
|
101
|
-
|
102
|
-
end
|
91
|
+
def eager_load_dep_classes
|
92
|
+
@deps_metadata_storage.values.each do |dep_metadata|
|
93
|
+
@const_loader.load_const(dep_metadata.dep_class)
|
94
|
+
@const_loader.load_const(dep_metadata.contract) if dep_metadata.contract
|
103
95
|
end
|
104
96
|
end
|
105
|
-
|
106
97
|
end
|
107
98
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Callme
|
2
|
+
# inspired by https://github.com/Sage/sinject/blob/master/lib/sinject/container.rb#L127
|
3
|
+
class ContractValidator
|
4
|
+
def validate(dependency_class, contract_class, const_loader)
|
5
|
+
contract_class = const_loader.load_const(contract_class) if contract_class.is_a?(String)
|
6
|
+
#get the methods defined for the contract
|
7
|
+
contract_methods = (contract_class.instance_methods - Object.instance_methods)
|
8
|
+
#get the methods defined for the dependency
|
9
|
+
dependency_methods = (dependency_class.instance_methods - Object.instance_methods)
|
10
|
+
#calculate any methods specified in the contract that are not specified in the dependency
|
11
|
+
missing_methods = contract_methods - dependency_methods
|
12
|
+
|
13
|
+
if !missing_methods.empty?
|
14
|
+
raise Callme::Errors::DependencyContractMissingMethodsException.new({
|
15
|
+
dep: dependency_class,
|
16
|
+
contract: contract_class,
|
17
|
+
missing: missing_methods
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
validate_methods(dependency_class, contract_class, contract_methods)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def validate_methods(dependency_class, contract_class, contract_methods)
|
27
|
+
contract_methods.each do |method|
|
28
|
+
#contract method parameters
|
29
|
+
cmp = contract_class.instance_method(method).parameters
|
30
|
+
#dependency method parameters
|
31
|
+
dmp = dependency_class.instance_method(method).parameters
|
32
|
+
|
33
|
+
if cmp != dmp
|
34
|
+
raise Callme::Errors::DependencyContractInvalidParametersException.new(method, cmp)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Instantiates deps according to their scopes
|
2
|
+
class Callme::DepFactory
|
3
|
+
attr_reader :const_loader
|
4
|
+
|
5
|
+
# Constructor
|
6
|
+
# @param deps_metadata_storage [DepsMetadataStorage] storage of dep metadatas
|
7
|
+
def initialize(const_loader, deps_metadata_storage)
|
8
|
+
@const_loader = const_loader
|
9
|
+
@deps_metadata_storage = deps_metadata_storage
|
10
|
+
@singleton_scope = Callme::Scopes::SingletonScope.new(self)
|
11
|
+
@prototype_scope = Callme::Scopes::PrototypeScope.new(self)
|
12
|
+
@request_scope = Callme::Scopes::RequestScope.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get dep from the container by it's +name+.
|
16
|
+
# According to the dep scope it will be newly created or returned already
|
17
|
+
# instantiated dep
|
18
|
+
# @param [Symbol] dep name
|
19
|
+
# @return dep instance
|
20
|
+
# @raise MissingDepError if dep with the specified name is not found
|
21
|
+
def get_dep(name)
|
22
|
+
dep_metadata = @deps_metadata_storage.by_name(name)
|
23
|
+
unless dep_metadata
|
24
|
+
raise Callme::Errors::MissingDepError, "Dep with name :#{name} is not defined"
|
25
|
+
end
|
26
|
+
get_dep_with_metadata(dep_metadata)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get dep by the specified +dep metadata+
|
30
|
+
# @param [DepMetadata] dep metadata
|
31
|
+
# @return dep instance
|
32
|
+
def get_dep_with_metadata(dep_metadata)
|
33
|
+
get_scope_by_metadata(dep_metadata).get_dep(dep_metadata)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create new dep instance according
|
37
|
+
# to the specified +dep_metadata+
|
38
|
+
# @param [DepMetadata] dep metadata
|
39
|
+
# @return dep instance
|
40
|
+
# @raise MissingDepError if some of dep dependencies are not found
|
41
|
+
def create_dep_and_save(dep_metadata, deps_storage)
|
42
|
+
if dep_metadata.dep_class.is_a?(Class)
|
43
|
+
dep_class = dep_metadata.dep_class
|
44
|
+
else
|
45
|
+
dep_class = const_loader.load_const(dep_metadata.dep_class)
|
46
|
+
dep_metadata.fetch_attrs!(dep_class)
|
47
|
+
end
|
48
|
+
dep = dep_metadata.instance ? dep_class.new : dep_class
|
49
|
+
|
50
|
+
if dep_metadata.has_contract?
|
51
|
+
contract_validator.validate(dep_class, dep_metadata.contract, const_loader)
|
52
|
+
end
|
53
|
+
|
54
|
+
if dep_metadata.has_factory_method?
|
55
|
+
set_dep_dependencies(dep, dep_metadata)
|
56
|
+
dep = dep.send(dep_metadata.factory_method)
|
57
|
+
deps_storage[dep_metadata.name] = dep
|
58
|
+
else
|
59
|
+
# put to container first to prevent circular dependencies
|
60
|
+
deps_storage[dep_metadata.name] = dep
|
61
|
+
set_dep_dependencies(dep, dep_metadata)
|
62
|
+
end
|
63
|
+
|
64
|
+
dep
|
65
|
+
end
|
66
|
+
|
67
|
+
# Delete dep from the container by it's +name+.
|
68
|
+
# @param [Symbol] dep name
|
69
|
+
# @raise MissingDepError if dep with the specified name is not found
|
70
|
+
def delete_dep(name)
|
71
|
+
dep_metadata = @deps_metadata_storage.by_name(name)
|
72
|
+
unless dep_metadata
|
73
|
+
raise Callme::Errors::MissingDepError, "Dep with name :#{name} is not defined"
|
74
|
+
end
|
75
|
+
get_scope_by_metadata(dep_metadata).delete_dep(dep_metadata)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def set_dep_dependencies(dep, dep_metadata)
|
81
|
+
dep_metadata.attrs.each do |attr|
|
82
|
+
dep_metadata = @deps_metadata_storage.by_name(attr.ref)
|
83
|
+
unless dep_metadata
|
84
|
+
raise Callme::Errors::MissingDepError, "Dep with name :#{attr.ref} is not defined, check #{dep.class}"
|
85
|
+
end
|
86
|
+
case dep_metadata.scope
|
87
|
+
when :singleton
|
88
|
+
dep.send("#{attr.name}=", get_dep(attr.ref))
|
89
|
+
when :prototype
|
90
|
+
dep.instance_variable_set(:@_callme_dep_factory, self)
|
91
|
+
dep.define_singleton_method(attr.name) do
|
92
|
+
@_callme_dep_factory.get_dep(attr.ref)
|
93
|
+
end
|
94
|
+
when :request
|
95
|
+
dep.instance_variable_set(:@_callme_dep_factory, self)
|
96
|
+
dep.define_singleton_method(attr.name) do
|
97
|
+
@_callme_dep_factory.get_dep(attr.ref)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_scope_by_metadata(dep_metadata)
|
104
|
+
case dep_metadata.scope
|
105
|
+
when :singleton
|
106
|
+
@singleton_scope
|
107
|
+
when :prototype
|
108
|
+
@prototype_scope
|
109
|
+
when :request
|
110
|
+
@request_scope
|
111
|
+
else
|
112
|
+
raise Callme::Errors::UnsupportedScopeError, "Dep with name :#{dep_metadata.name} has unsupported scope :#{dep_metadata.scope}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def contract_validator
|
117
|
+
Callme::ContractValidator.new
|
118
|
+
end
|
119
|
+
end
|