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 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