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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 22eae5e35215e5eed4a7876f1bdac5b8f8f9bcdb
4
- data.tar.gz: 734e00afe63b56af60134a224acc0489ae09d481
3
+ metadata.gz: 9f73aca55d583eee628366c833117fbb17be432f
4
+ data.tar.gz: e057e62c608d928460886bf84ccb8d3ebb31f0dd
5
5
  SHA512:
6
- metadata.gz: ab1b440d21c6f30ee6ff3d95b89d5462485bf5505a468994ee1712117dc3e5c2e6b019b6e17660313f6ecbb7937f1c539e286a98a3464e0cb5d8f091f6def3cd
7
- data.tar.gz: be2243ddbedb38520e29e85e652db9f2a09a168d99ae89dec6c30e2e8e57925f2e95be4e35094eb342d87f0adc93137bfdcdd008d6782c7fae096667c852d55b
6
+ metadata.gz: bc3406c637301c9813dfbe08cbc5c874778439f63e51800d2b9593f2fd4b8690b957463a73cce7f43d84a968db339e8244412b50c31dc53a1e35deaf0f814f61
7
+ data.tar.gz: 4352b39b8c1224621e30a58d28e3797b62ec7c519183090441aacf072b70c29f0227399d85768d09632852d3c671c6c197fb8ffe447eba1267463719d01c0682
@@ -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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- callme (0.5.0)
4
+ callme (0.6.0)
5
5
  request_store
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Callme [![Build Status](https://travis-ci.org/mindreframer/callme.png)](https://travis-ci.org/mindreframer/callme) [![Code Climate](https://codeclimate.com/github/mindreframer/callme.png)](https://codeclimate.com/github/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 beans) to it:
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.bean(:appender, class: Appender)
37
- c.bean(:logger, class: Logger) do
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 bean definition:
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.bean(:appender, class: Appender)
65
- c.bean(:logger, class: Logger)
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 beans copied from the parent container.
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.bean(:contacts_repository, class: ContactsRepository)
96
- c.bean(:contact_validator, class: ContactValidator)
97
- c.bean(:contact_book, class: ContactBook)
98
- c.bean(:contact_book_service, class: "ContactBookService")
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.bean(:contact_validator, class: TestContactValidator)
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.bean(:contact_validator, class: AnotherTestContactValidator)
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 BeanFactory. Callme:Container.register_scope(SomeScope)
145
+ 2. Scope registration, refactor DepFactory. Callme:Container.register_scope(SomeScope)
143
146
  3. Write documentation with more examples
144
147
 
145
148
  ## Author
@@ -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'
@@ -1,6 +1,7 @@
1
1
  module Callme::ConstLoaders
2
2
  module ActiveSupport
3
3
  def self.load_const(const_name)
4
+ return const_name if const_name.is_a?(Class)
4
5
  ::ActiveSupport::Inflector.constantize(const_name)
5
6
  end
6
7
  end
@@ -1,6 +1,7 @@
1
1
  module Callme::ConstLoaders
2
2
  module Native
3
3
  def self.load_const(const_name)
4
+ return const_name if const_name.is_a?(Class)
4
5
  const_name.split('::').inject(Object) do |mod, const_part|
5
6
  mod.const_get(const_part)
6
7
  end
@@ -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 beans). Beans
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 beans definitions
19
- # @param &block [Proc] optional proc with container's beans definitions
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 = const_loader
22
- @beans_metadata_storage = Callme::BeansMetadataStorage.new
23
- @bean_factory = Callme::BeanFactory.new(const_loader, @beans_metadata_storage)
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 bean definitions to the container
30
- # @param resources [Array] array of procs with container's beans definitions
31
- def self.new_with_beans(resources, const_loader = DEFAULT_CONST_LOADER)
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 = parent_container.instance_variable_get("@const_loader")
43
- beans_metadata_storage = parent_container.instance_variable_get("@beans_metadata_storage").copy
44
- container = self.new(const_loader)
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
- @beans_metadata_storage = beans_metadata_storage
47
- @bean_factory = Callme::BeanFactory.new(const_loader, beans_metadata_storage)
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 bean in container
54
- # @param bean_name [Symbol] bean name
55
- # @param options [Hash] includes bean class and bean scope
56
- # @param &block [Proc] the block which describes bean dependencies,
57
- # see more in the BeanMetadata
58
- def bean(bean_name, options, &block)
59
- Callme::ArgsValidator.is_symbol!(bean_name, :bean_name)
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
- bean = Callme::BeanMetadata.new(bean_name, options, &block)
63
- @beans_metadata_storage.put(bean)
55
+ dep = Callme::DepMetadata.new(dep_name, options, &block)
56
+ @deps_metadata_storage.put(dep)
64
57
  end
65
58
 
66
- # Registers new bean in container and replace existing instance if it's instantiated
67
- # @param bean_name [Symbol] bean name
68
- # @param options [Hash] includes bean class and bean scope
69
- # @param &block [Proc] the block which describes bean dependencies,
70
- # see more in the BeanMetadata
71
- def replace_bean(bean_name, options, &block)
72
- if @bean_factory.get_bean(bean_name)
73
- @bean_factory.delete_bean(bean_name)
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
- bean(bean_name, options, &block)
68
+ dep(dep_name, options, &block)
76
69
  end
77
70
 
78
71
  def reset!
79
- @bean_factory = Callme::BeanFactory.new(@const_loader, @beans_metadata_storage)
72
+ @dep_factory = Callme::DepFactory.new(@const_loader, @deps_metadata_storage)
80
73
  end
81
74
 
82
- # Returns bean instance from the container
83
- # by the specified bean name
84
- # @param name [Symbol] bean name
85
- # @return bean instance
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, :bean_name)
88
- return @bean_factory.get_bean(name)
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
- @beans_metadata_storage.keys
85
+ @deps_metadata_storage.keys
93
86
  end
94
87
 
95
- # Load defined in bean classes
88
+ # Load defined in dep classes
96
89
  # this is needed for production usage
97
90
  # for eager loading
98
- def eager_load_bean_classes
99
- @beans_metadata_storage.bean_classes.each do |bean_class|
100
- if !bean_class.is_a?(Class)
101
- @const_loader.load_const(bean_class)
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