smart_container 0.3.0 → 0.8.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.
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