smart_ioc 0.1.13
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 +6 -0
- data/.travis.yml +3 -0
- data/.yardops +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/Rakefile +1 -0
- data/lib/smart_ioc/bean_definition.rb +77 -0
- data/lib/smart_ioc/bean_definitions_storage.rb +79 -0
- data/lib/smart_ioc/bean_dependency.rb +18 -0
- data/lib/smart_ioc/bean_factory.rb +230 -0
- data/lib/smart_ioc/bean_file_loader.rb +26 -0
- data/lib/smart_ioc/bean_locations.rb +71 -0
- data/lib/smart_ioc/bean_locator.rb +32 -0
- data/lib/smart_ioc/container.rb +143 -0
- data/lib/smart_ioc/extra_package_contexts.rb +29 -0
- data/lib/smart_ioc/inject_metadata.rb +13 -0
- data/lib/smart_ioc/iocify.rb +101 -0
- data/lib/smart_ioc/scopes/prototype.rb +26 -0
- data/lib/smart_ioc/scopes/request.rb +35 -0
- data/lib/smart_ioc/scopes/singleton.rb +31 -0
- data/lib/smart_ioc/scopes.rb +2 -0
- data/lib/smart_ioc/version.rb +3 -0
- data/lib/smart_ioc.rb +44 -0
- data/smart_ioc.gemspec +23 -0
- data/spec/smart_ioc/bean_definition_spec.rb +29 -0
- data/spec/smart_ioc/bean_locator_spec.rb +27 -0
- data/spec/smart_ioc/example/admins/repository/admins_dao.rb +20 -0
- data/spec/smart_ioc/example/admins/repository/admins_repository.rb +15 -0
- data/spec/smart_ioc/example/admins/repository/test/admins_repository.rb +17 -0
- data/spec/smart_ioc/example/users/repository/users_dao.rb +17 -0
- data/spec/smart_ioc/example/users/repository/users_repository.rb +16 -0
- data/spec/smart_ioc/example/users/services/users_creator.rb +15 -0
- data/spec/smart_ioc/example/users/user.rb +8 -0
- data/spec/smart_ioc/example/utils/config.rb +15 -0
- data/spec/smart_ioc/example/utils/logger.rb +21 -0
- data/spec/smart_ioc/object_spec.rb +45 -0
- data/spec/smart_ioc/smart_ioc_spec.rb +43 -0
- data/spec/spec_helper.rb +8 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 651654180ff6efe44a53a5bf8295d0fdc2216efe
|
4
|
+
data.tar.gz: 7aff613bd666008bf5f444f7aeb1b8641a8c0e0e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 53900c9682dab42dc691fa95e504a3e5f2eb67daa312532cc7a239d33bf2673118b20c9ebc6a50a5151689e04f052bd6faaa1d6bdc9374462988ecf38d51179b
|
7
|
+
data.tar.gz: 1b8bc3b293360604e3b32cfe71b381f93d4e920352e482d7764faf3b50fdfdd6821134d7babe443568fada62bf7b7c6acc6f51f170f08afd385284e141b1eddc
|
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,32 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
smart_ioc (0.1.13)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
byebug (9.0.6)
|
10
|
+
diff-lcs (1.2.5)
|
11
|
+
rake (10.1.1)
|
12
|
+
rspec (2.14.1)
|
13
|
+
rspec-core (~> 2.14.0)
|
14
|
+
rspec-expectations (~> 2.14.0)
|
15
|
+
rspec-mocks (~> 2.14.0)
|
16
|
+
rspec-core (2.14.7)
|
17
|
+
rspec-expectations (2.14.4)
|
18
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
19
|
+
rspec-mocks (2.14.4)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
bundler (~> 1.3)
|
26
|
+
byebug
|
27
|
+
rake
|
28
|
+
rspec
|
29
|
+
smart_ioc!
|
30
|
+
|
31
|
+
BUNDLED WITH
|
32
|
+
1.13.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Ruslan Gatiyatov
|
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,91 @@
|
|
1
|
+
SmartIoC is a smart and really simple IoC container for Ruby applications.
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
`gem install smart_ioc`
|
5
|
+
|
6
|
+
## Setup
|
7
|
+
Set package name and source package folder with beans. SmartIoC will parse source files and detect bean definitions automatically for you.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
SmartIoC.find_package_beans(:PACKAGE_NAME, File.dirname(__FILE__))
|
11
|
+
```
|
12
|
+
|
13
|
+
If you have saveral packages in your application (like if you are using [rdm package manager](https://github.com/droidlabs/rdm)) you can run SmartIoC.find_package_beans several time pointing it to source folder and setting different package name.
|
14
|
+
|
15
|
+
## Basic information
|
16
|
+
1. Different packages can use beans with same name.
|
17
|
+
2. For a specific package you can declare beans with same name if they have different context.
|
18
|
+
```ruby
|
19
|
+
class UsersRepository
|
20
|
+
bean :users_repository
|
21
|
+
end
|
22
|
+
|
23
|
+
class Test::UsersRepository
|
24
|
+
bean :users_repository, context: :test
|
25
|
+
end
|
26
|
+
```
|
27
|
+
3. You can extend `:default` context with any other in the following way:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
SmartIoC::Container.get_instance.set_extra_context_for_package(:YOUR_PACKAGE_NAME, :test)
|
31
|
+
```
|
32
|
+
This allows to create test implementations that for any package dependencies.
|
33
|
+
4. In order to get bean use `SmartIoC::Container.get_bean(:BEAN_NAME, package: :PACKAGE_NAME, context: :default)`. `package` and `context` are optional arguments.
|
34
|
+
5. If you have name with same bean in different packages you will need to set package directly. You can simply do that in the following way:
|
35
|
+
```ruby
|
36
|
+
class UsersCreator
|
37
|
+
bean :users_creator
|
38
|
+
|
39
|
+
inject :users_repository, from: :repositories
|
40
|
+
|
41
|
+
def create
|
42
|
+
user = User.new
|
43
|
+
users_repository.put(user)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
```
|
48
|
+
6. Change dependency name inside your bean:
|
49
|
+
```ruby
|
50
|
+
class UsersCreator
|
51
|
+
bean :users_creator
|
52
|
+
|
53
|
+
inject :repo, ref: :users_repository, from: :repositories
|
54
|
+
|
55
|
+
def create
|
56
|
+
user = User.new
|
57
|
+
repo.put(user)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
7. Use factory method to instantiate the bean
|
62
|
+
```ruby
|
63
|
+
class RepositoryFactory
|
64
|
+
bean :users_creator, factory_method: :get_bean
|
65
|
+
|
66
|
+
inject :config
|
67
|
+
inject :users_repository
|
68
|
+
inject :admins_repository
|
69
|
+
|
70
|
+
def get_bean
|
71
|
+
if config.admin_access?
|
72
|
+
admins_repository
|
73
|
+
else
|
74
|
+
users_repository
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def create
|
79
|
+
user = User.new
|
80
|
+
repo.put(user)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
8. Class level beans (object will not be instantiated and class will be used for that bean instead). Set `instance: false`:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
class UsersCreator
|
88
|
+
bean :users_creator, instance: false
|
89
|
+
inject :users_repository
|
90
|
+
end
|
91
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class SmartIoC::BeanDefinition
|
2
|
+
attr_reader :name, :package, :path, :klass, :scope, :instance, :factory_method,
|
3
|
+
:context, :dependencies
|
4
|
+
|
5
|
+
def initialize(name:, package:, path:, klass:, scope:, context:, instance:, factory_method:)
|
6
|
+
not_nil(name, :name)
|
7
|
+
not_nil(package, :package)
|
8
|
+
not_nil(path, :path)
|
9
|
+
not_nil(klass, :klass)
|
10
|
+
not_nil(scope, :scope)
|
11
|
+
not_nil(context, :context)
|
12
|
+
not_nil(instance, :instance)
|
13
|
+
|
14
|
+
@name = name
|
15
|
+
@package = package
|
16
|
+
@path = path
|
17
|
+
@klass = klass
|
18
|
+
@scope = scope
|
19
|
+
@instance = instance
|
20
|
+
@factory_method = factory_method
|
21
|
+
@context = context
|
22
|
+
|
23
|
+
@dependencies = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_dependency(bean_name:, ref: nil, package: nil)
|
27
|
+
if !bean_name.is_a?(Symbol)
|
28
|
+
raise ArgumentError, 'bean name should be a Symbol'
|
29
|
+
end
|
30
|
+
|
31
|
+
if ref && !ref.is_a?(Symbol)
|
32
|
+
raise ArgumentError, 'ref name should be a Symbol'
|
33
|
+
end
|
34
|
+
|
35
|
+
if package && !package.is_a?(Symbol)
|
36
|
+
raise ArgumentError, 'package/from should be a Symbol'
|
37
|
+
end
|
38
|
+
|
39
|
+
@dependencies << SmartIoC::BeanDependency.new(
|
40
|
+
bean: bean_name,
|
41
|
+
ref: ref,
|
42
|
+
package: package
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_instance?
|
47
|
+
@instance
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_factory_method?
|
51
|
+
!@factory_method.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(bean_definition)
|
55
|
+
bean_definition.klass == @klass
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect
|
59
|
+
str = []
|
60
|
+
str << "class: #{@klass}"
|
61
|
+
str << "name: :#{@name}"
|
62
|
+
str << "package: :#{@package}"
|
63
|
+
str << "context: :#{@context}"
|
64
|
+
str << "path: #{@path}"
|
65
|
+
str << "instance: #{@instance}"
|
66
|
+
str << "factory_method: #{@factory_method}"
|
67
|
+
str.join("\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def not_nil(value, name)
|
73
|
+
if value.nil?
|
74
|
+
raise ArgumentError, ":#{name} should not be blank"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class SmartIoC::BeanDefinitionsStorage
|
2
|
+
def initialize
|
3
|
+
@collection = []
|
4
|
+
end
|
5
|
+
|
6
|
+
# @param bean_definition [BeanDefinition]
|
7
|
+
def push(bean_definition)
|
8
|
+
existing_bd = @collection.detect do |bd|
|
9
|
+
bd == bean_definition
|
10
|
+
end
|
11
|
+
|
12
|
+
if existing_bd
|
13
|
+
error_msg =
|
14
|
+
%Q(Not able to add bean to definitions storage.
|
15
|
+
Bean definition already exists.
|
16
|
+
New bean details:
|
17
|
+
#{bean_definition.inspect}
|
18
|
+
Existing bean details:
|
19
|
+
#{existing_bd.inspect})
|
20
|
+
|
21
|
+
raise ArgumentError, error_msg
|
22
|
+
end
|
23
|
+
|
24
|
+
@collection.push(bean_definition)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param klass [Class] bean class
|
28
|
+
# @return bean definition [BeanDefinition] or nil
|
29
|
+
def find_by_class(klass)
|
30
|
+
@collection.detect {|bd| bd.klass == klass}
|
31
|
+
end
|
32
|
+
|
33
|
+
def filter_by(bean_name, package = nil, context = nil)
|
34
|
+
bean_definitions = @collection.select do |bd|
|
35
|
+
bd.name == bean_name
|
36
|
+
end
|
37
|
+
|
38
|
+
if package
|
39
|
+
bean_definitions = bean_definitions.select do |bd|
|
40
|
+
bd.package == package
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if context
|
45
|
+
bean_definitions = bean_definitions.select do |bd|
|
46
|
+
bd.context == context
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
bean_definitions
|
51
|
+
end
|
52
|
+
|
53
|
+
# @bean_name [Symbol] bean name
|
54
|
+
# @package [Symbol] package name
|
55
|
+
# @context [Array[Symbol]] context
|
56
|
+
def filter_by_with_drop_to_default_context(bean_name, package = nil, context = nil)
|
57
|
+
bean_definitions = @collection.select do |bd|
|
58
|
+
bd.name == bean_name
|
59
|
+
end
|
60
|
+
|
61
|
+
if package
|
62
|
+
bean_definitions = bean_definitions.select do |bd|
|
63
|
+
bd.package == package
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if context
|
68
|
+
context_bean_definitions = bean_definitions.select do |bd|
|
69
|
+
bd.context == context
|
70
|
+
end
|
71
|
+
|
72
|
+
if !context_bean_definitions.empty?
|
73
|
+
bean_definitions = context_bean_definitions
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
bean_definitions
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class SmartIoC::BeanDependency
|
2
|
+
attr_reader :bean, :ref, :package
|
3
|
+
attr_accessor :bean_definition
|
4
|
+
|
5
|
+
def initialize(bean:, ref:, package:)
|
6
|
+
@bean = bean
|
7
|
+
@ref = ref
|
8
|
+
@package = package
|
9
|
+
end
|
10
|
+
|
11
|
+
def ref
|
12
|
+
@ref || @bean
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(dependency)
|
16
|
+
dependency.bean == @bean && dependency.package == @package
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# Instantiates beans according to their scopes
|
2
|
+
class SmartIoC::BeanFactory
|
3
|
+
def initialize(bean_definitions_storage, extra_package_contexts)
|
4
|
+
@bean_definitions_storage = bean_definitions_storage
|
5
|
+
@extra_package_contexts = extra_package_contexts
|
6
|
+
@bean_file_loader = SmartIoC::BeanFileLoader.new
|
7
|
+
@singleton_scope = SmartIoC::Scopes::Singleton.new
|
8
|
+
@prototype_scope = SmartIoC::Scopes::Prototype.new
|
9
|
+
@thread_scope = SmartIoC::Scopes::Request.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def clear_scopes
|
13
|
+
all_scopes.each(&:clear)
|
14
|
+
end
|
15
|
+
|
16
|
+
def force_clear_scopes
|
17
|
+
all_scopes.each(&:force_clear)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get bean from the container by it's name, package, context
|
21
|
+
# @param bean_name [Symbol] bean name
|
22
|
+
# @param package [Symbol] package name
|
23
|
+
# @param context [Symbol] context
|
24
|
+
# @return bean instance
|
25
|
+
# @raise [ArgumentError] if bean is not found
|
26
|
+
# @raise [ArgumentError] if ambiguous bean definition was found
|
27
|
+
def get_bean(bean_name, package: nil, context: SmartIoC::Container::DEFAULT_CONTEXT)
|
28
|
+
if !context.is_a?(Symbol)
|
29
|
+
raise ArgumentError, 'context should be a Symbol'
|
30
|
+
end
|
31
|
+
|
32
|
+
@bean_file_loader.require_bean(bean_name)
|
33
|
+
|
34
|
+
bds = @bean_definitions_storage.filter_by(bean_name, package, context)
|
35
|
+
if bds.size > 1
|
36
|
+
raise ArgumentError, "Unable to create bean :#{bean_name}.\nSeveral definitions were found.\n#{bds.map(&:inspect).join("\n\n")}"
|
37
|
+
elsif bds.size == 0
|
38
|
+
raise ArgumentError, "Unable to find bean :#{bean_name} in any packages"
|
39
|
+
end
|
40
|
+
|
41
|
+
bean_definition = bds.first
|
42
|
+
dependency_cache = {}
|
43
|
+
beans_cache = {}
|
44
|
+
|
45
|
+
autodetect_bean_definitions_for_dependencies(bean_definition, dependency_cache)
|
46
|
+
preload_beans(bean_definition, dependency_cache, beans_cache)
|
47
|
+
load_bean(bean_definition, dependency_cache, beans_cache)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def autodetect_bean_definitions_for_dependencies(bean_definition, dependency_cache)
|
53
|
+
bean_definition.dependencies.each do |dependency|
|
54
|
+
next if dependency_cache.has_key?(dependency)
|
55
|
+
|
56
|
+
@bean_file_loader.require_bean(dependency.ref)
|
57
|
+
|
58
|
+
bd = autodetect_bean_definition(
|
59
|
+
dependency.ref, dependency.package, bean_definition.package
|
60
|
+
)
|
61
|
+
|
62
|
+
dependency_cache[dependency] = bd
|
63
|
+
|
64
|
+
autodetect_bean_definitions_for_dependencies(bd, dependency_cache)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def autodetect_bean_definition(bean, package, parent_bean_package)
|
69
|
+
if package
|
70
|
+
bean_context = @extra_package_contexts.get_context(package)
|
71
|
+
bds = @bean_definitions_storage.filter_by_with_drop_to_default_context(bean, package, bean_context)
|
72
|
+
|
73
|
+
return bds.first if bds.size == 1
|
74
|
+
raise ArgumentError, "bean :#{bean} is not found in package :#{package}"
|
75
|
+
end
|
76
|
+
|
77
|
+
if parent_bean_package
|
78
|
+
bean_context = @extra_package_contexts.get_context(parent_bean_package)
|
79
|
+
bds = @bean_definitions_storage.filter_by_with_drop_to_default_context(bean, parent_bean_package, bean_context)
|
80
|
+
|
81
|
+
return bds.first if bds.size == 1
|
82
|
+
end
|
83
|
+
|
84
|
+
bds = @bean_definitions_storage.filter_by(bean)
|
85
|
+
bds_by_package = bds.group_by(&:package)
|
86
|
+
smart_bds = []
|
87
|
+
|
88
|
+
bds_by_package.each do |package, bd_list|
|
89
|
+
# try to find bean definition with package context
|
90
|
+
bd = bd_list.detect {|bd| bd.context == @extra_package_contexts.get_context(bd.package)}
|
91
|
+
smart_bds << bd if bd
|
92
|
+
|
93
|
+
# last try: find for :default context
|
94
|
+
if !bd
|
95
|
+
bd = bd_list.detect {|bd| bd.context == SmartIoC::Container::DEFAULT_CONTEXT}
|
96
|
+
smart_bds << bd if bd
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if smart_bds.size > 1
|
101
|
+
raise ArgumentError, "Unable to autodetect bean :#{bean}.\nSeveral definitions were found.\n#{smart_bds.map(&:inspect).join("\n\n")}. Set package directly for injected dependency"
|
102
|
+
end
|
103
|
+
|
104
|
+
return smart_bds.first
|
105
|
+
end
|
106
|
+
|
107
|
+
def preload_beans(bean_definition, dependency_cache, beans_cache)
|
108
|
+
preload_bean_instance(bean_definition, beans_cache)
|
109
|
+
|
110
|
+
bean_definition.dependencies.each do |dependency|
|
111
|
+
bd = dependency_cache[dependency]
|
112
|
+
|
113
|
+
next if beans_cache.has_key?(bd)
|
114
|
+
preload_beans(bd, dependency_cache, beans_cache)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def preload_bean_instance(bean_definition, beans_cache)
|
119
|
+
return beans_cache[bean_definition] if beans_cache.has_key?(bean_definition)
|
120
|
+
|
121
|
+
scope = get_scope(bean_definition)
|
122
|
+
bean = scope.get_bean(bean_definition.klass)
|
123
|
+
|
124
|
+
if bean
|
125
|
+
beans_cache[bean_definition] = bean
|
126
|
+
return bean
|
127
|
+
end
|
128
|
+
|
129
|
+
bean = if bean_definition.is_instance?
|
130
|
+
bean_definition.klass.allocate
|
131
|
+
else
|
132
|
+
bean_definition.klass
|
133
|
+
end
|
134
|
+
|
135
|
+
scope.save_bean(bean_definition.klass, bean)
|
136
|
+
beans_cache[bean_definition] = bean
|
137
|
+
|
138
|
+
bean
|
139
|
+
end
|
140
|
+
|
141
|
+
def load_bean(bean_definition, dependency_cache, beans_cache)
|
142
|
+
# first let's setup beans with factory_methods
|
143
|
+
zero_dep_bd_with_factory_methods = []
|
144
|
+
bd_with_factory_methods = []
|
145
|
+
|
146
|
+
beans_cache.each do |bean_definition, bean|
|
147
|
+
if bean_definition.has_factory_method?
|
148
|
+
if bean_definition.dependencies.size == 0
|
149
|
+
zero_dep_bd_with_factory_methods << bean_definition
|
150
|
+
else
|
151
|
+
bd_with_factory_methods << bean_definition
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
bd_with_factory_methods.each do |bean_definition|
|
157
|
+
if cross_refference_bd = get_cross_refference(bd_with_factory_methods, bean_definition, dependency_cache)
|
158
|
+
raise ArgumentError, "Factory method beans should not cross refference each other. Bean :#{bean_definition.name} cross refferences bean :#{cross_refference_bd.name}."
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
(zero_dep_bd_with_factory_methods + bd_with_factory_methods).each do |bean_definition|
|
163
|
+
inject_beans(bean_definition, dependency_cache, beans_cache)
|
164
|
+
bean = beans_cache[bean_definition]
|
165
|
+
bean = bean.send(bean_definition.factory_method)
|
166
|
+
beans_cache[bean_definition] = bean
|
167
|
+
end
|
168
|
+
|
169
|
+
inject_beans(bean_definition, dependency_cache, beans_cache)
|
170
|
+
|
171
|
+
beans_cache[bean_definition]
|
172
|
+
end
|
173
|
+
|
174
|
+
def inject_beans(bean_definition, dependency_cache, beans_cache)
|
175
|
+
bean = beans_cache[bean_definition]
|
176
|
+
bean_definition.dependencies.each do |dependency|
|
177
|
+
bd = dependency_cache[dependency]
|
178
|
+
dep_bean = beans_cache[bd]
|
179
|
+
bean.instance_variable_set(:"@#{dependency.bean}", dep_bean)
|
180
|
+
inject_beans(bd, dependency_cache, beans_cache)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def get_cross_refference(refer_bean_definitions, current_bean_definition, dependency_cache, seen_bean_definitions = [])
|
185
|
+
current_bean_definition.dependencies.each do |dependency|
|
186
|
+
bd = dependency_cache[dependency]
|
187
|
+
|
188
|
+
next if seen_bean_definitions.include?(bd)
|
189
|
+
|
190
|
+
if refer_bean_definitions.include?(bd)
|
191
|
+
return bd
|
192
|
+
end
|
193
|
+
|
194
|
+
if crbd = get_cross_refference(refer_bean_definitions, bd, dependency_cache, seen_bean_definitions + [bd])
|
195
|
+
return crbd
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def set_bean_dependencies(bean, bean_definition, parent_bean_package)
|
203
|
+
bean_definition.dependencies.each do |dependency|
|
204
|
+
context = if dependency.package
|
205
|
+
@extra_package_contexts.get_context(dependency.package)
|
206
|
+
end
|
207
|
+
|
208
|
+
dependent_bean = get_or_load_bean(dependency.ref, dependency.package, context, bean_definition.package)
|
209
|
+
|
210
|
+
bean.instance_variable_set(:"@#{dependency.bean}", dependent_bean)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def all_scopes
|
215
|
+
[@singleton_scope, @prototype_scope, @thread_scope]
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_scope(bean_definition)
|
219
|
+
case bean_definition.scope
|
220
|
+
when SmartIoC::Scopes::Singleton::VALUE
|
221
|
+
@singleton_scope
|
222
|
+
when SmartIoC::Scopes::Prototype::VALUE
|
223
|
+
@prototype_scope
|
224
|
+
when SmartIoC::Scopes::Request::VALUE
|
225
|
+
@thread_scope
|
226
|
+
else
|
227
|
+
raise ArgumentError, "bean definition for :#{bean_definition.name} has unsupported scope :#{bean_definition.scope}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class SmartIoC::BeanFileLoader
|
2
|
+
def initialize
|
3
|
+
@loaded_locations = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
# @param bean_name [Symbol] bean name
|
7
|
+
# return nil
|
8
|
+
def require_bean(bean_name)
|
9
|
+
locations = SmartIoC::BeanLocations.get_bean_locations(bean_name)
|
10
|
+
|
11
|
+
# load *.rb file if extra bean location was added or it was not loaded yet
|
12
|
+
location_count = locations.values.flatten.size
|
13
|
+
|
14
|
+
if !@loaded_locations.has_key?(bean_name) || @loaded_locations[bean_name] != location_count
|
15
|
+
locations.each do |package_name, locations|
|
16
|
+
locations.each do |location|
|
17
|
+
require location
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
@loaded_locations[bean_name] = location_count
|
22
|
+
end
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# SmartIoC::BeanLocations is a storage for locations of package bean definitions.
|
2
|
+
# Storage structure:
|
3
|
+
# {
|
4
|
+
# PACKAGE_NAME => { BEAN_SYM => BEAN_PATH}
|
5
|
+
# }
|
6
|
+
# Ex:
|
7
|
+
# {
|
8
|
+
# repository: {
|
9
|
+
# users_repository: ['/app/core/infrastructure/repository/users.rb'],
|
10
|
+
# posts_repository: ['/app/core/infrastructure/repository/posts.rb']
|
11
|
+
# }
|
12
|
+
# }
|
13
|
+
class SmartIoC::BeanLocations
|
14
|
+
@data = {}
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# @param package_name [Symbol] bean package name (ex: :repository)
|
18
|
+
# @param bean [Symbol] bean name (ex: :users_repository)
|
19
|
+
# @param bean_path [String] bean name (ex: :users_repository)
|
20
|
+
# @return nil
|
21
|
+
# @raise [ArgumentError] if bean previous bean definition with same name was found for package
|
22
|
+
def add_bean(package_name, bean, bean_path)
|
23
|
+
@data[package_name] ||= {}
|
24
|
+
package_beans = @data[package_name]
|
25
|
+
|
26
|
+
package_beans[bean] ||= []
|
27
|
+
package_beans[bean].push(bean_path)
|
28
|
+
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param bean [Symbol] bean name (ex: :users_repository)
|
33
|
+
# @return Hash[Array[String]] hash of bean definitions from all packages
|
34
|
+
def get_bean_locations(bean)
|
35
|
+
locations = {}
|
36
|
+
|
37
|
+
@data.each do |package, bean_locations|
|
38
|
+
if bean_locations.has_key?(bean)
|
39
|
+
locations[package] ||= []
|
40
|
+
locations[package] += bean_locations[bean]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
locations
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_all
|
48
|
+
@data.each do |package, bean_locations|
|
49
|
+
bean_locations.each do |bean, path|
|
50
|
+
load(path)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear
|
56
|
+
@data = {}
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param path [String] absolute bean path
|
60
|
+
# @return [nil or String] package name be absolute bean path
|
61
|
+
def get_bean_package(path)
|
62
|
+
@data.each do |package, bean_locations|
|
63
|
+
if bean_locations.values.flatten.include?(path)
|
64
|
+
return package
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|