smart_container 0.5.0 → 0.9.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 +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
|