callme 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/mindreframer/callme.
|
1
|
+
# Callme [![Build Status](https://travis-ci.org/mindreframer/callme.svg)](https://travis-ci.org/mindreframer/callme) [![Code Climate](https://codeclimate.com/github/mindreframer/callme.svg)](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
|