callme 0.5.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 +5 -0
- data/.travis.yml +3 -0
- data/.yardops +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +22 -0
- data/README.md +147 -0
- data/Rakefile +1 -0
- data/bin/console +7 -0
- data/callme.gemspec +24 -0
- data/lib/callme/args_validator.rb +49 -0
- data/lib/callme/bean_factory.rb +115 -0
- data/lib/callme/bean_metadata.rb +70 -0
- data/lib/callme/beans_metadata_storage.rb +32 -0
- data/lib/callme/const_loaders/active_support.rb +7 -0
- data/lib/callme/const_loaders/native.rb +9 -0
- data/lib/callme/container.rb +107 -0
- data/lib/callme/errors.rb +10 -0
- data/lib/callme/inject.rb +36 -0
- data/lib/callme/scopes/prototype_scope.rb +25 -0
- data/lib/callme/scopes/request_scope.rb +33 -0
- data/lib/callme/scopes/singleton_scope.rb +29 -0
- data/lib/callme/scopes.rb +2 -0
- data/lib/callme/version.rb +3 -0
- data/lib/callme.rb +4 -0
- data/lib/ext/vendored_activesupport.rb +181 -0
- data/spec/callme/container_spec.rb +235 -0
- data/spec/callme/inject_spec.rb +55 -0
- data/spec/spec_helper.rb +7 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 22eae5e35215e5eed4a7876f1bdac5b8f8f9bcdb
|
4
|
+
data.tar.gz: 734e00afe63b56af60134a224acc0489ae09d481
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab1b440d21c6f30ee6ff3d95b89d5462485bf5505a468994ee1712117dc3e5c2e6b019b6e17660313f6ecbb7937f1c539e286a98a3464e0cb5d8f091f6def3cd
|
7
|
+
data.tar.gz: be2243ddbedb38520e29e85e652db9f2a09a168d99ae89dec6c30e2e8e57925f2e95be4e35094eb342d87f0adc93137bfdcdd008d6782c7fae096667c852d55b
|
data/.travis.yml
ADDED
data/.yardops
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lib/**/*.rb
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
callme (0.5.0)
|
5
|
+
request_store
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.2.5)
|
11
|
+
rake (10.1.1)
|
12
|
+
request_store (1.0.5)
|
13
|
+
rspec (2.14.1)
|
14
|
+
rspec-core (~> 2.14.0)
|
15
|
+
rspec-expectations (~> 2.14.0)
|
16
|
+
rspec-mocks (~> 2.14.0)
|
17
|
+
rspec-core (2.14.7)
|
18
|
+
rspec-expectations (2.14.4)
|
19
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
20
|
+
rspec-mocks (2.14.4)
|
21
|
+
|
22
|
+
PLATFORMS
|
23
|
+
ruby
|
24
|
+
|
25
|
+
DEPENDENCIES
|
26
|
+
bundler (~> 1.3)
|
27
|
+
callme!
|
28
|
+
rake
|
29
|
+
rspec
|
30
|
+
|
31
|
+
BUNDLED WITH
|
32
|
+
1.14.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Albert Gazizov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# Callme [](https://travis-ci.org/mindreframer/callme) [](https://codeclimate.com/github/mindreframer/callme)
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
Callme is an Inversion of Control container for Ruby.
|
6
|
+
It takes advantage of the dynamic nature of Ruby to provide a rich and flexible approach to injecting dependencies.
|
7
|
+
It's inspired by SpringIoc and tries to give you the same features.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
Lets say you have a Logger which has the Appender dependency
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
class Logger
|
14
|
+
attr_accessor :appender
|
15
|
+
|
16
|
+
def info(message)
|
17
|
+
# do some work with appender
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Appender
|
22
|
+
end
|
23
|
+
```
|
24
|
+
To use Logger you need to inject the instance of Appender class, for example
|
25
|
+
using setter injection:
|
26
|
+
```ruby
|
27
|
+
logger = Logger.new
|
28
|
+
logger.appender = Appender.new
|
29
|
+
logger.info('some message')
|
30
|
+
```
|
31
|
+
|
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:
|
34
|
+
```ruby
|
35
|
+
container = Callme::Container.new do |c|
|
36
|
+
c.bean(:appender, class: Appender)
|
37
|
+
c.bean(:logger, class: Logger) do
|
38
|
+
attr :appender, ref: :appender
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
Now you can get the Logger instance from container with already set dependencies and use it:
|
43
|
+
```ruby
|
44
|
+
logger = container[:logger]
|
45
|
+
logger.info('some message')
|
46
|
+
```
|
47
|
+
|
48
|
+
To simplify injection Callme allows you specify dependencies inside of your class:
|
49
|
+
```ruby
|
50
|
+
class Logger
|
51
|
+
inject :appender
|
52
|
+
|
53
|
+
def info(message)
|
54
|
+
# do some work with appender
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Appender
|
59
|
+
end
|
60
|
+
```
|
61
|
+
With `inject` keyword you won't need to specify class dependencies in bean definition:
|
62
|
+
```ruby
|
63
|
+
container = Callme::Container.new do |c|
|
64
|
+
c.bean(:appender, class: Appender)
|
65
|
+
c.bean(:logger, class: Logger)
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
## Inheriting from other containers
|
72
|
+
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
|
+
|
75
|
+
```ruby
|
76
|
+
class ContactBook
|
77
|
+
inject :contacts_repository
|
78
|
+
inject :validator, ref: :contact_validator
|
79
|
+
end
|
80
|
+
class ContactBookService
|
81
|
+
inject :contacts_repository
|
82
|
+
inject :validator, ref: :contact_validator
|
83
|
+
end
|
84
|
+
class ContactsRepository
|
85
|
+
end
|
86
|
+
class ContactValidator
|
87
|
+
end
|
88
|
+
class TestContactValidator
|
89
|
+
end
|
90
|
+
|
91
|
+
class AnotherTestContactValidator
|
92
|
+
end
|
93
|
+
|
94
|
+
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")
|
99
|
+
end
|
100
|
+
puts parent[:contact_book_service].validator.class
|
101
|
+
#=> ContactValidator
|
102
|
+
|
103
|
+
testcontainer = Callme::Container.with_parent(parent) do |c|
|
104
|
+
c.bean(:contact_validator, class: TestContactValidator)
|
105
|
+
end
|
106
|
+
puts testcontainer[:contact_book_service].validator.class
|
107
|
+
#=> TestContactValidator
|
108
|
+
|
109
|
+
third = Callme::Container.with_parent(parent) do |c|
|
110
|
+
c.bean(:contact_validator, class: AnotherTestContactValidator)
|
111
|
+
end
|
112
|
+
|
113
|
+
puts third[:contact_book_service].validator.class
|
114
|
+
#=> AnotherTestContactValidator
|
115
|
+
```
|
116
|
+
|
117
|
+
|
118
|
+
## Installation
|
119
|
+
|
120
|
+
Add this line to your application's Gemfile:
|
121
|
+
|
122
|
+
gem 'callme'
|
123
|
+
|
124
|
+
And then execute:
|
125
|
+
|
126
|
+
$ bundle
|
127
|
+
|
128
|
+
Or install it yourself as:
|
129
|
+
|
130
|
+
$ gem install callme
|
131
|
+
|
132
|
+
## Contributing
|
133
|
+
|
134
|
+
1. Fork it
|
135
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
136
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
137
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
138
|
+
5. Create new Pull Request
|
139
|
+
|
140
|
+
# TODO
|
141
|
+
1. Constructor based injection
|
142
|
+
2. Scope registration, refactor BeanFactory. Callme:Container.register_scope(SomeScope)
|
143
|
+
3. Write documentation with more examples
|
144
|
+
|
145
|
+
## Author
|
146
|
+
Roman Heinrich, [@mindreframer](https://twitter.com/mindreframer)
|
147
|
+
Albert Gazizov, [@deeper4k](https://twitter.com/deeper4k)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
data/callme.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'callme/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "callme"
|
8
|
+
spec.version = Callme::VERSION
|
9
|
+
spec.authors = ["Roman Heinrich", "Albert Gazizov"]
|
10
|
+
spec.email = ["roman.heinrich@gmail.com"]
|
11
|
+
spec.description = %q{Callme: Simple depencency injection lib}
|
12
|
+
spec.summary = %q{Callme: Simple depencency injection lib}
|
13
|
+
spec.homepage = "http://github.com/mindreframer/callme"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(spec)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "request_store"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Helper class for arguments validation
|
2
|
+
module Callme::ArgsValidator
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# Checks that specifid +obj+ is a symbol
|
6
|
+
# @param obj some object
|
7
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
8
|
+
def is_symbol!(obj, obj_name)
|
9
|
+
unless obj.is_a?(Symbol)
|
10
|
+
raise ArgumentError, "#{obj_name} should be a Symbol"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Checks that specifid +obj+ is an Array
|
15
|
+
# @param obj some object
|
16
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
17
|
+
def is_array!(obj, obj_name)
|
18
|
+
unless obj.is_a?(Array)
|
19
|
+
raise ArgumentError, "#{obj_name} should be an Array"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Checks that specifid +obj+ is a Hash
|
24
|
+
# @param obj some object
|
25
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
26
|
+
def is_hash!(obj, obj_name)
|
27
|
+
unless obj.is_a?(Hash)
|
28
|
+
raise ArgumentError, "#{obj_name} should be a Hash"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Checks that specifid +hash+ has a specified +key+
|
33
|
+
# @param hash some hash
|
34
|
+
# @param key hash's key
|
35
|
+
def has_key!(hash, key)
|
36
|
+
unless hash.has_key?(key)
|
37
|
+
raise ArgumentError, "#{hash} should has #{key} key"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks that specified +block+ is given
|
42
|
+
# @param block some block
|
43
|
+
def block_given!(block)
|
44
|
+
unless block
|
45
|
+
raise ArgumentError, "Block should be given"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'callme/scopes'
|
2
|
+
require 'callme/scopes/singleton_scope'
|
3
|
+
require 'callme/scopes/prototype_scope'
|
4
|
+
require 'callme/scopes/request_scope'
|
5
|
+
|
6
|
+
# Instantiates beans according to their scopes
|
7
|
+
class Callme::BeanFactory
|
8
|
+
attr_reader :const_loader
|
9
|
+
|
10
|
+
# Constructor
|
11
|
+
# @param beans_metadata_storage [BeansMetadataStorage] storage of bean metadatas
|
12
|
+
def initialize(const_loader, beans_metadata_storage)
|
13
|
+
@const_loader = const_loader
|
14
|
+
@beans_metadata_storage = beans_metadata_storage
|
15
|
+
@singleton_scope = Callme::Scopes::SingletonScope.new(self)
|
16
|
+
@prototype_scope = Callme::Scopes::PrototypeScope.new(self)
|
17
|
+
@request_scope = Callme::Scopes::RequestScope.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get bean from the container by it's +name+.
|
21
|
+
# According to the bean scope it will be newly created or returned already
|
22
|
+
# instantiated bean
|
23
|
+
# @param [Symbol] bean name
|
24
|
+
# @return bean instance
|
25
|
+
# @raise MissingBeanError if bean with the specified name is not found
|
26
|
+
def get_bean(name)
|
27
|
+
bean_metadata = @beans_metadata_storage.by_name(name)
|
28
|
+
unless bean_metadata
|
29
|
+
raise Callme::Errors::MissingBeanError, "Bean with name :#{name} is not defined"
|
30
|
+
end
|
31
|
+
get_bean_with_metadata(bean_metadata)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get bean by the specified +bean metadata+
|
35
|
+
# @param [BeanMetadata] bean metadata
|
36
|
+
# @return bean instance
|
37
|
+
def get_bean_with_metadata(bean_metadata)
|
38
|
+
get_scope_by_metadata(bean_metadata).get_bean(bean_metadata)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create new bean instance according
|
42
|
+
# to the specified +bean_metadata+
|
43
|
+
# @param [BeanMetadata] bean metadata
|
44
|
+
# @return bean instance
|
45
|
+
# @raise MissingBeanError if some of bean dependencies are not found
|
46
|
+
def create_bean_and_save(bean_metadata, beans_storage)
|
47
|
+
if bean_metadata.bean_class.is_a?(Class)
|
48
|
+
bean_class = bean_metadata.bean_class
|
49
|
+
else
|
50
|
+
bean_class = const_loader.load_const(bean_metadata.bean_class)
|
51
|
+
bean_metadata.fetch_attrs!(bean_class)
|
52
|
+
end
|
53
|
+
bean = bean_metadata.instance ? bean_class.new : bean_class
|
54
|
+
if bean_metadata.has_factory_method?
|
55
|
+
set_bean_dependencies(bean, bean_metadata)
|
56
|
+
bean = bean.send(bean_metadata.factory_method)
|
57
|
+
beans_storage[bean_metadata.name] = bean
|
58
|
+
else
|
59
|
+
# put to container first to prevent circular dependencies
|
60
|
+
beans_storage[bean_metadata.name] = bean
|
61
|
+
set_bean_dependencies(bean, bean_metadata)
|
62
|
+
end
|
63
|
+
|
64
|
+
bean
|
65
|
+
end
|
66
|
+
|
67
|
+
# Delete bean from the container by it's +name+.
|
68
|
+
# @param [Symbol] bean name
|
69
|
+
# @raise MissingBeanError if bean with the specified name is not found
|
70
|
+
def delete_bean(name)
|
71
|
+
bean_metadata = @beans_metadata_storage.by_name(name)
|
72
|
+
unless bean_metadata
|
73
|
+
raise Callme::Errors::MissingBeanError, "Bean with name :#{name} is not defined"
|
74
|
+
end
|
75
|
+
get_scope_by_metadata(bean_metadata).delete_bean(bean_metadata)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def set_bean_dependencies(bean, bean_metadata)
|
81
|
+
bean_metadata.attrs.each do |attr|
|
82
|
+
bean_metadata = @beans_metadata_storage.by_name(attr.ref)
|
83
|
+
unless bean_metadata
|
84
|
+
raise Callme::Errors::MissingBeanError, "Bean with name :#{attr.ref} is not defined, check #{bean.class}"
|
85
|
+
end
|
86
|
+
case bean_metadata.scope
|
87
|
+
when :singleton
|
88
|
+
bean.send("#{attr.name}=", get_bean(attr.ref))
|
89
|
+
when :prototype
|
90
|
+
bean.instance_variable_set(:@_callme_bean_factory, self)
|
91
|
+
bean.define_singleton_method(attr.name) do
|
92
|
+
@_callme_bean_factory.get_bean(attr.ref)
|
93
|
+
end
|
94
|
+
when :request
|
95
|
+
bean.instance_variable_set(:@_callme_bean_factory, self)
|
96
|
+
bean.define_singleton_method(attr.name) do
|
97
|
+
@_callme_bean_factory.get_bean(attr.ref)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_scope_by_metadata(bean_metadata)
|
104
|
+
case bean_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, "Bean with name :#{bean_metadata.name} has unsupported scope :#{bean_metadata.scope}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Stores bean specific data: bean class, name,
|
2
|
+
# scope and bean dependencies
|
3
|
+
class Callme::BeanMetadata
|
4
|
+
attr_reader :name, :bean_class, :scope, :instance, :factory_method, :attrs
|
5
|
+
|
6
|
+
# Constructor
|
7
|
+
# @param name [Symbol] bean name
|
8
|
+
# @params options [Hash] includes bean class and scope
|
9
|
+
# @params &block [Proc] bean dependencies, has the following structure:
|
10
|
+
# do |c|
|
11
|
+
# attr :some_dependency, ref: :dependency_name
|
12
|
+
# arg :another_dependency, ref: :another_dependency_name
|
13
|
+
# end
|
14
|
+
# here attr means setter injection, arg means constructon injects
|
15
|
+
# +some_dependency+ is an attr_accessor defined in the bean class,
|
16
|
+
# +ref+ specifies what dependency from container to use to set the attribute
|
17
|
+
def initialize(name, options, &block)
|
18
|
+
Callme::ArgsValidator.has_key!(options, :class)
|
19
|
+
|
20
|
+
@name = name
|
21
|
+
@bean_class = options[:class]
|
22
|
+
@scope = options[:scope] || :singleton
|
23
|
+
@instance = options[:instance].nil? ? true : options[:instance]
|
24
|
+
@factory_method = options[:factory_method]
|
25
|
+
@attrs = []
|
26
|
+
|
27
|
+
fetch_attrs!(@bean_class)
|
28
|
+
|
29
|
+
if block
|
30
|
+
Dsl.new(@attrs).instance_exec(&block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetch_attrs!(klass)
|
35
|
+
if klass.respond_to?(:_callme_injectable_attrs)
|
36
|
+
klass._callme_injectable_attrs.each do |attr, options|
|
37
|
+
options[:ref] ||= attr
|
38
|
+
@attrs << Callme::BeanMetadata::Attribute.new(attr, options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_factory_method?
|
44
|
+
!!@factory_method
|
45
|
+
end
|
46
|
+
|
47
|
+
class Attribute
|
48
|
+
attr_reader :name, :ref
|
49
|
+
|
50
|
+
def initialize(name, options)
|
51
|
+
Callme::ArgsValidator.has_key!(options, :ref)
|
52
|
+
@name = name
|
53
|
+
@ref = options[:ref]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Dsl
|
58
|
+
def initialize(attrs)
|
59
|
+
@attrs = attrs
|
60
|
+
end
|
61
|
+
|
62
|
+
def attr(name, options)
|
63
|
+
@attrs << Callme::BeanMetadata::Attribute.new(name, options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def arg(name, options)
|
67
|
+
@args << Callme::BeanMetadata::Attribute.new(name, options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Storage of bean metadatas
|
2
|
+
class Callme::BeansMetadataStorage
|
3
|
+
def initialize(bean_metadatas = {})
|
4
|
+
@bean_metadatas = bean_metadatas
|
5
|
+
end
|
6
|
+
|
7
|
+
# Finds bean metadata in storage by it's name
|
8
|
+
# @param name [Symbol] bean metadata name
|
9
|
+
# @return bean metadata
|
10
|
+
def by_name(name)
|
11
|
+
@bean_metadatas[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
# Saves a given +bean_metadata+ to the storage
|
15
|
+
# @param bean_metadata [BeanMetadata] bean metadata for saving
|
16
|
+
def put(bean_metadata)
|
17
|
+
@bean_metadatas[bean_metadata.name] = bean_metadata
|
18
|
+
end
|
19
|
+
|
20
|
+
def bean_classes
|
21
|
+
@bean_metadatas.values.map(&:bean_class)
|
22
|
+
end
|
23
|
+
|
24
|
+
def keys
|
25
|
+
@bean_metadatas.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates an independent copy of this instance
|
29
|
+
def copy
|
30
|
+
self.class.new(@bean_metadatas.dup)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,107 @@
|
|
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
|
+
module Callme
|
9
|
+
|
10
|
+
# Callme::Container is the central data store for registering objects
|
11
|
+
# used for dependency injection. Users register classes by
|
12
|
+
# providing a name and a class to create the object(we call them beans). Beans
|
13
|
+
# may be retrieved by asking for them by name (via the [] operator)
|
14
|
+
class Container
|
15
|
+
DEFAULT_CONST_LOADER = Callme::ConstLoaders::Native
|
16
|
+
|
17
|
+
# Constructor
|
18
|
+
# @param resources [Array] array of procs with container's beans definitions
|
19
|
+
# @param &block [Proc] optional proc with container's beans definitions
|
20
|
+
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)
|
24
|
+
|
25
|
+
block.call(self) if block_given?
|
26
|
+
end
|
27
|
+
|
28
|
+
# 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)
|
32
|
+
Callme::ArgsValidator.is_array!(resources, :resources)
|
33
|
+
|
34
|
+
self.new(const_loader).tap do |container|
|
35
|
+
resources.each do |resource|
|
36
|
+
resource.call(container)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
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)
|
45
|
+
container.instance_eval do
|
46
|
+
@beans_metadata_storage = beans_metadata_storage
|
47
|
+
@bean_factory = Callme::BeanFactory.new(const_loader, beans_metadata_storage)
|
48
|
+
end
|
49
|
+
block.call(container) if block_given?
|
50
|
+
container
|
51
|
+
end
|
52
|
+
|
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)
|
60
|
+
Callme::ArgsValidator.is_hash!(options, :options)
|
61
|
+
|
62
|
+
bean = Callme::BeanMetadata.new(bean_name, options, &block)
|
63
|
+
@beans_metadata_storage.put(bean)
|
64
|
+
end
|
65
|
+
|
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)
|
74
|
+
end
|
75
|
+
bean(bean_name, options, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
def reset!
|
79
|
+
@bean_factory = Callme::BeanFactory.new(@const_loader, @beans_metadata_storage)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns bean instance from the container
|
83
|
+
# by the specified bean name
|
84
|
+
# @param name [Symbol] bean name
|
85
|
+
# @return bean instance
|
86
|
+
def [](name)
|
87
|
+
Callme::ArgsValidator.is_symbol!(name, :bean_name)
|
88
|
+
return @bean_factory.get_bean(name)
|
89
|
+
end
|
90
|
+
|
91
|
+
def keys
|
92
|
+
@beans_metadata_storage.keys
|
93
|
+
end
|
94
|
+
|
95
|
+
# Load defined in bean classes
|
96
|
+
# this is needed for production usage
|
97
|
+
# 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
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Callme::Errors
|
2
|
+
# Thrown when a service cannot be located by name.
|
3
|
+
class MissingBeanError < StandardError; end
|
4
|
+
|
5
|
+
# Thrown when a duplicate service is registered.
|
6
|
+
class DuplicateBeanError < StandardError; end
|
7
|
+
|
8
|
+
# Thrown when an unsupported bean scope is specified.
|
9
|
+
class UnsupportedScopeError < StandardError; end
|
10
|
+
end
|