smart_container 0.5.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -2
- data/CHANGELOG.md +37 -0
- data/Gemfile.lock +80 -59
- data/README.md +224 -16
- data/Rakefile +2 -1
- data/lib/smart_core/container.rb +104 -7
- data/lib/smart_core/container/{arbitary_lock.rb → arbitrary_lock.rb} +1 -1
- data/lib/smart_core/container/definition_dsl.rb +2 -2
- data/lib/smart_core/container/definition_dsl/command_set.rb +1 -1
- data/lib/smart_core/container/definition_dsl/commands/definition/register.rb +1 -1
- data/lib/smart_core/container/dependency_compatability/definition.rb +4 -0
- data/lib/smart_core/container/dependency_resolver.rb +12 -4
- data/lib/smart_core/container/dependency_watcher.rb +151 -0
- data/lib/smart_core/container/dependency_watcher/observer.rb +46 -0
- data/lib/smart_core/container/entities/dependency.rb +3 -1
- data/lib/smart_core/container/entities/dependency_builder.rb +27 -59
- data/lib/smart_core/container/entities/memoized_dependency.rb +4 -2
- data/lib/smart_core/container/entities/namespace.rb +23 -6
- data/lib/smart_core/container/entities/namespace_builder.rb +14 -34
- data/lib/smart_core/container/host.rb +81 -0
- data/lib/smart_core/container/mixin.rb +2 -2
- data/lib/smart_core/container/registry.rb +17 -9
- data/lib/smart_core/container/version.rb +3 -3
- data/smart_container.gemspec +7 -7
- metadata +23 -21
- data/.travis.yml +0 -21
data/Rakefile
CHANGED
@@ -4,6 +4,7 @@ require 'bundler/gem_tasks'
|
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
require 'rubocop'
|
6
6
|
require 'rubocop/rake_task'
|
7
|
+
require 'rubocop-rails'
|
7
8
|
require 'rubocop-performance'
|
8
9
|
require 'rubocop-rspec'
|
9
10
|
require 'rubocop-rake'
|
@@ -11,8 +12,8 @@ require 'rubocop-rake'
|
|
11
12
|
RuboCop::RakeTask.new(:rubocop) do |t|
|
12
13
|
config_path = File.expand_path(File.join('.rubocop.yml'), __dir__)
|
13
14
|
t.options = ['--config', config_path]
|
14
|
-
t.requires << 'rubocop-performance'
|
15
15
|
t.requires << 'rubocop-rspec'
|
16
|
+
t.requires << 'rubocop-performance'
|
16
17
|
t.requires << 'rubocop-rake'
|
17
18
|
end
|
18
19
|
|
data/lib/smart_core/container.rb
CHANGED
@@ -7,10 +7,10 @@ require 'smart_core'
|
|
7
7
|
module SmartCore
|
8
8
|
# @api public
|
9
9
|
# @since 0.1.0
|
10
|
-
class Container
|
10
|
+
class Container # rubocop:disable Metrics/ClassLength
|
11
11
|
require_relative 'container/version'
|
12
12
|
require_relative 'container/errors'
|
13
|
-
require_relative 'container/
|
13
|
+
require_relative 'container/arbitrary_lock'
|
14
14
|
require_relative 'container/key_guard'
|
15
15
|
require_relative 'container/entities'
|
16
16
|
require_relative 'container/definition_dsl'
|
@@ -18,27 +18,77 @@ module SmartCore
|
|
18
18
|
require_relative 'container/registry'
|
19
19
|
require_relative 'container/registry_builder'
|
20
20
|
require_relative 'container/dependency_resolver'
|
21
|
+
require_relative 'container/dependency_watcher'
|
22
|
+
require_relative 'container/host'
|
21
23
|
require_relative 'container/mixin'
|
22
24
|
|
25
|
+
class << self
|
26
|
+
# @param initial_container_klass [Class<SmartCore::Container>]
|
27
|
+
# @param container_definitions [Block]
|
28
|
+
# @return [SmartCore::Container]
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
# @since 0.7.0
|
32
|
+
def define(initial_container_klass = self, &container_definitions)
|
33
|
+
unless initial_container_klass <= SmartCore::Container
|
34
|
+
raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE)
|
35
|
+
Base class should be a type of SmartCore::Container
|
36
|
+
ERROR_MESSAGE
|
37
|
+
end
|
38
|
+
|
39
|
+
Class.new(initial_container_klass, &container_definitions).new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
23
43
|
# @since 0.4.0
|
24
44
|
include ::Enumerable
|
25
45
|
|
26
46
|
# @since 0.1.0
|
27
47
|
include DefinitionDSL
|
28
48
|
|
49
|
+
# @return [NilClass]
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
# @since 0.8.1
|
53
|
+
NO_HOST_CONTAINER = nil
|
54
|
+
|
55
|
+
# @return [NilClass]
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
# @since 0.8.1
|
59
|
+
NO_HOST_PATH = nil
|
60
|
+
|
29
61
|
# @return [SmartCore::Container::Registry]
|
30
62
|
#
|
31
63
|
# @api private
|
32
64
|
# @since 0.1.0
|
33
65
|
attr_reader :registry
|
34
66
|
|
67
|
+
# @return [SmartCore::Container::Host]
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
# @since 0.8.1
|
71
|
+
attr_reader :host
|
72
|
+
|
73
|
+
# @return [SmartCore::Container::DependencyWatcher]
|
74
|
+
#
|
75
|
+
# @api private
|
76
|
+
# @since 0.8.0
|
77
|
+
attr_reader :watcher
|
78
|
+
|
79
|
+
# @option host_container [SmartCore::Container, NilClass]
|
80
|
+
# @option host_path [String, NilClass]
|
35
81
|
# @return [void]
|
36
82
|
#
|
37
83
|
# @api public
|
38
84
|
# @since 0.1.0
|
39
|
-
|
85
|
+
# @version 0.8.1
|
86
|
+
def initialize(host_container: NO_HOST_CONTAINER, host_path: NO_HOST_PATH)
|
87
|
+
@host = SmartCore::Container::Host.build(host_container, host_path)
|
40
88
|
build_registry!
|
41
|
-
@
|
89
|
+
@watcher = SmartCore::Container::DependencyWatcher.new(self)
|
90
|
+
@host_path = host_path
|
91
|
+
@access_lock = ArbitraryLock.new
|
42
92
|
end
|
43
93
|
|
44
94
|
# @param dependency_name [String, Symbol]
|
@@ -47,8 +97,16 @@ module SmartCore
|
|
47
97
|
#
|
48
98
|
# @api public
|
49
99
|
# @sicne 0.1.0
|
50
|
-
|
51
|
-
|
100
|
+
# @version 0.8.0
|
101
|
+
def register(
|
102
|
+
dependency_name,
|
103
|
+
memoize: SmartCore::Container::Registry::DEFAULT_MEMOIZATION_BEHAVIOR,
|
104
|
+
&dependency_definition
|
105
|
+
)
|
106
|
+
thread_safe do
|
107
|
+
registry.register_dependency(dependency_name, memoize: memoize, &dependency_definition)
|
108
|
+
watcher.notify(dependency_name)
|
109
|
+
end
|
52
110
|
end
|
53
111
|
|
54
112
|
# @param namespace_name [String, Symbol]
|
@@ -57,8 +115,12 @@ module SmartCore
|
|
57
115
|
#
|
58
116
|
# @api public
|
59
117
|
# @since 0.1.0
|
118
|
+
# @version 0.8.0
|
60
119
|
def namespace(namespace_name, &dependencies_definition)
|
61
|
-
thread_safe
|
120
|
+
thread_safe do
|
121
|
+
registry.register_namespace(namespace_name, self, &dependencies_definition)
|
122
|
+
watcher.notify(namespace_name)
|
123
|
+
end
|
62
124
|
end
|
63
125
|
|
64
126
|
# @param dependency_path [String, Symbol]
|
@@ -171,12 +233,47 @@ module SmartCore
|
|
171
233
|
alias_method :to_h, :hash_tree
|
172
234
|
alias_method :to_hash, :hash_tree
|
173
235
|
|
236
|
+
# @param entity_path [String]
|
237
|
+
# @param observer [Block]
|
238
|
+
# @yield [entity_path, container]
|
239
|
+
# @yieldparam entity_path [String]
|
240
|
+
# @yieldparam container [SmartCore::Container]
|
241
|
+
# @return [SmartCore::Container::DependencyWatcher::Observer]
|
242
|
+
#
|
243
|
+
# @api public
|
244
|
+
# @since 0.8.0
|
245
|
+
def observe(entity_path, &observer) # TODO: support for pattern-based pathes
|
246
|
+
thread_safe { watcher.watch(entity_path, &observer) }
|
247
|
+
end
|
248
|
+
alias_method :subscribe, :observe
|
249
|
+
|
250
|
+
# @param observer [SmartCore::Container::DependencyWatcher::Observer]
|
251
|
+
# @return [Boolean]
|
252
|
+
#
|
253
|
+
# @api public
|
254
|
+
# @since 0.8.0
|
255
|
+
def unobserve(observer)
|
256
|
+
thread_safe { watcher.unwatch(observer) }
|
257
|
+
end
|
258
|
+
alias_method :unsubscribe, :unobserve
|
259
|
+
|
260
|
+
# @param entity_path [String, Symbol, NilClass]
|
261
|
+
# @return [void]
|
262
|
+
#
|
263
|
+
# @api public
|
264
|
+
# @since 0.8.0
|
265
|
+
def clear_observers(entity_path = nil) # TODO: support for pattern-based pathes
|
266
|
+
thread_safe { watcher.clear_listeners(entity_path) }
|
267
|
+
end
|
268
|
+
alias_method :clear_listeners, :clear_observers
|
269
|
+
|
174
270
|
private
|
175
271
|
|
176
272
|
# @return [void]
|
177
273
|
#
|
178
274
|
# @api private
|
179
275
|
# @since 0.1.0
|
276
|
+
# @version 0.8.1
|
180
277
|
def build_registry!
|
181
278
|
@registry = RegistryBuilder.build(self)
|
182
279
|
end
|
@@ -17,7 +17,7 @@ class SmartCore::Container
|
|
17
17
|
def included(base_klass)
|
18
18
|
base_klass.instance_variable_set(:@__container_definition_commands__, CommandSet.new)
|
19
19
|
base_klass.instance_variable_set(:@__container_instantiation_commands__, CommandSet.new)
|
20
|
-
base_klass.instance_variable_set(:@__container_definition_lock__,
|
20
|
+
base_klass.instance_variable_set(:@__container_definition_lock__, ArbitraryLock.new)
|
21
21
|
base_klass.singleton_class.send(:attr_reader, :__container_definition_commands__)
|
22
22
|
base_klass.singleton_class.send(:attr_reader, :__container_instantiation_commands__)
|
23
23
|
base_klass.extend(ClassMethods)
|
@@ -36,7 +36,7 @@ class SmartCore::Container
|
|
36
36
|
def inherited(child_klass)
|
37
37
|
child_klass.instance_variable_set(:@__container_definition_commands__, CommandSet.new)
|
38
38
|
child_klass.instance_variable_set(:@__container_instantiation_commands__, CommandSet.new)
|
39
|
-
child_klass.instance_variable_set(:@__container_definition_lock__,
|
39
|
+
child_klass.instance_variable_set(:@__container_definition_lock__, ArbitraryLock.new)
|
40
40
|
SmartCore::Container::DefinitionDSL::Inheritance.inherit(base: self, child: child_klass)
|
41
41
|
child_klass.singleton_class.prepend(ClassInheritance)
|
42
42
|
super
|
@@ -18,7 +18,7 @@ class SmartCore::Container::DefinitionDSL::CommandSet
|
|
18
18
|
# @since 0.1.0
|
19
19
|
def initialize
|
20
20
|
@commands = []
|
21
|
-
@access_lock = SmartCore::Container::
|
21
|
+
@access_lock = SmartCore::Container::ArbitraryLock.new
|
22
22
|
end
|
23
23
|
|
24
24
|
# @param [SmartCore::Container::DefinitionDSL::Commands::Base]
|
@@ -36,7 +36,7 @@ module SmartCore::Container::DefinitionDSL::Commands::Definition
|
|
36
36
|
# @since 0.1.0
|
37
37
|
# @version 0.2.0
|
38
38
|
def call(registry)
|
39
|
-
registry.register_dependency(dependency_name, memoize, &dependency_definition)
|
39
|
+
registry.register_dependency(dependency_name, memoize: memoize, &dependency_definition)
|
40
40
|
end
|
41
41
|
|
42
42
|
# @return [SmartCore::Container::DefinitionDSL::Commands::Definition::Register]
|
@@ -13,6 +13,7 @@ module SmartCore::Container::DependencyCompatability::Definition
|
|
13
13
|
#
|
14
14
|
# @api private
|
15
15
|
# @since 0.1.0
|
16
|
+
# rubocop:disable Lint/EmptyBlock
|
16
17
|
def potential_namespace_overlap?(container_klass, dependency_name)
|
17
18
|
anonymous_container = Class.new(container_klass).new
|
18
19
|
anonymous_container.register(dependency_name, &(proc {}))
|
@@ -20,6 +21,7 @@ module SmartCore::Container::DependencyCompatability::Definition
|
|
20
21
|
rescue SmartCore::Container::DependencyOverNamespaceOverlapError
|
21
22
|
true
|
22
23
|
end
|
24
|
+
# rubocop:enable Lint/EmptyBlock
|
23
25
|
|
24
26
|
# @param container_klass [Class<SmartCore::Container>]
|
25
27
|
# @param namespace_name [String, Symbol]
|
@@ -27,6 +29,7 @@ module SmartCore::Container::DependencyCompatability::Definition
|
|
27
29
|
#
|
28
30
|
# @api private
|
29
31
|
# @since 0.1.0
|
32
|
+
# rubocop:disable Lint/EmptyBlock
|
30
33
|
def potential_dependency_overlap?(container_klass, namespace_name)
|
31
34
|
anonymous_container = Class.new(container_klass).new
|
32
35
|
anonymous_container.namespace(namespace_name, &(proc {}))
|
@@ -34,5 +37,6 @@ module SmartCore::Container::DependencyCompatability::Definition
|
|
34
37
|
rescue SmartCore::Container::NamespaceOverDependencyOverlapError
|
35
38
|
true
|
36
39
|
end
|
40
|
+
# rubocop:enable Lint/EmptyBlock
|
37
41
|
end
|
38
42
|
end
|
@@ -22,8 +22,9 @@ module SmartCore::Container::DependencyResolver
|
|
22
22
|
#
|
23
23
|
# @api private
|
24
24
|
# @since 0.1.0
|
25
|
+
# @version 0.8.1
|
25
26
|
def fetch(container, dependency_path)
|
26
|
-
container.registry.resolve(dependency_path).reveal
|
27
|
+
container.registry.resolve(dependency_path).reveal(container)
|
27
28
|
end
|
28
29
|
|
29
30
|
# @param container [SmartCore::Container]
|
@@ -62,7 +63,7 @@ module SmartCore::Container::DependencyResolver
|
|
62
63
|
entity = extract(container, dependency_path)
|
63
64
|
|
64
65
|
case
|
65
|
-
when memoized
|
66
|
+
when memoized == nil
|
66
67
|
entity.is_a?(SmartCore::Container::Entities::Dependency)
|
67
68
|
when !!memoized == true
|
68
69
|
entity.is_a?(SmartCore::Container::Entities::MemoizedDependency)
|
@@ -86,12 +87,16 @@ module SmartCore::Container::DependencyResolver
|
|
86
87
|
#
|
87
88
|
# @api private
|
88
89
|
# @since 0.1.0
|
90
|
+
# @version 0.8.1
|
89
91
|
def resolve(container, dependency_path)
|
90
92
|
entity = container
|
93
|
+
host_container = container
|
94
|
+
|
91
95
|
Route.build(dependency_path).each do |cursor|
|
92
96
|
entity = entity.registry.resolve(cursor.current_path)
|
93
97
|
prevent_ambiguous_resolving!(cursor, entity)
|
94
|
-
entity = entity.reveal
|
98
|
+
entity = entity.reveal(host_container)
|
99
|
+
host_container = entity.is_a?(SmartCore::Container) ? entity : nil
|
95
100
|
end
|
96
101
|
entity
|
97
102
|
rescue SmartCore::Container::ResolvingError => error
|
@@ -106,14 +111,17 @@ module SmartCore::Container::DependencyResolver
|
|
106
111
|
#
|
107
112
|
# @api private
|
108
113
|
# @since 0.5.0
|
114
|
+
# @version 0.8.1
|
109
115
|
def extract(container, entity_path)
|
110
116
|
resolved_entity = container
|
111
117
|
extracted_entity = container
|
118
|
+
host_container = container
|
112
119
|
|
113
120
|
Route.build(entity_path).each do |cursor|
|
114
121
|
resolved_entity = resolved_entity.registry.resolve(cursor.current_path)
|
115
122
|
extracted_entity = resolved_entity
|
116
|
-
resolved_entity = resolved_entity.reveal
|
123
|
+
resolved_entity = resolved_entity.reveal(host_container)
|
124
|
+
host_container = resolved_entity.is_a?(SmartCore::Container) ? resolved_entity : nil
|
117
125
|
end
|
118
126
|
|
119
127
|
extracted_entity
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 0.8.0
|
5
|
+
class SmartCore::Container::DependencyWatcher
|
6
|
+
require_relative 'dependency_watcher/observer'
|
7
|
+
|
8
|
+
# @param container [SmartCore::Container]
|
9
|
+
# @return [void]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
# @since 0.8.0
|
13
|
+
def initialize(container)
|
14
|
+
@container = container
|
15
|
+
@observers = Hash.new { |h, k| h[k] = [] }
|
16
|
+
@access_lock = SmartCore::Container::ArbitraryLock.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param entity_path [String, Symbol]
|
20
|
+
# @return [void]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
# @since 0.8.0
|
24
|
+
def notify(entity_path)
|
25
|
+
thread_safe { notify_listeners(entity_path) }
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param entity_path [String, Symbol]
|
29
|
+
# @param observer [Block]
|
30
|
+
# @return [SmartCore::Container::DependencyWatcher::Observer]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
# @since 0.8.0
|
34
|
+
def watch(entity_path, &observer) # TODO: support for pattern-based pathes
|
35
|
+
thread_safe { listen(entity_path, observer) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param observer [SmartCore::Container::DependencyWatcher::Observer]
|
39
|
+
# @return [Boolean]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @since 0.8.0
|
43
|
+
def unwatch(observer)
|
44
|
+
thread_safe { remove_listener(observer) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param entity_path [String, Symbol, NilClass]
|
48
|
+
# @return [void]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
# @since 0.8.0
|
52
|
+
def clear_listeners(entity_path = nil) # TODO: support for pattern-based pathes
|
53
|
+
thread_safe { remove_listeners(entity_path) }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# @return [SmartCore::Container]
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
# @since 0.8.0
|
62
|
+
attr_reader :container
|
63
|
+
|
64
|
+
# @return [Hash<String,SmartCore::Container::DependencyWatcher::Observer>]
|
65
|
+
#
|
66
|
+
# @api private
|
67
|
+
# @since 0.8.0
|
68
|
+
attr_reader :observers
|
69
|
+
|
70
|
+
# @param entity_path [String, Symbol]
|
71
|
+
# @return [void]
|
72
|
+
#
|
73
|
+
# @api private
|
74
|
+
# @since 0.8.0
|
75
|
+
# @version 0.8.1
|
76
|
+
def notify_listeners(entity_path)
|
77
|
+
entity_path = indifferently_accessable_path(entity_path)
|
78
|
+
observers.fetch(entity_path).each(&:notify!) if observers.key?(entity_path)
|
79
|
+
container.host.notify_about_nested_changement(entity_path)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param entity_path [String, Symbol]
|
83
|
+
# @param observer [Proc]
|
84
|
+
# @return [SmartCore::Container::DependencyWatcher::Observer]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
# @since 0.8.0
|
88
|
+
def listen(entity_path, observer) # TODO: support for pattern-based pathes
|
89
|
+
raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE) unless observer.is_a?(Proc)
|
90
|
+
Observer is missing: you should provide an observer proc object (block).
|
91
|
+
ERROR_MESSAGE
|
92
|
+
|
93
|
+
entity_path = indifferently_accessable_path(entity_path)
|
94
|
+
Observer.new(container, entity_path, observer).tap { |obs| observers[entity_path] << obs }
|
95
|
+
end
|
96
|
+
|
97
|
+
# @param observer [SmartCore::Container::DependencyWatcher::Observer]
|
98
|
+
# @return [Boolean]
|
99
|
+
#
|
100
|
+
# @api private
|
101
|
+
# @since 0.8.0
|
102
|
+
def remove_listener(observer)
|
103
|
+
unless observer.is_a?(SmartCore::Container::DependencyWatcher::Observer)
|
104
|
+
raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE)
|
105
|
+
You should provide an observer object for unsubscribion
|
106
|
+
(an instance of SmartCore::Container::DependencyWatcher::Observer).
|
107
|
+
ERROR_MESSAGE
|
108
|
+
end
|
109
|
+
|
110
|
+
unsubscribed = false
|
111
|
+
observers.each_value do |observer_list|
|
112
|
+
if observer_list.delete(observer)
|
113
|
+
unsubscribed = true
|
114
|
+
break
|
115
|
+
end
|
116
|
+
end
|
117
|
+
unsubscribed
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param entity_path [String, Symbol]
|
121
|
+
# @return [void]
|
122
|
+
#
|
123
|
+
# @api private
|
124
|
+
# @since 0.8.0
|
125
|
+
def remove_listeners(entity_path) # TODO: support for pattern-based pathes
|
126
|
+
if entity_path == nil
|
127
|
+
observers.each_value(&:clear)
|
128
|
+
else
|
129
|
+
entity_path = indifferently_accessable_path(entity_path)
|
130
|
+
observers[entity_path].clear if observers.key?(entity_path)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# @param entity_path [String, Symbol]
|
135
|
+
# @return [String]
|
136
|
+
#
|
137
|
+
# @api private
|
138
|
+
# @since 0.8.0
|
139
|
+
def indifferently_accessable_path(entity_path)
|
140
|
+
SmartCore::Container::KeyGuard.indifferently_accessable_key(entity_path)
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param block [Block]
|
144
|
+
# @return [Any]
|
145
|
+
#
|
146
|
+
# @api private
|
147
|
+
# @since 0.8.0
|
148
|
+
def thread_safe(&block)
|
149
|
+
@access_lock.thread_safe(&block)
|
150
|
+
end
|
151
|
+
end
|