smart_container 0.3.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -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/arbitary_lock'
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,8 +18,30 @@ 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'
21
22
  require_relative 'container/mixin'
22
23
 
24
+ class << self
25
+ # @param initial_container_klass [Class<SmartCore::Container>]
26
+ # @param container_definitions [Block]
27
+ # @return [SmartCore::Container]
28
+ #
29
+ # @api public
30
+ # @since 0.7.0
31
+ def define(initial_container_klass = self, &container_definitions)
32
+ unless initial_container_klass <= SmartCore::Container
33
+ raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE)
34
+ Base class should be a type of SmartCore::Container
35
+ ERROR_MESSAGE
36
+ end
37
+
38
+ Class.new(initial_container_klass, &container_definitions).new
39
+ end
40
+ end
41
+
42
+ # @since 0.4.0
43
+ include ::Enumerable
44
+
23
45
  # @since 0.1.0
24
46
  include DefinitionDSL
25
47
 
@@ -35,7 +57,7 @@ module SmartCore
35
57
  # @since 0.1.0
36
58
  def initialize
37
59
  build_registry!
38
- @access_lock = ArbitaryLock.new
60
+ @access_lock = ArbitraryLock.new
39
61
  end
40
62
 
41
63
  # @param dependency_name [String, Symbol]
@@ -44,8 +66,16 @@ module SmartCore
44
66
  #
45
67
  # @api public
46
68
  # @sicne 0.1.0
47
- def register(dependency_name, &dependency_definition)
48
- thread_safe { registry.register_dependency(dependency_name, &dependency_definition) }
69
+ # @version 0.8.0
70
+ def register(
71
+ dependency_name,
72
+ memoize: SmartCore::Container::Registry::DEFAULT_MEMOIZATION_BEHAVIOR,
73
+ &dependency_definition
74
+ )
75
+ thread_safe do
76
+ registry.register_dependency(dependency_name, memoize: memoize, &dependency_definition)
77
+ watcher.notify(dependency_name)
78
+ end
49
79
  end
50
80
 
51
81
  # @param namespace_name [String, Symbol]
@@ -54,8 +84,12 @@ module SmartCore
54
84
  #
55
85
  # @api public
56
86
  # @since 0.1.0
87
+ # @version 0.8.0
57
88
  def namespace(namespace_name, &dependencies_definition)
58
- thread_safe { registry.register_namespace(namespace_name, &dependencies_definition) }
89
+ thread_safe do
90
+ registry.register_namespace(namespace_name, &dependencies_definition)
91
+ watcher.notify(namespace_name)
92
+ end
59
93
  end
60
94
 
61
95
  # @param dependency_path [String, Symbol]
@@ -102,6 +136,61 @@ module SmartCore
102
136
  thread_safe { build_registry! }
103
137
  end
104
138
 
139
+ # @option all_variants [Boolean]
140
+ # @return [Array<String>]
141
+ #
142
+ # @api public
143
+ # @since 0.4.0
144
+ def keys(all_variants: SmartCore::Container::Registry::DEFAULT_KEY_EXTRACTION_BEHAVIOUR)
145
+ thread_safe { registry.keys(all_variants: all_variants) }
146
+ end
147
+
148
+ # @param key [String, Symbol]
149
+ # @return [Boolean]
150
+ #
151
+ # @api public
152
+ # @since 0.5.0
153
+ def key?(key)
154
+ thread_safe { DependencyResolver.key?(self, key) }
155
+ end
156
+
157
+ # @param namespace_path [String, Symbol]
158
+ # @return [Boolean]
159
+ #
160
+ # @api public
161
+ # @since 0.5.0
162
+ def namespace?(namespace_path)
163
+ thread_safe { DependencyResolver.namespace?(self, namespace_path) }
164
+ end
165
+
166
+ # @param dependency_path [String, Symbol]
167
+ # @option memoized [NilClass, Boolean]
168
+ # @return [Boolean]
169
+ #
170
+ # @api public
171
+ # @since 0.5.0
172
+ def dependency?(dependency_path, memoized: nil)
173
+ thread_safe { DependencyResolver.dependency?(self, dependency_path, memoized: memoized) }
174
+ end
175
+
176
+ # @option yield_all [Boolean]
177
+ # @param block [Block]
178
+ # @yield [dependency_name, dependency_value]
179
+ # @yield_param dependency_name [String]
180
+ # @yield_param dependency_value [Any, SmartCore::Container]
181
+ # @return [Enumerable]
182
+ #
183
+ # @api public
184
+ # @since 0.4.0
185
+ def each_dependency(
186
+ yield_all: SmartCore::Container::Registry::DEFAULT_ITERATION_YIELD_BEHAVIOUR,
187
+ &block
188
+ )
189
+ thread_safe { registry.each_dependency(yield_all: yield_all, &block) }
190
+ end
191
+ alias_method :each, :each_dependency
192
+ alias_method :each_pair, :each_dependency
193
+
105
194
  # @option resolve_dependencies [Boolean]
106
195
  # @return [Hash<String|Symbol,SmartCore::Container::Entities::Base|Any>]
107
196
  #
@@ -113,14 +202,56 @@ module SmartCore
113
202
  alias_method :to_h, :hash_tree
114
203
  alias_method :to_hash, :hash_tree
115
204
 
205
+ # @param entity_path [String]
206
+ # @param observer [Block]
207
+ # @yield [entity_path, container]
208
+ # @yieldparam entity_path [String]
209
+ # @yieldparam container [SmartCore::Container]
210
+ # @return [SmartCore::Container::DependencyWatcher::Observer]
211
+ #
212
+ # @api public
213
+ # @since 0.8.0
214
+ def observe(entity_path, &observer) # TODO: support for pattern-based pathes
215
+ thread_safe { watcher.watch(entity_path, &observer) }
216
+ end
217
+ alias_method :subscribe, :observe
218
+
219
+ # @param observer [SmartCore::Container::DependencyWatcher::Observer]
220
+ # @return [Boolean]
221
+ #
222
+ # @api public
223
+ # @since 0.8.0
224
+ def unobserve(observer)
225
+ thread_safe { watcher.unwatch(observer) }
226
+ end
227
+ alias_method :unsubscribe, :unobserve
228
+
229
+ # @param entity_path [String, Symbol, NilClass]
230
+ # @return [void]
231
+ #
232
+ # @api public
233
+ # @since 0.8.0
234
+ def clear_observers(entity_path = nil) # TODO: support for pattern-based pathes
235
+ thread_safe { watcher.clear_listeners(entity_path) }
236
+ end
237
+ alias_method :clear_listeners, :clear_observers
238
+
116
239
  private
117
240
 
241
+ # @return [SmartCore::Container::DependencyWatcher]
242
+ #
243
+ # @api private
244
+ # @since 0.8.0
245
+ attr_reader :watcher
246
+
118
247
  # @return [void]
119
248
  #
120
249
  # @api private
121
250
  # @since 0.1.0
251
+ # @version 0.8.0
122
252
  def build_registry!
123
253
  @registry = RegistryBuilder.build(self)
254
+ @watcher = SmartCore::Container::DependencyWatcher.new(self)
124
255
  end
125
256
 
126
257
  # @param block [Block]
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
- class SmartCore::Container::ArbitaryLock
5
+ class SmartCore::Container::ArbitraryLock
6
6
  # @return [void]
7
7
  #
8
8
  # @api private
@@ -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__, ArbitaryLock.new)
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__, ArbitaryLock.new)
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::ArbitaryLock.new
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]
@@ -5,6 +5,12 @@
5
5
  module SmartCore::Container::DependencyResolver
6
6
  require_relative 'dependency_resolver/route'
7
7
 
8
+ # @return [String]
9
+ #
10
+ # @api private
11
+ # @since 0.4.0
12
+ PATH_PART_SEPARATOR = '.'
13
+
8
14
  class << self
9
15
  # @param container [SmartCore::Container]
10
16
  # @param dependency_path [String, Symbol]
@@ -20,6 +26,54 @@ module SmartCore::Container::DependencyResolver
20
26
  container.registry.resolve(dependency_path).reveal
21
27
  end
22
28
 
29
+ # @param container [SmartCore::Container]
30
+ # @param key [String, Symbol]
31
+ # @return [Boolean]
32
+ #
33
+ # @api private
34
+ # @since 0.5.0
35
+ def key?(container, key)
36
+ extract(container, key)
37
+ true
38
+ rescue SmartCore::Container::ResolvingError
39
+ false
40
+ end
41
+
42
+ # @param container [SmartCore::Container]
43
+ # @param namespace_path [String, Symbol]
44
+ # @return [Boolean]
45
+ #
46
+ # @api private
47
+ # @since 0.5.0
48
+ def namespace?(container, namespace_path)
49
+ extract(container, namespace_path).is_a?(SmartCore::Container::Entities::Namespace)
50
+ rescue SmartCore::Container::ResolvingError
51
+ false
52
+ end
53
+
54
+ # @param container [SmartCore::Container]
55
+ # @param dependency_path [String, Symbol]
56
+ # @option memoized [NilClass, Boolean]
57
+ # @return [Boolean]
58
+ #
59
+ # @api private
60
+ # @since 0.5.0
61
+ def dependency?(container, dependency_path, memoized: nil)
62
+ entity = extract(container, dependency_path)
63
+
64
+ case
65
+ when memoized.nil?
66
+ entity.is_a?(SmartCore::Container::Entities::Dependency)
67
+ when !!memoized == true
68
+ entity.is_a?(SmartCore::Container::Entities::MemoizedDependency)
69
+ when !!memoized == false
70
+ entity.is_a?(SmartCore::Container::Entities::Dependency) &&
71
+ !entity.is_a?(SmartCore::Container::Entities::MemoizedDependency)
72
+ end
73
+ rescue SmartCore::Container::ResolvingError
74
+ false
75
+ end
76
+
23
77
  # @param container [SmartCore::Container]
24
78
  # @param dependency_path [String, Symbol]
25
79
  # @return [SmartCore::Container, Any]
@@ -32,12 +86,11 @@ module SmartCore::Container::DependencyResolver
32
86
  #
33
87
  # @api private
34
88
  # @since 0.1.0
35
- # @version 0.1.0
36
89
  def resolve(container, dependency_path)
37
90
  entity = container
38
91
  Route.build(dependency_path).each do |cursor|
39
92
  entity = entity.registry.resolve(cursor.current_path)
40
- prevent_cursor_overflow!(cursor, entity)
93
+ prevent_ambiguous_resolving!(cursor, entity)
41
94
  entity = entity.reveal
42
95
  end
43
96
  entity
@@ -47,6 +100,25 @@ module SmartCore::Container::DependencyResolver
47
100
 
48
101
  private
49
102
 
103
+ # @param container [SmartCore::Container]
104
+ # @param entity_path [String, Symbol]
105
+ # @return [SmartCore::Container::Entities::Base]
106
+ #
107
+ # @api private
108
+ # @since 0.5.0
109
+ def extract(container, entity_path)
110
+ resolved_entity = container
111
+ extracted_entity = container
112
+
113
+ Route.build(entity_path).each do |cursor|
114
+ resolved_entity = resolved_entity.registry.resolve(cursor.current_path)
115
+ extracted_entity = resolved_entity
116
+ resolved_entity = resolved_entity.reveal
117
+ end
118
+
119
+ extracted_entity
120
+ end
121
+
50
122
  # @param cursor [SmartCore::Container::DependencyResolver::Route::Cursor]
51
123
  # @param entity [SmartCore::Container::Entities::Base]
52
124
  # @return [void]
@@ -54,8 +126,8 @@ module SmartCore::Container::DependencyResolver
54
126
  # @raise [SmartCore::Container::ResolvingError]
55
127
  #
56
128
  # @api private
57
- # @since 0.1.0
58
- def prevent_cursor_overflow!(cursor, entity)
129
+ # @since 0.5.0
130
+ def prevent_ambiguous_resolving!(cursor, entity)
59
131
  if cursor.last? && entity.is_a?(SmartCore::Container::Entities::Namespace)
60
132
  raise(
61
133
  SmartCore::Container::ResolvingError.new(
@@ -84,10 +156,9 @@ module SmartCore::Container::DependencyResolver
84
156
  # @api private
85
157
  # @since 0.1.0
86
158
  def process_resolving_error(dependency_path, error)
87
- full_dependency_path = Route.build_path(dependency_path, error.path_part)
88
- raise(SmartCore::Container::ResolvingError.new(<<~MESSAGE, path_part: full_dependency_path))
89
- #{error.message} (incorrect path: "#{full_dependency_path}")
90
- MESSAGE
159
+ full_dependency_path = Route.build_path(error.path_part)
160
+ message = "#{error.message} (incorrect path: \"#{full_dependency_path}\")"
161
+ raise(SmartCore::Container::ResolvingError.new(message, path_part: full_dependency_path))
91
162
  end
92
163
  end
93
164
  end
@@ -2,18 +2,13 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.4.0
5
6
  class SmartCore::Container::DependencyResolver::Route
6
7
  require_relative 'route/cursor'
7
8
 
8
9
  # @since 0.1.0
9
10
  include Enumerable
10
11
 
11
- # @return [String]
12
- #
13
- # @api private
14
- # @since 0.1.0
15
- PATH_PART_SEPARATOR = '.'
16
-
17
12
  class << self
18
13
  # @param path [String, Symbol]
19
14
  # @return [SmartCore::Container::DependencyResolver::Route]
@@ -28,8 +23,9 @@ class SmartCore::Container::DependencyResolver::Route
28
23
  #
29
24
  # @api private
30
25
  # @since 0.1.0
26
+ # @version 0.4.0
31
27
  def build_path(*path_parts)
32
- path_parts.join(PATH_PART_SEPARATOR)
28
+ path_parts.join(SmartCore::Container::DependencyResolver::PATH_PART_SEPARATOR)
33
29
  end
34
30
  end
35
31
 
@@ -50,9 +46,10 @@ class SmartCore::Container::DependencyResolver::Route
50
46
  #
51
47
  # @api private
52
48
  # @since 0.1.0
49
+ # @version 0.4.0
53
50
  def initialize(path)
54
51
  @path = path
55
- @path_parts = path.split(PATH_PART_SEPARATOR).freeze
52
+ @path_parts = path.split(SmartCore::Container::DependencyResolver::PATH_PART_SEPARATOR).freeze
56
53
  @size = @path_parts.size
57
54
  end
58
55
 
@@ -0,0 +1,149 @@
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 { |k, v| k[v] = [] }
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
+ def notify_listeners(entity_path)
76
+ entity_path = indifferently_accessable_path(entity_path)
77
+ observers.fetch(entity_path).each(&:notify!) if observers.key?(entity_path)
78
+ end
79
+
80
+ # @param entity_path [String, Symbol]
81
+ # @param observer [Proc]
82
+ # @return [SmartCore::Container::DependencyWatcher::Observer]
83
+ #
84
+ # @api private
85
+ # @since 0.8.0
86
+ def listen(entity_path, observer) # TODO: support for pattern-based pathes
87
+ raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE) unless observer.is_a?(Proc)
88
+ Observer is missing: you should provide an observer proc object (block).
89
+ ERROR_MESSAGE
90
+
91
+ entity_path = indifferently_accessable_path(entity_path)
92
+ Observer.new(container, entity_path, observer).tap { |obs| observers[entity_path] << obs }
93
+ end
94
+
95
+ # @param observer [SmartCore::Container::DependencyWatcher::Observer]
96
+ # @return [Boolean]
97
+ #
98
+ # @api private
99
+ # @since 0.8.0
100
+ def remove_listener(observer)
101
+ unless observer.is_a?(SmartCore::Container::DependencyWatcher::Observer)
102
+ raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE)
103
+ You should provide an observer object for unsubscribion
104
+ (an instance of SmartCore::Container::DependencyWatcher::Observer).
105
+ ERROR_MESSAGE
106
+ end
107
+
108
+ unsubscribed = false
109
+ observers.each_value do |observer_list|
110
+ if observer_list.delete(observer)
111
+ unsubscribed = true
112
+ break
113
+ end
114
+ end
115
+ unsubscribed
116
+ end
117
+
118
+ # @param entity_path [String, Symbol]
119
+ # @return [void]
120
+ #
121
+ # @api private
122
+ # @since 0.8.0
123
+ def remove_listeners(entity_path) # TODO: support for pattern-based pathes
124
+ if entity_path.nil?
125
+ observers.each_value(&:clear)
126
+ else
127
+ entity_path = indifferently_accessable_path(entity_path)
128
+ observers[entity_path].clear if observers.key?(entity_path)
129
+ end
130
+ end
131
+
132
+ # @param entity_path [String, Symbol]
133
+ # @return [String]
134
+ #
135
+ # @api private
136
+ # @since 0.8.0
137
+ def indifferently_accessable_path(entity_path)
138
+ SmartCore::Container::KeyGuard.indifferently_accessable_key(entity_path)
139
+ end
140
+
141
+ # @param block [Block]
142
+ # @return [Any]
143
+ #
144
+ # @api private
145
+ # @since 0.8.0
146
+ def thread_safe(&block)
147
+ @access_lock.thread_safe(&block)
148
+ end
149
+ end