smart_container 0.4.0 → 0.8.1

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,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
- def initialize
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
- @access_lock = ArbitaryLock.new
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
- def register(dependency_name, &dependency_definition)
51
- thread_safe { registry.register_dependency(dependency_name, &dependency_definition) }
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 { registry.register_namespace(namespace_name, &dependencies_definition) }
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]
@@ -114,6 +176,34 @@ module SmartCore
114
176
  thread_safe { registry.keys(all_variants: all_variants) }
115
177
  end
116
178
 
179
+ # @param key [String, Symbol]
180
+ # @return [Boolean]
181
+ #
182
+ # @api public
183
+ # @since 0.5.0
184
+ def key?(key)
185
+ thread_safe { DependencyResolver.key?(self, key) }
186
+ end
187
+
188
+ # @param namespace_path [String, Symbol]
189
+ # @return [Boolean]
190
+ #
191
+ # @api public
192
+ # @since 0.5.0
193
+ def namespace?(namespace_path)
194
+ thread_safe { DependencyResolver.namespace?(self, namespace_path) }
195
+ end
196
+
197
+ # @param dependency_path [String, Symbol]
198
+ # @option memoized [NilClass, Boolean]
199
+ # @return [Boolean]
200
+ #
201
+ # @api public
202
+ # @since 0.5.0
203
+ def dependency?(dependency_path, memoized: nil)
204
+ thread_safe { DependencyResolver.dependency?(self, dependency_path, memoized: memoized) }
205
+ end
206
+
117
207
  # @option yield_all [Boolean]
118
208
  # @param block [Block]
119
209
  # @yield [dependency_name, dependency_value]
@@ -143,12 +233,47 @@ module SmartCore
143
233
  alias_method :to_h, :hash_tree
144
234
  alias_method :to_hash, :hash_tree
145
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
+
146
270
  private
147
271
 
148
272
  # @return [void]
149
273
  #
150
274
  # @api private
151
275
  # @since 0.1.0
276
+ # @version 0.8.1
152
277
  def build_registry!
153
278
  @registry = RegistryBuilder.build(self)
154
279
  end
@@ -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]
@@ -22,8 +22,57 @@ 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)
28
+ end
29
+
30
+ # @param container [SmartCore::Container]
31
+ # @param key [String, Symbol]
32
+ # @return [Boolean]
33
+ #
34
+ # @api private
35
+ # @since 0.5.0
36
+ def key?(container, key)
37
+ extract(container, key)
38
+ true
39
+ rescue SmartCore::Container::ResolvingError
40
+ false
41
+ end
42
+
43
+ # @param container [SmartCore::Container]
44
+ # @param namespace_path [String, Symbol]
45
+ # @return [Boolean]
46
+ #
47
+ # @api private
48
+ # @since 0.5.0
49
+ def namespace?(container, namespace_path)
50
+ extract(container, namespace_path).is_a?(SmartCore::Container::Entities::Namespace)
51
+ rescue SmartCore::Container::ResolvingError
52
+ false
53
+ end
54
+
55
+ # @param container [SmartCore::Container]
56
+ # @param dependency_path [String, Symbol]
57
+ # @option memoized [NilClass, Boolean]
58
+ # @return [Boolean]
59
+ #
60
+ # @api private
61
+ # @since 0.5.0
62
+ def dependency?(container, dependency_path, memoized: nil)
63
+ entity = extract(container, dependency_path)
64
+
65
+ case
66
+ when memoized.nil?
67
+ entity.is_a?(SmartCore::Container::Entities::Dependency)
68
+ when !!memoized == true
69
+ entity.is_a?(SmartCore::Container::Entities::MemoizedDependency)
70
+ when !!memoized == false
71
+ entity.is_a?(SmartCore::Container::Entities::Dependency) &&
72
+ !entity.is_a?(SmartCore::Container::Entities::MemoizedDependency)
73
+ end
74
+ rescue SmartCore::Container::ResolvingError
75
+ false
27
76
  end
28
77
 
29
78
  # @param container [SmartCore::Container]
@@ -38,13 +87,16 @@ module SmartCore::Container::DependencyResolver
38
87
  #
39
88
  # @api private
40
89
  # @since 0.1.0
41
- # @version 0.1.0
90
+ # @version 0.8.1
42
91
  def resolve(container, dependency_path)
43
92
  entity = container
93
+ host_container = container
94
+
44
95
  Route.build(dependency_path).each do |cursor|
45
96
  entity = entity.registry.resolve(cursor.current_path)
46
- prevent_cursor_overflow!(cursor, entity)
47
- entity = entity.reveal
97
+ prevent_ambiguous_resolving!(cursor, entity)
98
+ entity = entity.reveal(host_container)
99
+ host_container = entity.is_a?(SmartCore::Container) ? entity : nil
48
100
  end
49
101
  entity
50
102
  rescue SmartCore::Container::ResolvingError => error
@@ -53,6 +105,28 @@ module SmartCore::Container::DependencyResolver
53
105
 
54
106
  private
55
107
 
108
+ # @param container [SmartCore::Container]
109
+ # @param entity_path [String, Symbol]
110
+ # @return [SmartCore::Container::Entities::Base]
111
+ #
112
+ # @api private
113
+ # @since 0.5.0
114
+ # @version 0.8.1
115
+ def extract(container, entity_path)
116
+ resolved_entity = container
117
+ extracted_entity = container
118
+ host_container = container
119
+
120
+ Route.build(entity_path).each do |cursor|
121
+ resolved_entity = resolved_entity.registry.resolve(cursor.current_path)
122
+ extracted_entity = resolved_entity
123
+ resolved_entity = resolved_entity.reveal(host_container)
124
+ host_container = resolved_entity.is_a?(SmartCore::Container) ? resolved_entity : nil
125
+ end
126
+
127
+ extracted_entity
128
+ end
129
+
56
130
  # @param cursor [SmartCore::Container::DependencyResolver::Route::Cursor]
57
131
  # @param entity [SmartCore::Container::Entities::Base]
58
132
  # @return [void]
@@ -60,8 +134,8 @@ module SmartCore::Container::DependencyResolver
60
134
  # @raise [SmartCore::Container::ResolvingError]
61
135
  #
62
136
  # @api private
63
- # @since 0.1.0
64
- def prevent_cursor_overflow!(cursor, entity)
137
+ # @since 0.5.0
138
+ def prevent_ambiguous_resolving!(cursor, entity)
65
139
  if cursor.last? && entity.is_a?(SmartCore::Container::Entities::Namespace)
66
140
  raise(
67
141
  SmartCore::Container::ResolvingError.new(
@@ -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