ioc_rb 0.0.2
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.
- data/.gitignore +4 -0
- data/.travis.yml +3 -0
- data/.yardops +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +51 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +1 -0
- data/ioc_rb.gemspec +25 -0
- data/lib/ioc_rb/args_validator.rb +49 -0
- data/lib/ioc_rb/bean_factory.rb +89 -0
- data/lib/ioc_rb/bean_metadata.rb +61 -0
- data/lib/ioc_rb/beans_metadata_storage.rb +19 -0
- data/lib/ioc_rb/container.rb +65 -0
- data/lib/ioc_rb/errors.rb +10 -0
- data/lib/ioc_rb/inject.rb +35 -0
- data/lib/ioc_rb/scopes/prototype_scope.rb +17 -0
- data/lib/ioc_rb/scopes/request_scope.rb +27 -0
- data/lib/ioc_rb/scopes/singleton_scope.rb +23 -0
- data/lib/ioc_rb/scopes.rb +2 -0
- data/lib/ioc_rb/version.rb +3 -0
- data/lib/ioc_rb.rb +4 -0
- data/spec/ioc_rb/container_spec.rb +122 -0
- data/spec/ioc_rb/inject_spec.rb +46 -0
- data/spec/spec_helper.rb +7 -0
- metadata +141 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardops
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lib/**/*.rb
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ioc_rb (0.0.2)
|
5
|
+
activesupport
|
6
|
+
request_store
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (4.0.2)
|
12
|
+
i18n (~> 0.6, >= 0.6.4)
|
13
|
+
minitest (~> 4.2)
|
14
|
+
multi_json (~> 1.3)
|
15
|
+
thread_safe (~> 0.1)
|
16
|
+
tzinfo (~> 0.3.37)
|
17
|
+
atomic (1.1.14)
|
18
|
+
columnize (0.3.6)
|
19
|
+
debugger (1.6.5)
|
20
|
+
columnize (>= 0.3.1)
|
21
|
+
debugger-linecache (~> 1.2.0)
|
22
|
+
debugger-ruby_core_source (~> 1.3.1)
|
23
|
+
debugger-linecache (1.2.0)
|
24
|
+
debugger-ruby_core_source (1.3.1)
|
25
|
+
diff-lcs (1.2.5)
|
26
|
+
i18n (0.6.9)
|
27
|
+
minitest (4.7.5)
|
28
|
+
multi_json (1.8.4)
|
29
|
+
rake (10.1.1)
|
30
|
+
request_store (1.0.5)
|
31
|
+
rspec (2.14.1)
|
32
|
+
rspec-core (~> 2.14.0)
|
33
|
+
rspec-expectations (~> 2.14.0)
|
34
|
+
rspec-mocks (~> 2.14.0)
|
35
|
+
rspec-core (2.14.7)
|
36
|
+
rspec-expectations (2.14.4)
|
37
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
38
|
+
rspec-mocks (2.14.4)
|
39
|
+
thread_safe (0.1.3)
|
40
|
+
atomic
|
41
|
+
tzinfo (0.3.38)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
bundler (~> 1.3)
|
48
|
+
debugger
|
49
|
+
ioc_rb!
|
50
|
+
rake
|
51
|
+
rspec
|
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,94 @@
|
|
1
|
+
# IocRb [](https://travis-ci.org/AlbertGazizov/ioc_rb) [](https://codeclimate.com/github/AlbertGazizov/ioc_rb)
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
IocRb 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
|
+
IocRb eliminates the manual injection step and injects dependencies by itself.
|
33
|
+
To use it you need to instantiate IocRb::Container and pass dependency definitions(we call them beans) to it:
|
34
|
+
```ruby
|
35
|
+
container = IocRb::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 IocRb 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 = IocRb::Container.new do |c|
|
64
|
+
c.bean(:appender, class: Appender)
|
65
|
+
c.bean(:logger, class: Logger)
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
## Installation
|
70
|
+
|
71
|
+
Add this line to your application's Gemfile:
|
72
|
+
|
73
|
+
gem 'ioc_rb'
|
74
|
+
|
75
|
+
And then execute:
|
76
|
+
|
77
|
+
$ bundle
|
78
|
+
|
79
|
+
Or install it yourself as:
|
80
|
+
|
81
|
+
$ gem install ioc_rb
|
82
|
+
|
83
|
+
## Contributing
|
84
|
+
|
85
|
+
1. Fork it
|
86
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
87
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
88
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
89
|
+
5. Create new Pull Request
|
90
|
+
|
91
|
+
# TODO
|
92
|
+
1. Constructor based injection
|
93
|
+
2. Scope registration, refactor BeanFactory. IocRb:Container.register_scope(SomeScope)
|
94
|
+
3. Write documentation with more examples
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/ioc_rb.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ioc_rb/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ioc_rb"
|
8
|
+
spec.version = IocRb::VERSION
|
9
|
+
spec.authors = ["Albert Gazizov", "Ruslan Gatiyatov"]
|
10
|
+
spec.email = ["deeper4k@gmail.com", "ruslan.gatiyatov@gmail.com"]
|
11
|
+
spec.description = %q{Inversion of Controll Container}
|
12
|
+
spec.summary = %q{Inversion of Controll Container}
|
13
|
+
spec.homepage = "http://github.com/deeper4k/ioc_rb"
|
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_dependency "activesupport"
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Helper class for arguments validation
|
2
|
+
module IocRb::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,89 @@
|
|
1
|
+
require 'ioc_rb/scopes'
|
2
|
+
require 'ioc_rb/scopes/singleton_scope'
|
3
|
+
require 'ioc_rb/scopes/prototype_scope'
|
4
|
+
require 'ioc_rb/scopes/request_scope'
|
5
|
+
|
6
|
+
# Instantiates beans according to their scopes
|
7
|
+
class IocRb::BeanFactory
|
8
|
+
|
9
|
+
# Constructor
|
10
|
+
# @param beans_metadata_storage [BeansMetadataStorage] storage of bean metadatas
|
11
|
+
def initialize(beans_metadata_storage)
|
12
|
+
@beans_metadata_storage = beans_metadata_storage
|
13
|
+
@singleton_scope = IocRb::Scopes::SingletonScope.new(self)
|
14
|
+
@prototype_scope = IocRb::Scopes::PrototypeScope.new(self)
|
15
|
+
@request_scope = IocRb::Scopes::RequestScope.new(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get bean from the container by it's +name+.
|
19
|
+
# According to the bean scope it will be newly created or returned already
|
20
|
+
# instantiated bean
|
21
|
+
# @param [Symbol] bean name
|
22
|
+
# @return bean instance
|
23
|
+
# @raise MissingBeanError if bean with the specified name is not found
|
24
|
+
def get_bean(name)
|
25
|
+
bean_metadata = @beans_metadata_storage.by_name(name)
|
26
|
+
unless bean_metadata
|
27
|
+
raise IocRb::Errors::MissingBeanError, "Bean with name :#{name} is not defined"
|
28
|
+
end
|
29
|
+
get_bean_with_metadata(bean_metadata)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get bean by the specified +bean metadata+
|
33
|
+
# @param [BeanMetadata] bean metadata
|
34
|
+
# @return bean instance
|
35
|
+
def get_bean_with_metadata(bean_metadata)
|
36
|
+
case bean_metadata.scope
|
37
|
+
when :singleton
|
38
|
+
@singleton_scope.get_bean(bean_metadata)
|
39
|
+
when :prototype
|
40
|
+
@prototype_scope.get_bean(bean_metadata)
|
41
|
+
when :request
|
42
|
+
@request_scope.get_bean(bean_metadata)
|
43
|
+
else
|
44
|
+
raise IocRb::Errors::UnsupportedScopeError, "Bean with name :#{bean_metadata.name} has unsupported scope :#{bean_metadata.scope}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create new bean instance according
|
49
|
+
# to the specified +bean_metadata+
|
50
|
+
# @param [BeanMetadata] bean metadata
|
51
|
+
# @return bean instance
|
52
|
+
# @raise MissingBeanError if some of bean dependencies are not found
|
53
|
+
def create_bean_and_save(bean_metadata, beans_storage)
|
54
|
+
if bean_metadata.bean_class.is_a?(Class)
|
55
|
+
bean_class = bean_metadata.bean_class
|
56
|
+
else
|
57
|
+
bean_class = bean_metadata.bean_class.split('::').inject(Object) do |mod, class_name|
|
58
|
+
mod.const_get(class_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
bean = bean_metadata.instance ? bean_class.new : bean_class
|
62
|
+
|
63
|
+
# put to container first to prevent circular dependencies
|
64
|
+
beans_storage[bean_metadata.name] = bean
|
65
|
+
|
66
|
+
bean_metadata.attrs.each do |attr|
|
67
|
+
bean_metadata = @beans_metadata_storage.by_name(attr.ref)
|
68
|
+
unless bean_metadata
|
69
|
+
raise IocRb::Errors::MissingBeanError, "Bean with name :#{attr.ref} is not defined"
|
70
|
+
end
|
71
|
+
case bean_metadata.scope
|
72
|
+
when :singleton
|
73
|
+
bean.send("#{attr.name}=", get_bean(attr.ref))
|
74
|
+
when :prototype
|
75
|
+
bean.instance_variable_set(:@_ioc_rb_bean_factory, self)
|
76
|
+
bean.define_singleton_method(attr.name) do
|
77
|
+
@_ioc_rb_bean_factory.get_bean(attr.ref)
|
78
|
+
end
|
79
|
+
when :request
|
80
|
+
bean.instance_variable_set(:@_ioc_rb_bean_factory, self)
|
81
|
+
bean.define_singleton_method(attr.name) do
|
82
|
+
@_ioc_rb_bean_factory.get_bean(attr.ref)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
bean
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Stores bean specific data: bean class, name,
|
2
|
+
# scope and bean dependencies
|
3
|
+
class IocRb::BeanMetadata
|
4
|
+
attr_reader :name, :bean_class, :scope, :instance, :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
|
+
IocRb::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
|
+
@attrs = []
|
25
|
+
|
26
|
+
if @bean_class.respond_to?(:_iocrb_injectable_attrs)
|
27
|
+
@bean_class._iocrb_injectable_attrs.each do |attr, options|
|
28
|
+
options[:ref] ||= attr
|
29
|
+
@attrs << IocRb::BeanMetadata::Attribute.new(attr, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if block
|
34
|
+
Dsl.new(@attrs).instance_exec(&block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Attribute
|
39
|
+
attr_reader :name, :ref
|
40
|
+
|
41
|
+
def initialize(name, options)
|
42
|
+
IocRb::ArgsValidator.has_key!(options, :ref)
|
43
|
+
@name = name
|
44
|
+
@ref = options[:ref]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Dsl
|
49
|
+
def initialize(attrs)
|
50
|
+
@attrs = attrs
|
51
|
+
end
|
52
|
+
|
53
|
+
def attr(name, options)
|
54
|
+
@attrs << IocRb::BeanMetadata::Attribute.new(name, options)
|
55
|
+
end
|
56
|
+
|
57
|
+
def arg(name, options)
|
58
|
+
@args << IocRb::BeanMetadata::Attribute.new(name, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Storage of bean metadatas
|
2
|
+
class IocRb::BeansMetadataStorage
|
3
|
+
def initialize
|
4
|
+
@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
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'ioc_rb/errors'
|
2
|
+
require 'ioc_rb/args_validator'
|
3
|
+
require 'ioc_rb/bean_metadata'
|
4
|
+
require 'ioc_rb/beans_metadata_storage'
|
5
|
+
require 'ioc_rb/bean_factory'
|
6
|
+
|
7
|
+
module IocRb
|
8
|
+
|
9
|
+
# IocRb::Container is the central data store for registering objects
|
10
|
+
# used for dependency injection. Users register classes by
|
11
|
+
# providing a name and a class to create the object(we call them beans). Beans
|
12
|
+
# may be retrieved by asking for them by name (via the [] operator)
|
13
|
+
class Container
|
14
|
+
|
15
|
+
# Constructor
|
16
|
+
# @param resources [Array] array of procs with container's beans definitions
|
17
|
+
# @param &block [Proc] optional proc with container's beans definitions
|
18
|
+
def initialize(resources = nil, &block)
|
19
|
+
@beans_metadata_storage = IocRb::BeansMetadataStorage.new
|
20
|
+
@bean_factory = IocRb::BeanFactory.new(@beans_metadata_storage)
|
21
|
+
|
22
|
+
if resources
|
23
|
+
IocRb::ArgsValidator.is_array!(resources, :resources)
|
24
|
+
load_bean_definitions(resources)
|
25
|
+
end
|
26
|
+
if block_given?
|
27
|
+
load_bean_definitions([block])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Registers new bean in container
|
32
|
+
# @param bean_name [Symbol] bean name
|
33
|
+
# @param options [Hash] includes bean class and bean scope
|
34
|
+
# @param &block [Proc] the block which describes bean dependencies,
|
35
|
+
# see more in the BeanMetadata
|
36
|
+
def bean(bean_name, options, &block)
|
37
|
+
IocRb::ArgsValidator.is_symbol!(bean_name, :bean_name)
|
38
|
+
IocRb::ArgsValidator.is_hash!(options, :options)
|
39
|
+
|
40
|
+
bean = IocRb::BeanMetadata.new(bean_name, options, &block)
|
41
|
+
@beans_metadata_storage.put(bean)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns bean instance from the container
|
45
|
+
# by the specified bean name
|
46
|
+
# @param name [Symbol] bean name
|
47
|
+
# @return bean instance
|
48
|
+
def [](name)
|
49
|
+
IocRb::ArgsValidator.is_symbol!(name, :bean_name)
|
50
|
+
@bean_factory.get_bean(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Evaluates the given array of blocks on the container instance
|
56
|
+
# what adds new bean definitions to the container
|
57
|
+
# @param resources [Array] array of procs with container's beans definitions
|
58
|
+
def load_bean_definitions(resources)
|
59
|
+
resources.each do |resource|
|
60
|
+
resource.call(self)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module IocRb::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
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Extend object with the bean injection mechanism
|
2
|
+
# Example of usage:
|
3
|
+
# class Bar
|
4
|
+
# end
|
5
|
+
#
|
6
|
+
# class Foo
|
7
|
+
# inject :bar
|
8
|
+
# or:
|
9
|
+
# inject :some_bar, ref: bar
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# ioc_container[:foo].bar == ioc_container[:bar]
|
13
|
+
class Object
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def inject(dependency_name, options = {})
|
18
|
+
unless dependency_name.is_a?(Symbol)
|
19
|
+
raise ArgumentError, "dependency name should be a symbol"
|
20
|
+
end
|
21
|
+
unless options.is_a?(Hash)
|
22
|
+
raise ArgumentError, "second argument for inject method should be a Hash"
|
23
|
+
end
|
24
|
+
unless respond_to?(:_iocrb_injectable_attrs)
|
25
|
+
class_attribute :_iocrb_injectable_attrs
|
26
|
+
self._iocrb_injectable_attrs = { dependency_name => options.dup }
|
27
|
+
else
|
28
|
+
self._iocrb_injectable_attrs = self._iocrb_injectable_attrs.merge(dependency_name => options.dup)
|
29
|
+
end
|
30
|
+
attr_accessor dependency_name
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Prototype scope instantiates new bean instance
|
2
|
+
# on each +get_bean+ call
|
3
|
+
class IocRb::Scopes::PrototypeScope
|
4
|
+
|
5
|
+
# Constructon
|
6
|
+
# @param bean_factory bean factory
|
7
|
+
def initialize(bean_factory)
|
8
|
+
@bean_factory = bean_factory
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get new bean instance
|
12
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
13
|
+
# @returns bean instance
|
14
|
+
def get_bean(bean_metadata)
|
15
|
+
@bean_factory.create_bean_and_save(bean_metadata, {})
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'request_store'
|
2
|
+
|
3
|
+
# Request scope instantiates new bean instance
|
4
|
+
# on each new HTTP request
|
5
|
+
class IocRb::Scopes::RequestScope
|
6
|
+
|
7
|
+
# Constructon
|
8
|
+
# @param bean_factory bean factory
|
9
|
+
def initialize(bean_factory)
|
10
|
+
@bean_factory = bean_factory
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a bean from the +RequestStore+
|
14
|
+
# RequestStore is a wrapper for Thread.current
|
15
|
+
# which clears it on each new HTTP request
|
16
|
+
#
|
17
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
18
|
+
# @returns bean instance
|
19
|
+
def get_bean(bean_metadata)
|
20
|
+
RequestStore.store[:_iocrb_beans] ||= {}
|
21
|
+
if bean = RequestStore.store[:_iocrb_beans][bean_metadata.name]
|
22
|
+
bean
|
23
|
+
else
|
24
|
+
@bean_factory.create_bean_and_save(bean_metadata, RequestStore.store[:_iocrb_beans])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Singleton scope returns the same bean instance
|
2
|
+
# on each call
|
3
|
+
class IocRb::Scopes::SingletonScope
|
4
|
+
|
5
|
+
# Constructon
|
6
|
+
# @param bean_factory bean factory
|
7
|
+
def initialize(bean_factory)
|
8
|
+
@beans = {}
|
9
|
+
@bean_factory = bean_factory
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns the same bean instance
|
13
|
+
# on each call
|
14
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
15
|
+
# @returns bean instance
|
16
|
+
def get_bean(bean_metadata)
|
17
|
+
if bean = @beans[bean_metadata.name]
|
18
|
+
bean
|
19
|
+
else
|
20
|
+
@bean_factory.create_bean_and_save(bean_metadata, @beans)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/ioc_rb.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ioc_rb'
|
3
|
+
|
4
|
+
describe IocRb::Container do
|
5
|
+
|
6
|
+
class Logger
|
7
|
+
attr_accessor :appender
|
8
|
+
end
|
9
|
+
class Appender
|
10
|
+
end
|
11
|
+
class Printer
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "bean definitions" do
|
15
|
+
let(:container) do
|
16
|
+
container = IocRb::Container.new
|
17
|
+
container.bean(:appender, class: Appender)
|
18
|
+
container.bean(:logger, class: Logger) do
|
19
|
+
attr :appender, ref: :appender
|
20
|
+
end
|
21
|
+
container.bean(:printer, class: Printer, instance: false)
|
22
|
+
container
|
23
|
+
end
|
24
|
+
it "should instanciate bean and it's dependencies" do
|
25
|
+
container[:logger].should be_a(Logger)
|
26
|
+
container[:logger].appender.should be_a(Appender)
|
27
|
+
container[:printer].should be(Printer)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "container should return the same instance on each call" do
|
31
|
+
logger = container[:logger]
|
32
|
+
container[:logger].should == logger
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "passing bean definitions to container constructor" do
|
37
|
+
let(:resource) do
|
38
|
+
Proc.new do |c|
|
39
|
+
c.bean(:appender, class: 'Appender')
|
40
|
+
c.bean(:logger, class: Logger) do
|
41
|
+
attr :appender, ref: :appender
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should instanciate given bean definitions" do
|
47
|
+
container = IocRb::Container.new([resource])
|
48
|
+
container[:logger].should be_a(Logger)
|
49
|
+
container[:appender].should be_a(Appender)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "inheritance" do
|
55
|
+
class Form
|
56
|
+
inject :validator
|
57
|
+
end
|
58
|
+
|
59
|
+
class Circle < Form
|
60
|
+
inject :circle_validator
|
61
|
+
end
|
62
|
+
class Rectangle < Form
|
63
|
+
inject :rectangle_validator
|
64
|
+
end
|
65
|
+
|
66
|
+
class Validator
|
67
|
+
end
|
68
|
+
class CircleValidator
|
69
|
+
end
|
70
|
+
class RectangleValidator
|
71
|
+
end
|
72
|
+
|
73
|
+
let(:container) do
|
74
|
+
IocRb::Container.new do |c|
|
75
|
+
c.bean(:circle, class: Circle)
|
76
|
+
c.bean(:rectangle, class: Rectangle)
|
77
|
+
c.bean(:validator, class: Validator)
|
78
|
+
c.bean(:circle_validator, class: CircleValidator)
|
79
|
+
c.bean(:rectangle_validator, class: RectangleValidator)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "dependencies in subclasses shouldn't affect on each other" do
|
84
|
+
container[:circle].circle_validator.should be_a(CircleValidator)
|
85
|
+
container[:rectangle].rectangle_validator.should be_a(RectangleValidator)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "bean scopes" do
|
90
|
+
class ContactsService
|
91
|
+
inject :contacts_repository
|
92
|
+
inject :contacts_validator
|
93
|
+
end
|
94
|
+
class ContactsRepository
|
95
|
+
end
|
96
|
+
class ContactsValidator
|
97
|
+
end
|
98
|
+
|
99
|
+
let(:container) do
|
100
|
+
container = IocRb::Container.new
|
101
|
+
container.bean(:contacts_repository, class: ContactsRepository, scope: :request)
|
102
|
+
container.bean(:contacts_service, class: ContactsService, scope: :singleton)
|
103
|
+
container.bean(:contacts_validator, class: ContactsValidator, scope: :prototype)
|
104
|
+
container
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should instanciate bean with :request scope on each request" do
|
108
|
+
first_repo = container[:contacts_service].contacts_repository
|
109
|
+
second_repo = container[:contacts_service].contacts_repository
|
110
|
+
first_repo.should == second_repo
|
111
|
+
RequestStore.clear! # new request
|
112
|
+
third_repo = container[:contacts_service].contacts_repository
|
113
|
+
first_repo.should_not == third_repo
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should instanciate bean with :prototype scope on each call" do
|
117
|
+
first_validator = container[:contacts_service].contacts_validator
|
118
|
+
second_validator = container[:contacts_service].contacts_validator
|
119
|
+
first_validator.should_not == second_validator
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ioc_rb'
|
3
|
+
|
4
|
+
# Ensures that :inject keyword works as it should
|
5
|
+
describe "Object.inject" do
|
6
|
+
class ContactBook
|
7
|
+
inject :contacts_repository
|
8
|
+
inject :validator, ref: :contact_validator
|
9
|
+
end
|
10
|
+
class ContactsRepository
|
11
|
+
end
|
12
|
+
class ContactValidator
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:container) do
|
16
|
+
IocRb::Container.new do |c|
|
17
|
+
c.bean(:contacts_repository, class: ContactsRepository)
|
18
|
+
c.bean(:contact_validator, class: ContactValidator)
|
19
|
+
c.bean(:contact_book, class: ContactBook)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should autowire dependencies" do
|
24
|
+
container[:contact_book].contacts_repository.should be_a(ContactsRepository)
|
25
|
+
container[:contact_book].validator.should be_a(ContactValidator)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should raise ArgumentError if non-symbol passed as dependency name" do
|
29
|
+
expect do
|
30
|
+
class SomeClass
|
31
|
+
inject 'bar'
|
32
|
+
end
|
33
|
+
end.to raise_error(ArgumentError, "dependency name should be a symbol")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "inject should define instance variable" do
|
37
|
+
container[:contact_book].instance_variable_get(:@contacts_repository).should be_a(ContactsRepository)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "inject should not define class variable" do
|
41
|
+
expect do
|
42
|
+
container[:contact_book].class.contacts_repository
|
43
|
+
end.to raise_error(NoMethodError)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ioc_rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Albert Gazizov
|
9
|
+
- Ruslan Gatiyatov
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2014-02-24 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ! '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
none: false
|
22
|
+
name: request_store
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
requirement: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
none: false
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
version_requirements: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ! '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
none: false
|
38
|
+
name: activesupport
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
requirement: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
none: false
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
version_requirements: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ~>
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1.3'
|
53
|
+
none: false
|
54
|
+
name: bundler
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
none: false
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
none: false
|
70
|
+
name: rake
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
none: false
|
79
|
+
description: Inversion of Controll Container
|
80
|
+
email:
|
81
|
+
- deeper4k@gmail.com
|
82
|
+
- ruslan.gatiyatov@gmail.com
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- .gitignore
|
88
|
+
- .travis.yml
|
89
|
+
- .yardops
|
90
|
+
- Gemfile
|
91
|
+
- Gemfile.lock
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- ioc_rb.gemspec
|
96
|
+
- lib/ioc_rb.rb
|
97
|
+
- lib/ioc_rb/args_validator.rb
|
98
|
+
- lib/ioc_rb/bean_factory.rb
|
99
|
+
- lib/ioc_rb/bean_metadata.rb
|
100
|
+
- lib/ioc_rb/beans_metadata_storage.rb
|
101
|
+
- lib/ioc_rb/container.rb
|
102
|
+
- lib/ioc_rb/errors.rb
|
103
|
+
- lib/ioc_rb/inject.rb
|
104
|
+
- lib/ioc_rb/scopes.rb
|
105
|
+
- lib/ioc_rb/scopes/prototype_scope.rb
|
106
|
+
- lib/ioc_rb/scopes/request_scope.rb
|
107
|
+
- lib/ioc_rb/scopes/singleton_scope.rb
|
108
|
+
- lib/ioc_rb/version.rb
|
109
|
+
- spec/ioc_rb/container_spec.rb
|
110
|
+
- spec/ioc_rb/inject_spec.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
homepage: http://github.com/deeper4k/ioc_rb
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
none: false
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
none: false
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.8.23
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: Inversion of Controll Container
|
137
|
+
test_files:
|
138
|
+
- spec/ioc_rb/container_spec.rb
|
139
|
+
- spec/ioc_rb/inject_spec.rb
|
140
|
+
- spec/spec_helper.rb
|
141
|
+
has_rdoc:
|